blob: 18111e1565f7976523a2001acf0044a7850703df [file] [log] [blame]
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/ztest.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/bluetooth/mesh.h>
#include "mesh/blob.h"
#define SLOT1_PARTITION slot1_partition
#define SLOT1_PARTITION_ID FIXED_PARTITION_ID(SLOT1_PARTITION)
#define SLOT1_PARTITION_SIZE FIXED_PARTITION_SIZE(SLOT1_PARTITION)
/* Chunk size is set to value that is not multiple of 4, to verify that chunks are written correctly
* even if they are not aligned with word length used in flash
*/
#define CHUNK_SIZE 65
static struct bt_mesh_blob_io_flash blob_flash_stream;
static size_t chunk_size(const struct bt_mesh_blob_block *block,
uint16_t chunk_idx)
{
if ((chunk_idx == block->chunk_count - 1) &&
(block->size % CHUNK_SIZE)) {
return block->size % CHUNK_SIZE;
}
return CHUNK_SIZE;
}
static uint8_t block_size_to_log(size_t size)
{
uint8_t block_size_log = 0;
while (size > 1) {
size = size / 2;
block_size_log++;
}
return block_size_log;
}
ZTEST_SUITE(blob_io_flash, NULL, NULL, NULL, NULL, NULL);
ZTEST(blob_io_flash, test_chunk_read)
{
const struct flash_area *fa = NULL;
struct bt_mesh_blob_xfer xfer;
struct bt_mesh_blob_block block = { 0 };
struct bt_mesh_blob_chunk chunk = { 0 };
size_t remaining = SLOT1_PARTITION_SIZE;
off_t block_idx = 0;
uint16_t chunk_idx = 0;
uint8_t chunk_data[CHUNK_SIZE];
uint8_t test_data[SLOT1_PARTITION_SIZE];
uint8_t ctrl_data[SLOT1_PARTITION_SIZE];
size_t tests_data_offset = 0;
int i, err;
/* Fill test data with pattern */
for (i = 0; i < SLOT1_PARTITION_SIZE; i++) {
test_data[i] = i % 0xFF;
}
err = flash_area_open(SLOT1_PARTITION_ID, &fa);
zassert_equal(err, 0, "Preparing test data failed with err=%d", err);
err = flash_area_erase(fa, 0, ARRAY_SIZE(ctrl_data));
zassert_equal(err, 0, "Preparing test data failed with err=%d", err);
err = flash_area_write(fa, 0, test_data, ARRAY_SIZE(ctrl_data));
zassert_equal(err, 0, "Preparing test data failed with err=%d", err);
err = flash_area_read(fa, 0, ctrl_data, ARRAY_SIZE(ctrl_data));
zassert_equal(err, 0, "Preparing test data failed with err=%d", err);
zassert_mem_equal(ctrl_data, test_data, ARRAY_SIZE(ctrl_data),
"Incorrect data written into flash");
memset(ctrl_data, 0, SLOT1_PARTITION_SIZE);
flash_area_close(fa);
err = bt_mesh_blob_io_flash_init(&blob_flash_stream,
SLOT1_PARTITION_ID, 0);
zassert_equal(err, 0, "BLOB I/O init failed with err=%d", err);
err = blob_flash_stream.io.open(&blob_flash_stream.io, &xfer, BT_MESH_BLOB_READ);
zassert_equal(err, 0, "BLOB I/O open failed with err=%d", err);
chunk.data = chunk_data;
/* Simulate reading whole partition divided into blocks and chunk of maximum sizes */
while (remaining > 0) {
block.chunk_count =
DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX,
CHUNK_SIZE);
block.size = remaining > CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX
? CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX
: remaining;
/* BLOB Client should do nothing to flash area as it's in read mode */
err = blob_flash_stream.io.block_start(&blob_flash_stream.io, &xfer, &block);
zassert_equal(err, 0, "BLOB I/O open failed with err=%d", err);
/* `block_start` in write mode will erase flash pages that can fit block.
* Assert that at least block size of data was not erased in read mode
*/
flash_area_read(blob_flash_stream.area, block.offset, ctrl_data, block.size);
zassert_mem_equal(ctrl_data, &test_data[block.offset], block.size,
"Flash data was altered by `block_start` in read mode");
memset(ctrl_data, 0, SLOT1_PARTITION_SIZE);
block.offset = block_idx * (1 << block_size_to_log(block.size));
for (i = 0; i < block.chunk_count; i++) {
chunk.size = chunk_size(&block, chunk_idx);
chunk.offset = CHUNK_SIZE * chunk_idx;
err = blob_flash_stream.io.rd(&blob_flash_stream.io, &xfer, &block, &chunk);
zassert_equal(err, 0, "BLOB I/O read failed with err=%d off=%d len=%d",
err, block.offset + chunk.offset, chunk.size);
zassert_mem_equal(&chunk_data, &test_data[tests_data_offset], chunk.size,
"Incorrect data written into flash");
chunk_idx++;
remaining -= chunk.size;
tests_data_offset += chunk.size;
}
block_idx++;
chunk_idx = 0;
}
/* We read whole sector as BLOB. Try to increment every offset by one and read,
* which should attempt to read outside flash area
*/
chunk.offset++;
err = blob_flash_stream.io.rd(&blob_flash_stream.io, &xfer, &block, &chunk);
zassert_false(err == 0, "Read outside flash area successful");
chunk.offset--;
block.offset++;
err = blob_flash_stream.io.rd(&blob_flash_stream.io, &xfer, &block, &chunk);
zassert_false(err == 0, "Read outside flash area successful");
block.offset--;
blob_flash_stream.offset++;
err = blob_flash_stream.io.rd(&blob_flash_stream.io, &xfer, &block, &chunk);
zassert_false(err == 0, "Read outside flash area successful");
blob_flash_stream.io.close(&blob_flash_stream.io, &xfer);
}
ZTEST(blob_io_flash, test_chunk_write)
{
struct bt_mesh_blob_xfer xfer;
struct bt_mesh_blob_block block = { 0 };
struct bt_mesh_blob_chunk chunk = { 0 };
size_t remaining = SLOT1_PARTITION_SIZE;
off_t block_idx = 0;
uint16_t chunk_idx = 0;
uint8_t chunk_data[CHUNK_SIZE];
/* 3 is maximum length of padding at the end of written chunk */
uint8_t chunk_ctrl_data[CHUNK_SIZE + 3];
uint8_t end_padding_len;
uint8_t test_data[SLOT1_PARTITION_SIZE];
uint8_t erased_block_data[CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX];
uint8_t ctrl_data[SLOT1_PARTITION_SIZE];
size_t tests_data_offset = 0;
int i, j, err;
/* Fill test data with pattern */
for (i = 0; i < SLOT1_PARTITION_SIZE; i++) {
test_data[i] = i % 0xFF;
}
err = bt_mesh_blob_io_flash_init(&blob_flash_stream,
SLOT1_PARTITION_ID, 0);
zassert_equal(err, 0, "BLOB I/O init failed with err=%d", err);
err = blob_flash_stream.io.open(&blob_flash_stream.io, &xfer, BT_MESH_BLOB_WRITE);
zassert_equal(err, 0, "BLOB I/O open failed with err=%d", err);
chunk.data = chunk_data;
memset(erased_block_data, flash_area_erased_val(blob_flash_stream.area),
CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX);
/* Simulate writing whole partition divided into blocks and chunk of maximum sizes */
while (remaining > 0) {
block.chunk_count =
DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX,
CHUNK_SIZE);
block.size = remaining > CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX
? CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX
: remaining;
block.offset = block_idx * (1 << block_size_to_log(block.size));
err = blob_flash_stream.io.block_start(&blob_flash_stream.io, &xfer, &block);
zassert_equal(err, 0, "BLOB I/O open failed with err=%d", err);
flash_area_read(blob_flash_stream.area, block.offset,
ctrl_data, block.size);
zassert_mem_equal(ctrl_data, erased_block_data, block.size,
"Flash data was not erased by `block_start` in write mode");
memset(ctrl_data, 0, SLOT1_PARTITION_SIZE);
for (i = 0; i < block.chunk_count; i++) {
chunk.size = chunk_size(&block, chunk_idx);
chunk.offset = CHUNK_SIZE * chunk_idx;
memcpy(chunk.data,
&test_data[chunk.offset + block.offset],
chunk.size);
err = blob_flash_stream.io.wr(&blob_flash_stream.io, &xfer, &block, &chunk);
zassert_equal(err, 0, "BLOB I/O write failed with err=%d", err);
/* To calculate end padding length we must calculate size of whole buffer
* and subtract start offset length and chunk size
*/
end_padding_len =
ROUND_UP((block.offset + chunk.offset) %
flash_area_align(blob_flash_stream.area) +
chunk.size, flash_area_align(blob_flash_stream.area)) -
(block.offset + chunk.offset) %
flash_area_align(blob_flash_stream.area) - chunk.size;
flash_area_read(blob_flash_stream.area, block.offset + chunk.offset,
chunk_ctrl_data, chunk.size + end_padding_len);
zassert_mem_equal(chunk_ctrl_data, chunk_data, chunk.size,
"Incorrect data written into flash");
/* Assert that nothing was written into end padding */
for (j = 1; j <= end_padding_len; j++) {
zassert_equal(chunk_ctrl_data[chunk.size + j],
flash_area_erased_val(blob_flash_stream.area));
}
chunk_idx++;
remaining -= chunk.size;
tests_data_offset += chunk.size;
}
block_idx++;
chunk_idx = 0;
}
flash_area_read(blob_flash_stream.area, 0, ctrl_data, SLOT1_PARTITION_SIZE);
zassert_mem_equal(ctrl_data, test_data, SLOT1_PARTITION_SIZE,
"Incorrect chunks written into flash");
/* We wrote whole sector as BLOB. Try to increment every offset by one and write,
* which should attempt to write outside flash area
*/
chunk.offset++;
err = blob_flash_stream.io.wr(&blob_flash_stream.io, &xfer, &block, &chunk);
zassert_false(err == 0, "Write outside flash area successful");
chunk.offset--;
block.offset++;
err = blob_flash_stream.io.wr(&blob_flash_stream.io, &xfer, &block, &chunk);
zassert_false(err == 0, "Write outside flash area successful");
block.offset--;
blob_flash_stream.offset++;
err = blob_flash_stream.io.wr(&blob_flash_stream.io, &xfer, &block, &chunk);
zassert_false(err == 0, "Write outside flash area successful");
blob_flash_stream.io.close(&blob_flash_stream.io, &xfer);
}