Commit 592647cf authored by Bjarki Arge Andreasen's avatar Bjarki Arge Andreasen Committed by Fabio Baltieri
Browse files

tests: flash: Add erase_blocks test for flash controllers

Add test suite which tests writing to and erasing all pages,
of any size defined by the page layout.

If the test is built with MCUBOOT, the test will jump into the
application, then run write/erase/boundary tests on the
bootload partition. If MCUBOOT is not selected, the test will
target the slot1 partition instead, performing the same tests.

Signed-off-by: default avatarBjarki Arge Andreasen <bjarki@arge-andreasen.me>
parent b6c1bf82
Loading
Loading
Loading
Loading
+8 −0
Changes for tests/drivers/flash/erase_blocks/CMakeLists.txt: 8 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Copyright (c) 2023 Bjarki Arge Andreasen
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(erase_blocks)

target_sources(app PRIVATE src/main.c)
+5 −0
Changes for tests/drivers/flash/erase_blocks/prj.conf: 5 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Copyright (c) 2023 Bjarki Arge Andreasen
# SPDX-License-Identifier: Apache-2.0

CONFIG_FLASH=y
CONFIG_ZTEST=y
+218 −0
Changes for tests/drivers/flash/erase_blocks/src/main.c: 218 added lines, 0 removed lines.
Original line number Diff line number Diff line
/*
 * Copyright (c) 2023 Bjarki Arge Andreasen
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/kernel.h>
#include <string.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/devicetree.h>
#include <zephyr/ztest.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(test_flash);

#ifdef CONFIG_BOOTLOADER_MCUBOOT

#define TEST_FLASH_PART_NODE \
	DT_NODELABEL(boot_partition)

#else

#define TEST_FLASH_PART_NODE \
	DT_NODELABEL(slot1_partition)

#endif

#define TEST_FLASH_PART_OFFSET \
	DT_REG_ADDR(TEST_FLASH_PART_NODE)

#define TEST_FLASH_PART_SIZE \
	DT_REG_SIZE(TEST_FLASH_PART_NODE)

#define TEST_FLASH_PART_END_OFFSET \
	(TEST_FLASH_PART_OFFSET + TEST_FLASH_PART_SIZE)

#define TEST_FLASH_CONTROLLER_NODE \
	DT_MTD_FROM_FIXED_PARTITION(TEST_FLASH_PART_NODE)

static const struct device *flash_controller = DEVICE_DT_GET(TEST_FLASH_CONTROLLER_NODE);
static uint8_t test_write_block[128];
static uint8_t test_read_block[128];

static void test_flash_fill_test_write_block(void)
{
	for (uint8_t i = 0; i < sizeof(test_write_block); i++) {
		test_write_block[i] = i;
	}
}

static void *test_flash_setup(void)
{
	test_flash_fill_test_write_block();
	return NULL;
}

static bool test_flash_mem_is_set_to(const uint8_t *data, uint8_t to, size_t size)
{
	for (size_t i = 0; i < size; i++) {
		if (data[i] != to) {
			return false;
		}
	}

	return true;
}

static bool test_flash_is_erased(off_t offset, size_t size)
{
	static uint8_t test_erase_buffer[99];
	const struct flash_parameters *parameters = flash_get_parameters(flash_controller);
	size_t remaining;
	size_t readsize;

	while (offset < size) {
		remaining = size - (size_t)offset;
		readsize = MIN(sizeof(test_erase_buffer), remaining);

		if (flash_read(flash_controller, offset, test_erase_buffer, readsize) < 0) {
			return false;
		}

		if (!test_flash_mem_is_set_to(test_erase_buffer, parameters->erase_value,
					      readsize)) {
			return false;
		}

		offset += (off_t)readsize;
	}

	return true;
}

static bool test_flash_verify_partition_is_erased(void)
{
	return test_flash_is_erased(TEST_FLASH_PART_OFFSET, TEST_FLASH_PART_SIZE);
}

static int test_flash_erase_partition(void)
{
	LOG_INF("Erasing section of size %zu at offset %zu controlled by %s",
		TEST_FLASH_PART_SIZE,
		TEST_FLASH_PART_OFFSET,
		flash_controller->name);

	return flash_erase(flash_controller, TEST_FLASH_PART_OFFSET,
			   TEST_FLASH_PART_SIZE);
}

static void test_flash_before(void *f)
{
	ARG_UNUSED(f);
	__ASSERT(test_flash_erase_partition() == 0,
		 "Failed to erase partition");
	__ASSERT(test_flash_verify_partition_is_erased(),
		 "Failed to erase partition");
}

static void test_flash_write_block_at_offset(off_t offset, size_t size)
{
	zassert_ok(flash_write(flash_controller, offset, test_write_block, size),
		   "Failed to write block at offset %zu, of size %zu", (size_t)offset, size);
	zassert_ok(flash_read(flash_controller, offset, test_read_block, size),
		   "Failed to read block at offset %zu, of size %zu", (size_t)offset, size);
	zassert_ok(memcmp(test_write_block, test_read_block, size),
		   "Failed to write block at offset %zu, of size %zu to page", (size_t)offset,
		   size);
}

static void test_flash_write_across_page_boundary(const struct flash_pages_info *info,
						  size_t write_block_size)
{
	off_t page_boundary = info->start_offset;
	uint32_t page0_index = info->index - 1;
	uint32_t page1_index = info->index;
	off_t cross_write_start_offset = page_boundary - (off_t)write_block_size;
	size_t cross_write_size = write_block_size * 2;

	LOG_INF("Writing across page boundary at %zu, between page index %u and %u",
		(size_t)page_boundary,
		page0_index,
		page1_index);

	test_flash_write_block_at_offset(cross_write_start_offset, cross_write_size);
}

static bool test_flash_write_across_page_boundaries(const struct flash_pages_info *info,
						    void *data)
{
	size_t write_block_size = *((size_t *)data);

	if (info->start_offset <= TEST_FLASH_PART_OFFSET) {
		/* Not yet reached second page within partition */
		return true;
	}

	if (info->start_offset >= TEST_FLASH_PART_END_OFFSET) {
		/* Reached first page after partition end */
		return false;
	}

	test_flash_write_across_page_boundary(info, write_block_size);
	return true;
}

ZTEST(flash_page_layout, test_write_across_page_boundaries_in_part)
{
	const struct flash_parameters *parameters = flash_get_parameters(flash_controller);
	size_t write_block_size = parameters->write_block_size;

	flash_page_foreach(flash_controller, test_flash_write_across_page_boundaries,
			   &write_block_size);
}

static void test_flash_erase_page(const struct flash_pages_info *info)
{
	off_t page_offset = info->start_offset;
	size_t page_size = info->size;
	size_t page_index = info->index;

	LOG_INF("Erasing page at %zu of size %zu with index %zu",
		(size_t)page_offset, page_size, page_index);

	zassert_ok(flash_erase(flash_controller, page_offset, page_size),
		   "Failed to erase page");

	zassert_true(test_flash_is_erased(page_offset, page_size),
		     "Failed to erase page");
}

static bool test_flash_erase_pages(const struct flash_pages_info *info, void *data)
{
	if (info->start_offset < TEST_FLASH_PART_OFFSET) {
		/* Not yet reached first page within partition */
		return true;
	}

	if (info->start_offset >= TEST_FLASH_PART_END_OFFSET) {
		/* Reached first page after partition end */
		return false;
	}

	test_flash_erase_page(info);
	return true;
}

ZTEST(flash_page_layout, test_erase_single_pages_in_part)
{
	const struct flash_parameters *parameters = flash_get_parameters(flash_controller);
	size_t write_block_size = parameters->write_block_size;

	flash_page_foreach(flash_controller, test_flash_write_across_page_boundaries,
			   &write_block_size);

	flash_page_foreach(flash_controller, test_flash_erase_pages, NULL);
}

ZTEST_SUITE(flash_page_layout, NULL, test_flash_setup, test_flash_before, NULL, NULL);
+4 −0
Changes for tests/drivers/flash/erase_blocks/sysbuild.conf: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Copyright (c) 2023 Bjarki Arge Andreasen
# SPDX-License-Identifier: Apache-2.0

SB_CONFIG_BOOTLOADER_MCUBOOT=y
+17 −0
Changes for tests/drivers/flash/erase_blocks/testcase.yaml: 17 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Copyright (c) 2023 Bjarki Arge Andreasen
# SPDX-License-Identifier: Apache-2.0

common:
  sysbuild: true
  tags:
    - drivers
    - flash
tests:
  flash.erase_blocks.build_only:
    harness: ztest
    build_only: true
    platform_allow:
      - sam_v71_xult
      - b_u585i_iot02a
      - nrf9160dk_nrf9160
      - nrf5340dk_nrf5340_cpuapp