988 lines
30 KiB
C
988 lines
30 KiB
C
/****************************************************************************
|
|
* drivers/mtd/mtd_partition.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 <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 */
|
|
off_t neraseblocks; /* The number of erase blocks in the managed
|
|
* sub-region */
|
|
off_t blocksize; /* The size of one read/write block */
|
|
uint16_t blkpererase; /* Number of R/W blocks in one erase block */
|
|
|
|
#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 */
|
|
|
|
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->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 erasesize;
|
|
off_t readend;
|
|
|
|
erasesize = priv->blocksize * priv->blkpererase;
|
|
readend = (byoff + erasesize - 1) / erasesize;
|
|
return readend <= priv->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->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->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)
|
|
{
|
|
memset(geo, 0, sizeof(*geo));
|
|
|
|
/* Populate the geometry structure with information needed to
|
|
* know the capacity and how to access the device.
|
|
*/
|
|
|
|
geo->blocksize = priv->blocksize;
|
|
geo->erasesize = priv->blocksize * priv->blkpererase;
|
|
geo->neraseblocks = priv->neraseblocks;
|
|
ret = OK;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case BIOC_PARTINFO:
|
|
{
|
|
FAR struct partition_info_s *info =
|
|
(FAR struct partition_info_s *)arg;
|
|
if (info != NULL)
|
|
{
|
|
info->numsectors = priv->neraseblocks * priv->blkpererase;
|
|
info->sectorsize = priv->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->blocksize);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MTDIOC_BULKERASE:
|
|
{
|
|
/* Erase the entire partition */
|
|
|
|
ret = priv->parent->erase(priv->parent,
|
|
priv->firstblock / priv->blkpererase,
|
|
priv->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->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->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;
|
|
FAR struct mtd_geometry_s geo;
|
|
unsigned int blkpererase;
|
|
off_t erasestart;
|
|
off_t eraseend;
|
|
int ret;
|
|
|
|
DEBUGASSERT(mtd);
|
|
|
|
/* Get the geometry of the FLASH device */
|
|
|
|
ret = mtd->ioctl(mtd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&geo));
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: mtd->ioctl failed: %d\n", ret);
|
|
return NULL;
|
|
}
|
|
|
|
/* Get the number of blocks per erase. There must be an even number of
|
|
* blocks in one erase blocks.
|
|
*/
|
|
|
|
blkpererase = geo.erasesize / geo.blocksize;
|
|
DEBUGASSERT(blkpererase * geo.blocksize == 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 + blkpererase - 1) / blkpererase;
|
|
eraseend = (firstblock + nblocks) / blkpererase;
|
|
|
|
if (erasestart >= eraseend)
|
|
{
|
|
ferr("ERROR: sub-region too small\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* Verify that the sub-region is valid for this geometry */
|
|
|
|
if (eraseend > geo.neraseblocks)
|
|
{
|
|
ferr("ERROR: sub-region too big\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Initialize the partition device structure. (unsupported methods were
|
|
* nullified by kmm_zalloc).
|
|
*/
|
|
|
|
part->child.erase = part_erase;
|
|
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
|
|
part->child.name = "part";
|
|
|
|
part->parent = mtd;
|
|
part->firstblock = erasestart * blkpererase;
|
|
part->neraseblocks = eraseend - erasestart;
|
|
part->blocksize = geo.blocksize;
|
|
part->blkpererase = blkpererase;
|
|
|
|
#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
|