| /**************************************************************************** |
| * drivers/mtd/mtd_progmem.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 <stdint.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <errno.h> |
| |
| #include <nuttx/progmem.h> |
| #include <nuttx/fs/fs.h> |
| #include <nuttx/fs/ioctl.h> |
| #include <nuttx/mtd/mtd.h> |
| |
| #ifdef CONFIG_MTD_PROGMEM |
| |
| /**************************************************************************** |
| * 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 progmem_dev_s. |
| */ |
| |
| struct progmem_dev_s |
| { |
| /* Publicly visible representation of the interface */ |
| |
| struct mtd_dev_s mtd; |
| |
| /* Fields unique to the progmem MTD driver */ |
| |
| bool initialized; /* True: Already initialized */ |
| uint8_t blkshift; /* Log2 of the flash read/write block size */ |
| uint8_t ersshift; /* Log2 of the flash erase block size */ |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Internal helper functions */ |
| |
| static int32_t progmem_log2(size_t blocksize); |
| |
| /* MTD driver methods */ |
| |
| static int progmem_erase(FAR struct mtd_dev_s *dev, off_t startblock, |
| size_t nblocks); |
| static ssize_t progmem_bread(FAR struct mtd_dev_s *dev, off_t startblock, |
| size_t nblocks, FAR uint8_t *buf); |
| static ssize_t progmem_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, |
| size_t nblocks, FAR const uint8_t *buf); |
| static ssize_t progmem_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 progmem_write(FAR struct mtd_dev_s *dev, off_t offset, |
| size_t nbytes, FAR const uint8_t *buffer); |
| #endif |
| static int progmem_ioctl(FAR struct mtd_dev_s *dev, int cmd, |
| unsigned long arg); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* This structure holds the state of the MTD driver */ |
| |
| static struct progmem_dev_s g_progmem = |
| { |
| { |
| progmem_erase, |
| progmem_bread, |
| progmem_bwrite, |
| progmem_read, |
| #ifdef CONFIG_MTD_BYTE_WRITE |
| progmem_write, |
| #endif |
| progmem_ioctl, |
| NULL, |
| NULL, |
| "progmem", |
| } |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: progmem_log2 |
| * |
| * Description: |
| * Check block size is exact powers of two. |
| * |
| ****************************************************************************/ |
| |
| static int32_t progmem_log2(size_t blocksize) |
| { |
| uint32_t log2 = 0; |
| |
| /* Search every bit in the blocksize from bit zero to bit 30 (omitting bit |
| * 31 which is the sign bit on return) |
| */ |
| |
| for (log2 = 0; log2 < 31; log2++, blocksize >>= 1) |
| { |
| /* Is bit zero set? */ |
| |
| if ((blocksize & 1) != 0) |
| { |
| /* Yes... the value should be exactly one. We do not support |
| * block sizes that are not exact powers of two. |
| */ |
| |
| return blocksize == 1 ? log2 : -ENOSYS; |
| } |
| } |
| |
| return blocksize == 0 ? -EINVAL : -E2BIG; |
| } |
| |
| /**************************************************************************** |
| * Name: progmem_erase |
| * |
| * Description: |
| * Erase several blocks, each of the size previously reported. |
| * |
| ****************************************************************************/ |
| |
| static int progmem_erase(FAR struct mtd_dev_s *dev, off_t startblock, |
| size_t nblocks) |
| { |
| ssize_t result; |
| |
| /* Erase the specified blocks and return status (OK or a negated errno) */ |
| |
| while (nblocks > 0) |
| { |
| result = up_progmem_eraseblock(startblock); |
| if (result < 0) |
| { |
| return (int)result; |
| } |
| |
| /* Setup for the next pass through the loop */ |
| |
| startblock++; |
| nblocks--; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: progmem_bread |
| * |
| * Description: |
| * Read the specified number of blocks into the user provided buffer. |
| * |
| ****************************************************************************/ |
| |
| static ssize_t progmem_bread(FAR struct mtd_dev_s *dev, off_t startblock, |
| size_t nblocks, FAR uint8_t *buffer) |
| { |
| FAR struct progmem_dev_s *priv = (FAR struct progmem_dev_s *)dev; |
| #ifdef CONFIG_ARCH_HAVE_PROGMEM_READ |
| ssize_t result; |
| |
| result = up_progmem_read(up_progmem_getaddress(startblock), buffer, |
| nblocks << priv->blkshift); |
| return result < 0 ? result : nblocks; |
| #else |
| FAR const uint8_t *src; |
| |
| /* Read the specified blocks into the provided user buffer and return |
| * status (The positive, number of blocks actually read or a negated |
| * errno). |
| */ |
| |
| src = (FAR const uint8_t *)up_progmem_getaddress(startblock); |
| memcpy(buffer, src, nblocks << priv->blkshift); |
| return nblocks; |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: progmem_bwrite |
| * |
| * Description: |
| * Write the specified number of blocks from the user provided buffer. |
| * |
| ****************************************************************************/ |
| |
| static ssize_t progmem_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, |
| size_t nblocks, FAR const uint8_t *buffer) |
| { |
| FAR struct progmem_dev_s *priv = (FAR struct progmem_dev_s *)dev; |
| ssize_t result; |
| |
| /* Write the specified blocks from the provided user buffer and return |
| * status (The positive, number of blocks actually written or a negated |
| * errno) |
| */ |
| |
| result = up_progmem_write(up_progmem_getaddress(startblock), buffer, |
| nblocks << priv->blkshift); |
| return result < 0 ? result : nblocks; |
| } |
| |
| /**************************************************************************** |
| * Name: progmem_read |
| * |
| * Description: |
| * Read the specified number of bytes to the user provided buffer. |
| * |
| ****************************************************************************/ |
| |
| static ssize_t progmem_read(FAR struct mtd_dev_s *dev, off_t offset, |
| size_t nbytes, FAR uint8_t *buffer) |
| { |
| FAR struct progmem_dev_s *priv = (FAR struct progmem_dev_s *)dev; |
| off_t startblock = offset >> priv->blkshift; |
| #ifdef CONFIG_ARCH_HAVE_PROGMEM_READ |
| ssize_t result; |
| |
| result = up_progmem_read(up_progmem_getaddress(startblock) + |
| (offset & ((1 << priv->blkshift) - 1)), buffer, nbytes); |
| return result < 0 ? result : nbytes; |
| #else |
| FAR const uint8_t *src; |
| |
| /* Read the specified bytes into the provided user buffer and return |
| * status (The positive, number of bytes actually read or a negated |
| * errno) |
| */ |
| |
| src = (FAR const uint8_t *)up_progmem_getaddress(startblock) + |
| (offset & ((1 << priv->blkshift) - 1)); |
| memcpy(buffer, src, nbytes); |
| return nbytes; |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: progmem_write |
| * |
| * Description: |
| * Some FLASH parts have the ability to write an arbitrary number of |
| * bytes to an arbitrary offset on the device. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_MTD_BYTE_WRITE |
| static ssize_t progmem_write(FAR struct mtd_dev_s *dev, off_t offset, |
| size_t nbytes, FAR const uint8_t *buffer) |
| { |
| FAR struct progmem_dev_s *priv = (FAR struct progmem_dev_s *)dev; |
| off_t startblock; |
| ssize_t result; |
| |
| /* Write the specified blocks from the provided user buffer and return |
| * status (The positive number of blocks actually written or a negated |
| * errno). |
| */ |
| |
| startblock = offset >> priv->blkshift; |
| result = up_progmem_write(up_progmem_getaddress(startblock) + |
| (offset & ((1 << priv->blkshift) - 1)), buffer, nbytes); |
| return result < 0 ? result : nbytes; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: progmem_ioctl |
| ****************************************************************************/ |
| |
| static int progmem_ioctl(FAR struct mtd_dev_s *dev, int cmd, |
| unsigned long arg) |
| { |
| FAR struct progmem_dev_s *priv = (FAR struct progmem_dev_s *)dev; |
| int ret = -EINVAL; /* Assume good command with bad parameters */ |
| |
| 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. |
| * |
| * NOTE: that the device is treated as though it where just an |
| * array of fixed size blocks. That is most likely not true, |
| * but the client will expect the device logic to do whatever |
| * is necessary to make it appear so. |
| */ |
| |
| geo->blocksize = (1 << priv->blkshift); /* Size of one read/write block */ |
| geo->erasesize = (1 << priv->ersshift); /* Size of one erase block */ |
| geo->neraseblocks = up_progmem_neraseblocks(); /* Number of erase blocks */ |
| ret = OK; |
| } |
| } |
| break; |
| |
| case BIOC_PARTINFO: |
| { |
| FAR struct partition_info_s *info = |
| (FAR struct partition_info_s *)arg; |
| if (info != NULL) |
| { |
| info->numsectors = up_progmem_neraseblocks() << |
| (priv->ersshift - priv->blkshift); |
| info->sectorsize = 1 << priv->blkshift; |
| info->startsector = 0; |
| info->parent[0] = '\0'; |
| ret = OK; |
| } |
| } |
| break; |
| |
| case BIOC_XIPBASE: |
| { |
| FAR void **ppv = (FAR void**)arg; |
| |
| if (ppv) |
| { |
| /* Return (void*) base address of FLASH memory. */ |
| |
| *ppv = (FAR void *)up_progmem_getaddress(0); |
| ret = OK; |
| } |
| } |
| break; |
| |
| case MTDIOC_BULKERASE: |
| { |
| size_t nblocks = up_progmem_neraseblocks(); |
| |
| /* Erase the entire device */ |
| |
| ret = progmem_erase(dev, 0, nblocks); |
| } |
| break; |
| |
| case MTDIOC_ERASESTATE: |
| { |
| FAR uint8_t *result = (FAR uint8_t *)arg; |
| *result = up_progmem_erasestate(); |
| |
| ret = OK; |
| } |
| break; |
| |
| default: |
| ret = -ENOTTY; /* Bad command */ |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: progmem_initialize |
| * |
| * Description: |
| * Create and initialize an MTD device instance that can be used to access |
| * on-chip program memory. |
| * |
| * MTD devices are not registered in the file system, but are created as |
| * instances that can be bound to other functions (such as a block or |
| * character driver front end). |
| * |
| ****************************************************************************/ |
| |
| FAR struct mtd_dev_s *progmem_initialize(void) |
| { |
| FAR struct progmem_dev_s *priv = (FAR struct progmem_dev_s *)&g_progmem; |
| int32_t blkshift; |
| int32_t ersshift; |
| |
| /* Perform initialization if necessary */ |
| |
| if (!g_progmem.initialized) |
| { |
| /* Get the size of one block. Here we assume that the block size is |
| * uniform and that the size of block0 is the same as the size of any |
| * other block. |
| */ |
| |
| size_t blocksize = up_progmem_pagesize(0); |
| |
| /* Calculate Log2 of the flash read/write block size */ |
| |
| blkshift = progmem_log2(blocksize); |
| if (blkshift < 0) |
| { |
| return NULL; |
| } |
| |
| /* Calculate Log2 of the flash erase block size */ |
| |
| blocksize = up_progmem_erasesize(0); |
| |
| ersshift = progmem_log2(blocksize); |
| if (ersshift < 0) |
| { |
| return NULL; |
| } |
| |
| /* Save the configuration data */ |
| |
| g_progmem.blkshift = blkshift; |
| g_progmem.ersshift = ersshift; |
| g_progmem.initialized = true; |
| } |
| |
| /* Return the implementation-specific state structure as the MTD device */ |
| |
| return (FAR struct mtd_dev_s *)priv; |
| } |
| |
| #endif /* CONFIG_MTD_PROGMEM */ |