bsps/shared/dev/nor: Add SFDP and CFI parsers

This commit is contained in:
Kinsey Moore
2023-06-02 15:07:50 -05:00
committed by Kinsey Moore
parent 2179cd2fee
commit f77bb9762a
3 changed files with 550 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* Copyright (C) 2023 On-Line Applications Research Corporation (OAR)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <rtems/rtems/status.h>
#include <stddef.h>
#include <stdint.h>
/**
* This struct holds the information parsed from the Common Flash Memory
* Information (CFI) read from a flash chip.
*/
typedef struct NOR_Config_Data {
/* JEDEC ID */
uint32_t jedec_id;
/* Sector size in bytes */
uint32_t SectorSize;
/* Number of sectors */
uint32_t NumSectors;
/* Page size in bytes. This will default to 256 if unavailable. */
uint16_t PageSize;
/* This is the size of the flash device */
uint64_t DeviceSize;
/*
* Alternative Sector size in bytes. This will be 0 if the alternative sector
* size is unsupported or unavailable.
*/
uint32_t AltSectorSize;
/* The command byte to erase sectors in the alternative size */
uint8_t AltSectorCmd;
/* Number of sectors for the alternative sector size */
uint32_t NumAltSectors;
} NOR_Config_Data;
/**
*
* This function parses the provided buffer of CFI data into a NOR_Config_Data
* structure.
*
* @param cfi_raw is a buffer containing CFI data.
* @param cfi_raw_len is the length of the data in cfi_raw.
* @param data is a pointer to a NOR_Config_Data struct to be filled with data
* about the flash chip.
*
* @return RTEMS_SUCCESSFUL if successful.
*/
rtems_status_code CFI_parse_from_buffer(
uint8_t *cfi_raw,
size_t cfi_raw_len,
NOR_Config_Data *data
);
/**
*
* This function parses the provided buffer of SFDP data into a NOR_Config_Data
* structure.
*
* @param sfdp_raw is a buffer containing SFDP data.
* @param sfdp_raw_len is the length of the data in sfdp_raw.
* @param data is a pointer to a NOR_Config_Data struct to be filled with data
* about the flash chip.
*
* @return RTEMS_SUCCESSFUL if successful.
*/
rtems_status_code SFDP_parse_from_buffer(
uint8_t *sfdp_raw,
size_t sfdp_raw_len,
NOR_Config_Data *data
);
/**
* @brief Acquire data from the NOR chip.
*
* @param[in] context The context data used to retrieve bytes.
* @param[in] offset The offset to read from the flash begin in bytes.
* @param[in] length The size of the buffer in bytes.
*
* @retval NULL on failure.
* @retval pointer to the requested data on success.
*/
typedef uint8_t *(*NOR_Config_Resource_Acquire)(
void *context,
uint32_t offset,
size_t length
);
/**
* @brief Release data acquired from the NOR chip.
*
* @param[in] context The context data used to retrieve bytes.
* @param[in] data The data previously acquired.
*/
typedef void (*NOR_Config_Resource_Release)(
void *context,
uint8_t *data
);
typedef struct NOR_Config_Accessor {
/* The context provided to the acquire and release functions */
void *context;
/* The function used to acquire a block of data */
NOR_Config_Resource_Acquire acquire;
/* The function used to release a block of data */
NOR_Config_Resource_Release release;
} NOR_Config_Accessor;
/**
*
* This function parses a SFDP configuration space into a NOR_Config_Data
* structure.
*
* @param accessor is the accessor for the SFDP configuration space.
* @param data is a pointer to a NOR_Config_Data struct to be filled with data
* about the flash chip.
*
* @return RTEMS_SUCCESSFUL if successful.
*/
rtems_status_code SFDP_parse(
NOR_Config_Accessor *accessor,
NOR_Config_Data *data
);

View File

@@ -0,0 +1,402 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* Copyright (C) 2023 On-Line Applications Research Corporation (OAR)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <dev/nor/config-parser.h>
#include <string.h>
static int read_config_byte(uint8_t *config_raw, size_t config_raw_len, int datalen, int desired_byte, uint8_t *read_byte)
{
/* access outside buffer */
if (desired_byte >= config_raw_len) {
return 1;
}
if (datalen != 0) {
/* access past self-declared limit */
if (desired_byte >= datalen) {
return 1;
}
}
*read_byte = config_raw[desired_byte];
return 0;
}
static int verify_config_string(uint8_t *config_raw, size_t config_raw_len, int datalen, int startbyte, char *config_string)
{
uint8_t bufbyte;
int endbyte = strlen(config_string) + startbyte;
for (;startbyte < endbyte; startbyte++, config_string++) {
if (read_config_byte(config_raw, config_raw_len, datalen, startbyte, &bufbyte)) {
return 1;
}
if (bufbyte != *config_string) {
return 1;
}
}
return 0;
}
/* Read two consecutive bytes as a little endian short */
static int read_config_short(uint8_t *config_raw, size_t config_raw_len, int datalen, int startbyte, uint16_t *config_short)
{
uint8_t bufbyte;
if (read_config_byte(config_raw, config_raw_len, datalen, startbyte + 1, &bufbyte)) {
return 1;
}
*config_short = bufbyte;
*config_short <<= 8;
if (read_config_byte(config_raw, config_raw_len, datalen, startbyte, &bufbyte)) {
return 1;
}
*config_short |= bufbyte;
return 0;
}
/* Read two consecutive shorts as an int */
static int read_config_int(uint8_t *config_raw, size_t config_raw_len, int datalen, int startbyte, uint32_t *config_int)
{
uint16_t bufshort;
if (read_config_short(config_raw, config_raw_len, datalen, startbyte + 2, &bufshort)) {
return 1;
}
*config_int = bufshort;
*config_int <<= 16;
if (read_config_short(config_raw, config_raw_len, datalen, startbyte, &bufshort)) {
return 1;
}
*config_int |= bufshort;
return 0;
}
rtems_status_code CFI_parse_from_buffer(
uint8_t *cfi_raw,
size_t cfi_raw_len,
NOR_Config_Data *data
)
{
memset(data, 0, sizeof(NOR_Config_Data));
if (cfi_raw_len < 4 || cfi_raw_len > 512) {
return RTEMS_INVALID_SIZE;
}
data->jedec_id = ((cfi_raw[0] << 16) | (cfi_raw[1] << 8) | cfi_raw[2]);
int datalen = cfi_raw[3];
if (datalen != 0) {
/*
* datalen as read is remaining valid bytes after 0x3 (the 4th byte), so
* account for those bytes since this is used as an absolute index limit
* */
datalen += 4;
}
/* verify query string ("QRY") exists */
if (verify_config_string(cfi_raw, cfi_raw_len, datalen, 0x10, "QRY")) {
return RTEMS_INVALID_NAME;
}
/* get device geometry */
uint8_t bufbyte;
/* Device size for at least s25fl512s is off by 1, calculate with sectors */
if (read_config_byte(cfi_raw, cfi_raw_len, datalen, 0x27, &bufbyte)) {
return RTEMS_INVALID_ADDRESS;
}
data->DeviceSize = 1UL << bufbyte;
uint16_t page_power;
if (read_config_short(cfi_raw, cfi_raw_len, datalen, 0x2a, &page_power)) {
return RTEMS_INVALID_ADDRESS;
}
data->PageSize = 1UL << page_power;
if (read_config_byte(cfi_raw, cfi_raw_len, datalen, 0x2c, &bufbyte)) {
return RTEMS_INVALID_ADDRESS;
}
/* this parser only supports one erase block region */
if (bufbyte != 1) {
return RTEMS_TOO_MANY;
}
uint16_t num_sectors_sub;
if (read_config_short(cfi_raw, cfi_raw_len, datalen, 0x2d, &num_sectors_sub)) {
return RTEMS_INVALID_ADDRESS;
}
data->NumSectors = num_sectors_sub + 1;
uint16_t sector_base;
if (read_config_short(cfi_raw, cfi_raw_len, datalen, 0x2f, &sector_base)) {
return RTEMS_INVALID_ADDRESS;
}
data->SectorSize = sector_base * 256UL;
data->DeviceSize = data->NumSectors * data->SectorSize;
return RTEMS_SUCCESSFUL;
}
static int parse_sfdp_jedec_table(
uint8_t *tdata,
size_t tlen,
int version,
NOR_Config_Data *data
)
{
/* locals to be applied once parsing is guaranteed successful */
NOR_Config_Data local = {0,};
local.PageSize = 256;
int datalen = 0;
/* Read raw size from second dword */
uint32_t raw_size;
int raw_size_offset = 4;
if (read_config_int(tdata, tlen, datalen, raw_size_offset, &raw_size)) {
return -1;
}
if (raw_size & 0x80000000UL) {
raw_size &= 0x7FFFFFFFUL;
local.DeviceSize = 1ULL << raw_size;
} else {
local.DeviceSize = raw_size + 1;
}
/* Device size is specified in bits, convert to bytes */
local.DeviceSize /= 8;
/* Parse sector information from dwords 8 and 9 */
int sector_erase_dword_offset = 7;
int sector_erase_offset = 4 * sector_erase_dword_offset;
uint8_t bufbyte;
for (int sect_erase_index = 0; sect_erase_index < 4; sect_erase_index++) {
int sector_info_offset = sector_erase_offset + 2 * sect_erase_index;
if (read_config_byte(tdata, tlen, datalen, sector_info_offset, &bufbyte)) {
return -1;
}
if (bufbyte == 0) {
/* empty sector descriptor */
continue;
}
int sector_size = 1UL << bufbyte;
if (read_config_byte(tdata, tlen, datalen, sector_info_offset + 1, &bufbyte)) {
return -1;
}
if (bufbyte == 0xD8) {
/* This is the 'normal' sector size */
local.SectorSize = sector_size;
local.NumSectors = (uint32_t)(local.DeviceSize/local.SectorSize);
} else {
/* store the alternate sector size/command */
local.AltSectorSize = sector_size;
local.AltSectorCmd = bufbyte;
local.NumAltSectors = (uint32_t)(local.DeviceSize/local.AltSectorSize);
}
}
if (version >= 5) {
int page_info_dword_offset = 10;
int page_info_offset = 4 * page_info_dword_offset;
if (read_config_byte(tdata, tlen, datalen, page_info_offset, &bufbyte)) {
return -1;
}
bufbyte >>= 4;
bufbyte &= 0xF;
local.PageSize = 1U << bufbyte;
}
/* Apply local parse data now that parsing has succeeded */
*data = local;
return 0;
}
static int parse_sfdp_basic_header(
NOR_Config_Accessor *accessor,
uint8_t *header_base,
NOR_Config_Data *data
)
{
int datalen = 0;
uint8_t bufbyte;
int hlen = 8;
/* check minor version */
if (read_config_byte(header_base, hlen, datalen, 0x1, &bufbyte)) {
return -1;
}
int version = bufbyte;
/* get table length in 4-byte words */
if (read_config_byte(header_base, hlen, datalen, 0x3, &bufbyte)) {
return -1;
}
int tlen = bufbyte;
tlen *= 4;
/* read table offset */
uint32_t toffset = 0;
if (read_config_int(header_base, hlen, datalen, 0x4, &toffset)) {
return -1;
}
/* mask off the top byte */
toffset &= 0xFFFFFFUL;
uint8_t *table_data = accessor->acquire(accessor->context, toffset, tlen);
if (table_data == NULL) {
return RTEMS_INVALID_SIZE;
}
if (parse_sfdp_jedec_table(table_data, tlen, version, data)) {
accessor->release(accessor->context, table_data);
table_data = NULL;
return -1;
}
accessor->release(accessor->context, table_data);
table_data = NULL;
return 0;
}
rtems_status_code SFDP_parse(
NOR_Config_Accessor *accessor,
NOR_Config_Data *data
)
{
int datalen = 0;
memset(data, 0, sizeof(NOR_Config_Data));
size_t header_len = 8;
uint8_t *header_data = accessor->acquire(accessor->context, 0, header_len);
if (header_data == NULL) {
return RTEMS_INVALID_SIZE;
}
/* verify SFDP ID string ("SFDP") exists */
if (verify_config_string(header_data, header_len, datalen, 0x0, "SFDP")) {
return RTEMS_INVALID_NAME;
}
/* check major version */
uint8_t bufbyte;
if (read_config_byte(header_data, header_len, datalen, 0x05, &bufbyte)) {
return RTEMS_INVALID_ADDRESS;
}
if (bufbyte != 1) {
return RTEMS_TOO_MANY;
}
/* get count of parameter headers */
if (read_config_byte(header_data, header_len, datalen, 0x06, &bufbyte)) {
return RTEMS_INVALID_ADDRESS;
}
int pheaders_count = bufbyte + 1;
accessor->release(accessor->context, header_data);
header_data = NULL;
/* get parameter table descriptors */
size_t ptd_len = 8;
size_t total_ptd_len = ptd_len * pheaders_count;
uint8_t *ptable_desc = accessor->acquire(accessor->context, header_len, total_ptd_len);
if (ptable_desc == NULL) {
return RTEMS_INVALID_SIZE;
}
int pheader_index;
int param_version_seen = -1;
for (pheader_index = 0; pheader_index < pheaders_count; pheader_index++) {
int ptable_offset = pheader_index * ptd_len;
uint8_t *header_base = ptable_desc + ptable_offset;
/* only parse JEDEC SFDP Basic SPI Flash Parameter */
if (read_config_byte(header_base, ptd_len, datalen, 0x0, &bufbyte)) {
accessor->release(accessor->context, ptable_desc);
ptable_desc = NULL;
return -1;
}
if (bufbyte != 0) {
continue;
}
/* check minor version */
if (read_config_byte(header_base, ptd_len, datalen, 0x1, &bufbyte)) {
accessor->release(accessor->context, ptable_desc);
ptable_desc = NULL;
return -1;
}
int version = bufbyte;
if (version > param_version_seen) {
/* only parse if interested in this version */
if (parse_sfdp_basic_header(accessor, header_base, data)) {
/* parse failed, continue */
continue;
}
param_version_seen = version;
}
}
accessor->release(accessor->context, ptable_desc);
ptable_desc = NULL;
if (param_version_seen == -1) {
return RTEMS_INVALID_ADDRESS;
}
return RTEMS_SUCCESSFUL;
}
struct buffer_context {
uint8_t *data;
size_t length;
};
static uint8_t *buffer_acquire(
void *arg,
uint32_t offset,
size_t length
)
{
struct buffer_context *context = arg;
if (offset + length > context->length) return NULL;
return context->data + offset;
}
static void buffer_release(
void *arg,
uint8_t *data
)
{
(void) arg;
(void) data;
/* Do nothing */
}
rtems_status_code SFDP_parse_from_buffer(
uint8_t *sfdp_raw,
size_t sfdp_raw_len,
NOR_Config_Data *data
)
{
struct buffer_context context = {sfdp_raw, sfdp_raw_len};
NOR_Config_Accessor accessor;
accessor.context = &context;
accessor.acquire = buffer_acquire;
accessor.release = buffer_release;
return SFDP_parse(&accessor, data);
}

View File

@@ -55,6 +55,9 @@ install:
- bsps/include/libchip/spi-memdrv.h
- bsps/include/libchip/spi-sd-card.h
- bsps/include/libchip/wd80x3.h
- destination: ${BSP_INCLUDEDIR}/dev/nor
source:
- bsps/include/dev/nor/config-parser.h
- destination: ${BSP_INCLUDEDIR}/dev/flash
source:
- bsps/include/dev/flash/jffs2_flashdev.h
@@ -89,6 +92,7 @@ source:
- bsps/shared/dev/ide/ata.c
- bsps/shared/dev/ide/ata_util.c
- bsps/shared/dev/ide/ide_controller.c
- bsps/shared/dev/nor/config-parser.c
- bsps/shared/dev/rtc/abeoz9.c
- bsps/shared/dev/rtc/ds1375.c
- bsps/shared/dev/rtc/i2c-rtc.c