mirror of
https://github.com/apache/nuttx.git
synced 2026-03-23 22:43:57 +08:00
Since there is a storage device like RRAM that doesn't require erasing, the MTD erase function may not exist. Here, we should first check whether the erase interface exists before performing the erasing operation. Signed-off-by: jingfei <jingfei@xiaomi.com>
983 lines
30 KiB
C
983 lines
30 KiB
C
/****************************************************************************
|
|
* drivers/mtd/mtd_partition.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* 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 <sys/types.h>
|
|
#include <inttypes.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <debug.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <nuttx/mtd/mtd.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/fs/ioctl.h>
|
|
#ifdef CONFIG_FS_PROCFS
|
|
#include <nuttx/fs/procfs.h>
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define PART_NAME_MAX 15
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* This type represents the state of the MTD device. The struct mtd_dev_s
|
|
* must appear at the beginning of the definition so that you can freely
|
|
* cast between pointers to struct mtd_dev_s and struct mtd_partition_s.
|
|
*/
|
|
|
|
struct mtd_partition_s
|
|
{
|
|
/* This structure must reside at the beginning so that we can simply cast
|
|
* from struct mtd_dev_s * to struct mtd_partition_s *
|
|
*/
|
|
|
|
struct mtd_dev_s child; /* The "child" MTD vtable that manages the
|
|
* sub-region */
|
|
|
|
/* Other implementation specific data may follow here */
|
|
|
|
FAR struct mtd_dev_s *parent; /* The "parent" MTD driver that manages the
|
|
* entire FLASH */
|
|
off_t firstblock; /* Offset to the first block of the managed
|
|
* sub-region */
|
|
uint16_t blkpererase; /* Number of R/W blocks in one erase block */
|
|
struct mtd_geometry_s geo; /* The geometry for the partition */
|
|
|
|
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_PROCFS_EXCLUDE_PARTITIONS)
|
|
struct mtd_partition_s *pnext; /* Pointer to next partition struct */
|
|
#endif
|
|
#ifdef CONFIG_MTD_PARTITION_NAMES
|
|
char name[PART_NAME_MAX + 1]; /* Name of the partition */
|
|
#endif
|
|
};
|
|
|
|
/* This structure describes one open "file" */
|
|
|
|
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_PROCFS_EXCLUDE_PARTITIONS)
|
|
struct part_procfs_file_s
|
|
{
|
|
struct procfs_file_s base; /* Base open file structure */
|
|
struct mtd_partition_s *nextpart;
|
|
};
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* MTD driver methods */
|
|
|
|
static int part_erase(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks);
|
|
static ssize_t part_bread(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks, FAR uint8_t *buf);
|
|
static ssize_t part_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks, FAR const uint8_t *buf);
|
|
static ssize_t part_read(FAR struct mtd_dev_s *dev, off_t offset,
|
|
size_t nbytes, FAR uint8_t *buffer);
|
|
#ifdef CONFIG_MTD_BYTE_WRITE
|
|
static ssize_t part_write(FAR struct mtd_dev_s *dev, off_t offset,
|
|
size_t nbytes, FAR const uint8_t *buffer);
|
|
#endif
|
|
static int part_ioctl(FAR struct mtd_dev_s *dev, int cmd,
|
|
unsigned long arg);
|
|
static int part_isbad(FAR struct mtd_dev_s *dev, off_t block);
|
|
static int part_markbad(FAR struct mtd_dev_s *dev, off_t block);
|
|
|
|
/* File system methods */
|
|
|
|
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_PROCFS_EXCLUDE_PARTITIONS)
|
|
static int part_procfs_open(FAR struct file *filep,
|
|
FAR const char *relpath, int oflags, mode_t mode);
|
|
static int part_procfs_close(FAR struct file *filep);
|
|
static ssize_t part_procfs_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
|
|
static int part_procfs_dup(FAR const struct file *oldp,
|
|
FAR struct file *newp);
|
|
|
|
#if 0 /* Not implemented */
|
|
static int part_procfs_opendir(const char *relpath,
|
|
FAR struct fs_dirent_s *dir);
|
|
static int part_procfs_closedir(FAR struct fs_dirent_s *dir);
|
|
static int part_procfs_readdir(FAR struct fs_dirent_s *dir,
|
|
FAR struct dirent *entry);
|
|
static int part_procfs_rewinddir(FAR struct fs_dirent_s *dir);
|
|
#endif
|
|
|
|
static int part_procfs_stat(FAR const char *relpath,
|
|
FAR struct stat *buf);
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_PROCFS_EXCLUDE_PARTITIONS)
|
|
static struct mtd_partition_s *g_pfirstpartition = NULL;
|
|
|
|
const struct procfs_operations g_part_operations =
|
|
{
|
|
part_procfs_open, /* open */
|
|
part_procfs_close, /* close */
|
|
part_procfs_read, /* read */
|
|
NULL, /* write */
|
|
NULL, /* poll */
|
|
|
|
part_procfs_dup, /* dup */
|
|
|
|
NULL, /* opendir */
|
|
NULL, /* closedir */
|
|
NULL, /* readdir */
|
|
NULL, /* rewinddir */
|
|
|
|
part_procfs_stat /* stat */
|
|
};
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: part_blockcheck
|
|
*
|
|
* Description:
|
|
* Check if the provided block offset lies within the partition
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool part_blockcheck(FAR struct mtd_partition_s *priv, off_t block)
|
|
{
|
|
off_t partsize;
|
|
|
|
partsize = priv->geo.neraseblocks * priv->blkpererase;
|
|
return block < partsize;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: part_bytecheck
|
|
*
|
|
* Description:
|
|
* Check if the provided byte offset lies within the partition
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool part_bytecheck(FAR struct mtd_partition_s *priv, off_t byoff)
|
|
{
|
|
off_t readend;
|
|
|
|
readend = (byoff + priv->geo.erasesize - 1) / priv->geo.erasesize;
|
|
return readend <= priv->geo.neraseblocks;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: part_erase
|
|
*
|
|
* Description:
|
|
* Erase several blocks, each of the size previously reported.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int part_erase(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks)
|
|
{
|
|
FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)dev;
|
|
off_t eoffset;
|
|
|
|
DEBUGASSERT(priv);
|
|
|
|
/* Make sure that erase would not extend past the end of the partition */
|
|
|
|
if (!part_blockcheck(priv, (startblock + nblocks - 1) * priv->blkpererase))
|
|
{
|
|
ferr("ERROR: Erase beyond the end of the partition\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* Just add the partition offset to the requested block and let the
|
|
* underlying MTD driver perform the erase.
|
|
*
|
|
* NOTE: the offset here is in units of erase blocks.
|
|
*/
|
|
|
|
eoffset = priv->firstblock / priv->blkpererase;
|
|
DEBUGASSERT(eoffset * priv->blkpererase == priv->firstblock);
|
|
|
|
return priv->parent->erase(priv->parent, startblock + eoffset, nblocks);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: part_bread
|
|
*
|
|
* Description:
|
|
* Read the specified number of blocks into the user provided buffer.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static ssize_t part_bread(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks, FAR uint8_t *buf)
|
|
{
|
|
FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)dev;
|
|
|
|
DEBUGASSERT(priv && (buf || nblocks == 0));
|
|
|
|
/* Make sure that read would not extend past the end of the partition */
|
|
|
|
if (!part_blockcheck(priv, startblock + nblocks - 1))
|
|
{
|
|
ferr("ERROR: Read beyond the end of the partition\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* Just add the partition offset to the requested block and let the
|
|
* underlying MTD driver perform the read.
|
|
*/
|
|
|
|
return priv->parent->bread(priv->parent, startblock + priv->firstblock,
|
|
nblocks, buf);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: part_bwrite
|
|
*
|
|
* Description:
|
|
* Write the specified number of blocks from the user provided buffer.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static ssize_t part_bwrite(FAR struct mtd_dev_s *dev, off_t startblock,
|
|
size_t nblocks, FAR const uint8_t *buf)
|
|
{
|
|
FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)dev;
|
|
|
|
DEBUGASSERT(priv && (buf || nblocks == 0));
|
|
|
|
/* Make sure that write would not extend past the end of the partition */
|
|
|
|
if (!part_blockcheck(priv, startblock + nblocks - 1))
|
|
{
|
|
ferr("ERROR: Write beyond the end of the partition\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* Just add the partition offset to the requested block and let the
|
|
* underlying MTD driver perform the write.
|
|
*/
|
|
|
|
return priv->parent->bwrite(priv->parent, startblock + priv->firstblock,
|
|
nblocks, buf);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: part_read
|
|
*
|
|
* Description:
|
|
* Read the specified number of bytes to the user provided buffer.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static ssize_t part_read(FAR struct mtd_dev_s *dev, off_t offset,
|
|
size_t nbytes, FAR uint8_t *buffer)
|
|
{
|
|
FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)dev;
|
|
off_t newoffset;
|
|
|
|
DEBUGASSERT(priv && (buffer || nbytes == 0));
|
|
|
|
/* Does the underlying MTD device support the read method? */
|
|
|
|
if (priv->parent->read)
|
|
{
|
|
/* Make sure that read would not extend past the end of the partition */
|
|
|
|
if (!part_bytecheck(priv, offset + nbytes - 1))
|
|
{
|
|
ferr("ERROR: Read beyond the end of the partition\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* Just add the partition offset to the requested block and let the
|
|
* underlying MTD driver perform the read.
|
|
*/
|
|
|
|
newoffset = offset + priv->firstblock * priv->geo.blocksize;
|
|
return priv->parent->read(priv->parent, newoffset, nbytes, buffer);
|
|
}
|
|
|
|
/* The underlying MTD driver does not support the read() method */
|
|
|
|
return -ENOSYS;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: part_write
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_MTD_BYTE_WRITE
|
|
static ssize_t part_write(FAR struct mtd_dev_s *dev, off_t offset,
|
|
size_t nbytes, FAR const uint8_t *buffer)
|
|
{
|
|
FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)dev;
|
|
off_t newoffset;
|
|
|
|
DEBUGASSERT(priv && (buffer || nbytes == 0));
|
|
|
|
/* Does the underlying MTD device support the write method? */
|
|
|
|
if (priv->parent->write)
|
|
{
|
|
/* Make sure that write would not extend past the end of the
|
|
* partition
|
|
*/
|
|
|
|
if (!part_bytecheck(priv, offset + nbytes - 1))
|
|
{
|
|
ferr("ERROR: Write beyond the end of the partition\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* Just add the partition offset to the requested block and let the
|
|
* underlying MTD driver perform the write.
|
|
*/
|
|
|
|
newoffset = offset + priv->firstblock * priv->geo.blocksize;
|
|
return priv->parent->write(priv->parent, newoffset, nbytes, buffer);
|
|
}
|
|
|
|
/* The underlying MTD driver does not support the write() method */
|
|
|
|
return -ENOSYS;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: part_ioctl
|
|
****************************************************************************/
|
|
|
|
static int part_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg)
|
|
{
|
|
FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)dev;
|
|
int ret = -EINVAL; /* Assume good command with bad parameters */
|
|
|
|
DEBUGASSERT(priv);
|
|
|
|
switch (cmd)
|
|
{
|
|
case MTDIOC_GEOMETRY:
|
|
{
|
|
FAR struct mtd_geometry_s *geo = (FAR struct mtd_geometry_s *)arg;
|
|
if (geo != NULL)
|
|
{
|
|
memcpy(geo, &priv->geo, sizeof(*geo));
|
|
ret = OK;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case BIOC_PARTINFO:
|
|
{
|
|
FAR struct partition_info_s *info =
|
|
(FAR struct partition_info_s *)arg;
|
|
if (info != NULL)
|
|
{
|
|
info->numsectors = priv->geo.neraseblocks * priv->blkpererase;
|
|
info->sectorsize = priv->geo.blocksize;
|
|
info->startsector = priv->firstblock;
|
|
|
|
strlcpy(info->parent, priv->parent->name,
|
|
sizeof(info->parent));
|
|
|
|
ret = OK;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case BIOC_XIPBASE:
|
|
{
|
|
FAR void **ppv = (FAR void**)arg;
|
|
unsigned long base;
|
|
|
|
if (ppv)
|
|
{
|
|
/* Get the XIP base of the entire FLASH */
|
|
|
|
ret = priv->parent->ioctl(priv->parent, BIOC_XIPBASE,
|
|
(unsigned long)((uintptr_t)&base));
|
|
if (ret == OK)
|
|
{
|
|
/* Add the offset of this partition to the XIP base and
|
|
* return the sum to the caller.
|
|
*/
|
|
|
|
*ppv = (FAR void *)(uintptr_t)
|
|
(base + priv->firstblock * priv->geo.blocksize);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MTDIOC_BULKERASE:
|
|
{
|
|
/* Erase the entire partition */
|
|
|
|
ret = priv->parent->erase(priv->parent,
|
|
priv->firstblock / priv->blkpererase,
|
|
priv->geo.neraseblocks);
|
|
}
|
|
break;
|
|
|
|
case MTDIOC_ERASESECTORS:
|
|
{
|
|
/* Erase sectors as defined in mtd_erase_s structure */
|
|
|
|
FAR struct mtd_erase_s *erase = (FAR struct mtd_erase_s *)arg;
|
|
|
|
ret = priv->parent->erase(priv->parent,
|
|
priv->firstblock / priv->blkpererase +
|
|
erase->startblock,
|
|
erase->nblocks);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
/* Pass any unhandled ioctl() calls to the underlying driver */
|
|
|
|
ret = priv->parent->ioctl(priv->parent, cmd, arg);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: part_isbad
|
|
*
|
|
* Description:
|
|
* Check bad block for the specified block number.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int part_isbad(FAR struct mtd_dev_s *dev, off_t block)
|
|
{
|
|
FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)dev;
|
|
|
|
DEBUGASSERT(priv);
|
|
|
|
/* Does the underlying MTD device support the isbad method? */
|
|
|
|
if (priv->parent->isbad)
|
|
{
|
|
return priv->parent->isbad(priv->parent, block +
|
|
priv->firstblock / priv->blkpererase);
|
|
}
|
|
|
|
/* The underlying MTD driver does not support the isbad() method */
|
|
|
|
return -ENOSYS;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: part_markbad
|
|
*
|
|
* Description:
|
|
* Mark bad block for the specified block number.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int part_markbad(FAR struct mtd_dev_s *dev, off_t block)
|
|
{
|
|
FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)dev;
|
|
|
|
DEBUGASSERT(priv);
|
|
|
|
/* Does the underlying MTD device support the markbad method? */
|
|
|
|
if (priv->parent->markbad)
|
|
{
|
|
return priv->parent->markbad(priv->parent, block +
|
|
priv->firstblock / priv->blkpererase);
|
|
}
|
|
|
|
/* The underlying MTD driver does not support the markbad() method */
|
|
|
|
return -ENOSYS;
|
|
}
|
|
|
|
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_PROCFS_EXCLUDE_PARTITIONS)
|
|
|
|
/****************************************************************************
|
|
* Name: part_procfs_open
|
|
****************************************************************************/
|
|
|
|
static int part_procfs_open(FAR struct file *filep, FAR const char *relpath,
|
|
int oflags, mode_t mode)
|
|
{
|
|
FAR struct part_procfs_file_s *attr;
|
|
|
|
finfo("Open '%s'\n", relpath);
|
|
|
|
/* PROCFS is read-only. Any attempt to open with any kind of write
|
|
* access is not permitted.
|
|
*
|
|
* REVISIT: Write-able proc files could be quite useful.
|
|
*/
|
|
|
|
if ((oflags & O_WRONLY) != 0 || (oflags & O_RDONLY) == 0)
|
|
{
|
|
ferr("ERROR: Only O_RDONLY supported\n");
|
|
return -EACCES;
|
|
}
|
|
|
|
/* Allocate a container to hold the task and attribute selection */
|
|
|
|
attr = (FAR struct part_procfs_file_s *)
|
|
kmm_zalloc(sizeof(struct part_procfs_file_s));
|
|
if (!attr)
|
|
{
|
|
ferr("ERROR: Failed to allocate file attributes\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Initialize the file attributes */
|
|
|
|
attr->nextpart = g_pfirstpartition;
|
|
|
|
/* Save the index as the open-specific state in filep->f_priv */
|
|
|
|
filep->f_priv = (FAR void *)attr;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: part_procfs_close
|
|
****************************************************************************/
|
|
|
|
static int part_procfs_close(FAR struct file *filep)
|
|
{
|
|
FAR struct part_procfs_file_s *attr;
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
attr = (FAR struct part_procfs_file_s *)filep->f_priv;
|
|
DEBUGASSERT(attr);
|
|
|
|
/* Release the file attributes structure */
|
|
|
|
kmm_free(attr);
|
|
filep->f_priv = NULL;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: part_procfs_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t part_procfs_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct part_procfs_file_s *attr;
|
|
FAR struct mtd_geometry_s geo;
|
|
ssize_t total = 0;
|
|
ssize_t blkpererase;
|
|
ssize_t ret;
|
|
#ifdef CONFIG_MTD_PARTITION_NAMES
|
|
char partname[PART_NAME_MAX + 1];
|
|
FAR const char *ptr;
|
|
uint8_t x;
|
|
#endif
|
|
|
|
finfo("buffer=%p buflen=%d\n", buffer, (int)buflen);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
attr = (FAR struct part_procfs_file_s *)filep->f_priv;
|
|
DEBUGASSERT(attr);
|
|
|
|
/* If we are at the end of the list, then return 0 signifying the
|
|
* end-of-file. This also handles the special case when there are
|
|
* no registered MTD partitions.
|
|
*/
|
|
|
|
if (attr->nextpart)
|
|
{
|
|
/* Output a header before the first entry */
|
|
|
|
if (attr->nextpart == g_pfirstpartition)
|
|
{
|
|
#ifdef CONFIG_MTD_PARTITION_NAMES
|
|
total = snprintf(buffer, buflen, "%-*s Start Size MTD\n",
|
|
PART_NAME_MAX, "Name");
|
|
#else
|
|
total = snprintf(buffer, buflen, " Start Size MTD\n");
|
|
#endif
|
|
}
|
|
|
|
/* Provide the requested data */
|
|
|
|
do
|
|
{
|
|
/* Get the geometry of the FLASH device */
|
|
|
|
ret = attr->nextpart->parent->ioctl(attr->nextpart->parent,
|
|
MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&geo));
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: mtd->ioctl failed: %zd\n", ret);
|
|
return 0;
|
|
}
|
|
|
|
/* Get the number of blocks per erase. There must be an even
|
|
* number of blocks in one erase blocks.
|
|
*/
|
|
|
|
blkpererase = geo.erasesize / geo.blocksize;
|
|
|
|
/* Copy data from the next known partition */
|
|
|
|
#ifdef CONFIG_MTD_PARTITION_NAMES
|
|
ptr = attr->nextpart->name;
|
|
for (x = 0; x < sizeof(partname) - 1; x++)
|
|
{
|
|
/* Test for end of partition name */
|
|
|
|
if (*ptr == ',' || *ptr == '\0')
|
|
{
|
|
/* Perform space fill for alignment */
|
|
|
|
partname[x] = ' ';
|
|
}
|
|
else
|
|
{
|
|
/* Copy next byte of partition name */
|
|
|
|
partname[x] = *ptr++;
|
|
}
|
|
}
|
|
|
|
partname[x] = '\0';
|
|
|
|
/* Terminate the partition name and add to output buffer */
|
|
|
|
ret = snprintf(&buffer[total], buflen - total,
|
|
"%s%7ju %7ju %s\n",
|
|
partname,
|
|
(uintmax_t)attr->nextpart->firstblock / blkpererase,
|
|
(uintmax_t)attr->nextpart->geo.neraseblocks,
|
|
attr->nextpart->parent->name);
|
|
#else
|
|
ret = snprintf(&buffer[total], buflen - total, "%7ju %7ju %s\n",
|
|
(uintmax_t)attr->nextpart->firstblock / blkpererase,
|
|
(uintmax_t)attr->nextpart->geo.neraseblocks,
|
|
attr->nextpart->parent->name);
|
|
#endif
|
|
|
|
if (ret + total < buflen)
|
|
{
|
|
/* It fit in the buffer totally. Advance total and move to
|
|
* next partition.
|
|
*/
|
|
|
|
total += ret;
|
|
attr->nextpart = attr->nextpart->pnext;
|
|
}
|
|
else
|
|
{
|
|
/* This one didn't fit completely. Truncate the partial
|
|
* entry and break the loop.
|
|
*/
|
|
|
|
buffer[total] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
while (attr->nextpart);
|
|
}
|
|
|
|
/* Update the file offset */
|
|
|
|
if (total > 0)
|
|
{
|
|
filep->f_pos += total;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: part_procfs_dup
|
|
*
|
|
* Description:
|
|
* Duplicate open file data in the new file structure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int part_procfs_dup(FAR const struct file *oldp,
|
|
FAR struct file *newp)
|
|
{
|
|
FAR struct part_procfs_file_s *oldattr;
|
|
FAR struct part_procfs_file_s *newattr;
|
|
|
|
finfo("Dup %p->%p\n", oldp, newp);
|
|
|
|
/* Recover our private data from the old struct file instance */
|
|
|
|
oldattr = (FAR struct part_procfs_file_s *)oldp->f_priv;
|
|
DEBUGASSERT(oldattr);
|
|
|
|
/* Allocate a new container to hold the task and attribute selection */
|
|
|
|
newattr = (FAR struct part_procfs_file_s *)
|
|
kmm_zalloc(sizeof(struct part_procfs_file_s));
|
|
if (!newattr)
|
|
{
|
|
ferr("ERROR: Failed to allocate file attributes\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* The copy the file attribute from the old attributes to the new */
|
|
|
|
memcpy(newattr, oldattr, sizeof(struct part_procfs_file_s));
|
|
|
|
/* Save the new attributes in the new file structure */
|
|
|
|
newp->f_priv = (FAR void *)newattr;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: part_procfs_stat
|
|
*
|
|
* Description: Return information about a file or directory
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int part_procfs_stat(const char *relpath, struct stat *buf)
|
|
{
|
|
/* File/directory size, access block size */
|
|
|
|
buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
|
|
buf->st_size = 0;
|
|
buf->st_blksize = 0;
|
|
buf->st_blocks = 0;
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: mtd_partition
|
|
*
|
|
* Description:
|
|
* Give an instance of an MTD driver, create a flash partition, ie.,
|
|
* another MTD driver instance that only operates with a sub-region of
|
|
* FLASH media. That sub-region is defined by a sector offset and a
|
|
* sector count (where the size of a sector is provided the by parent MTD
|
|
* driver).
|
|
*
|
|
* NOTE: Since there may be a number of MTD partition drivers operating on
|
|
* the same, underlying FLASH driver, that FLASH driver must be capable
|
|
* of enforcing mutually exclusive access to the FLASH device. Without
|
|
* partitions, that mutual exclusion would be provided by the file system
|
|
* above the FLASH driver.
|
|
*
|
|
* Input Parameters:
|
|
* mtd - The MTD device to be partitioned
|
|
* firstblock - The offset in bytes to the first block
|
|
* nblocks - The number of blocks in the partition
|
|
*
|
|
* Returned Value:
|
|
* On success, another MTD device representing the partition is returned.
|
|
* A NULL value is returned on a failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR struct mtd_dev_s *mtd_partition(FAR struct mtd_dev_s *mtd,
|
|
off_t firstblock,
|
|
off_t nblocks)
|
|
{
|
|
FAR struct mtd_partition_s *part;
|
|
off_t erasestart;
|
|
off_t eraseend;
|
|
int ret;
|
|
|
|
DEBUGASSERT(mtd);
|
|
|
|
/* Allocate a partition device structure */
|
|
|
|
part = (FAR struct mtd_partition_s *)
|
|
kmm_zalloc(sizeof(struct mtd_partition_s));
|
|
if (!part)
|
|
{
|
|
ferr("ERROR: Failed to allocate memory for the partition device\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* Get the geometry of the FLASH device */
|
|
|
|
ret = mtd->ioctl(mtd, MTDIOC_GEOMETRY,
|
|
(unsigned long)((uintptr_t)&part->geo));
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: mtd->ioctl failed: %d\n", ret);
|
|
kmm_free(part);
|
|
return NULL;
|
|
}
|
|
|
|
/* Get the number of blocks per erase. There must be an even number of
|
|
* blocks in one erase blocks.
|
|
*/
|
|
|
|
part->blkpererase = part->geo.erasesize / part->geo.blocksize;
|
|
DEBUGASSERT(part->blkpererase * part->geo.blocksize ==
|
|
part->geo.erasesize);
|
|
|
|
/* Adjust the offset and size if necessary so that they are multiples of
|
|
* the erase block size (making sure that we do not go outside of the
|
|
* requested sub-region). NOTE that 'eraseend' is the first erase block
|
|
* beyond the sub-region.
|
|
*/
|
|
|
|
erasestart = (firstblock + part->blkpererase - 1) / part->blkpererase;
|
|
eraseend = (firstblock + nblocks) / part->blkpererase;
|
|
|
|
if (erasestart >= eraseend)
|
|
{
|
|
ferr("ERROR: sub-region too small\n");
|
|
kmm_free(part);
|
|
return NULL;
|
|
}
|
|
|
|
/* Verify that the sub-region is valid for this geometry */
|
|
|
|
if (eraseend > part->geo.neraseblocks)
|
|
{
|
|
ferr("ERROR: sub-region too big\n");
|
|
kmm_free(part);
|
|
return NULL;
|
|
}
|
|
|
|
/* Initialize the partition device structure. (unsupported methods were
|
|
* nullified by kmm_zalloc).
|
|
*/
|
|
|
|
part->child.erase = mtd->erase ? part_erase : NULL;
|
|
part->child.bread = part_bread;
|
|
part->child.bwrite = part_bwrite;
|
|
part->child.read = mtd->read ? part_read : NULL;
|
|
part->child.ioctl = part_ioctl;
|
|
part->child.isbad = part_isbad;
|
|
part->child.markbad = part_markbad;
|
|
#ifdef CONFIG_MTD_BYTE_WRITE
|
|
part->child.write = mtd->write ? part_write : NULL;
|
|
#endif
|
|
#ifdef CONFIG_MTD_PARTITION_NAMES
|
|
part->child.name = part->name;
|
|
#else
|
|
part->child.name = "part";
|
|
#endif
|
|
part->parent = mtd;
|
|
part->firstblock = erasestart * part->blkpererase;
|
|
part->geo.neraseblocks = eraseend - erasestart;
|
|
|
|
#ifdef CONFIG_MTD_PARTITION_NAMES
|
|
strlcpy(part->name, "(noname)", sizeof(part->name));
|
|
#endif
|
|
|
|
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_PROCFS_EXCLUDE_PARTITIONS)
|
|
/* Add this partition to the list of known partitions */
|
|
|
|
if (g_pfirstpartition == NULL)
|
|
{
|
|
g_pfirstpartition = part;
|
|
}
|
|
else
|
|
{
|
|
struct mtd_partition_s *plast;
|
|
|
|
/* Add the partition to the end of the list */
|
|
|
|
part->pnext = NULL;
|
|
|
|
plast = g_pfirstpartition;
|
|
while (plast->pnext != NULL)
|
|
{
|
|
/* Get pointer to next partition */
|
|
|
|
plast = plast->pnext;
|
|
}
|
|
|
|
plast->pnext = part;
|
|
}
|
|
#endif
|
|
|
|
/* Return the implementation-specific state structure as the MTD device */
|
|
|
|
return &part->child;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: mtd_setpartitionname
|
|
*
|
|
* Description:
|
|
* Sets the name of the specified partition.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_MTD_PARTITION_NAMES
|
|
int mtd_setpartitionname(FAR struct mtd_dev_s *mtd, FAR const char *name)
|
|
{
|
|
FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)mtd;
|
|
|
|
if (priv == NULL || name == NULL)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Allocate space for the name */
|
|
|
|
strlcpy(priv->name, name, sizeof(priv->name));
|
|
return OK;
|
|
}
|
|
#endif
|