blob: f3696d8906057201bb448f6319acdb32c66b8283 [file] [log] [blame]
/* Copyright 2018 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include "action_descriptor.h"
#include "flash.h"
#include "flashchips.h"
#define SMALLEST_BLOCK_SZ 4096
#define SECOND_SMALLEST_BLOCK_SZ (SMALLEST_BLOCK_SZ * 8)
#define LARGER_BLOCK_SZ (SECOND_SMALLEST_BLOCK_SZ * 2)
/* Fake erase function pointer to plug into the test chip descriptor. */
static int dummy_erase(struct flashctx *flash,
unsigned int addr,
unsigned int blocklen)
{
return 0;
}
/*
* Let's pretend we are dealing with a slightly modified Windbond W25Q64 chip,
* copy its description from flashchips.c dropping pointers to functions and
* adding a second eraseblocks option for the lowest size range.
*/
static struct flashchip test_chip = {
.vendor = "Winbond",
.name = "W25Q64",
.bustype = BUS_SPI,
.manufacture_id = WINBOND_NEX_ID,
.model_id = WINBOND_NEX_W25Q64_V,
.total_size = 8192,
.page_size = 256,
.feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP,
.tested = TEST_OK_PREW,
.block_erasers =
{
{
.eraseblocks = { {SMALLEST_BLOCK_SZ, 128},
{SMALLEST_BLOCK_SZ, 2048} },
.block_erase = dummy_erase,
}, {
.eraseblocks = { {SECOND_SMALLEST_BLOCK_SZ, 256} },
.block_erase = dummy_erase,
}, {
.eraseblocks = { {LARGER_BLOCK_SZ, 128} },
.block_erase = dummy_erase,
}, {
.eraseblocks = { {8 * 1024 * 1024, 1} },
.block_erase = dummy_erase,
}, {
.eraseblocks = { {8 * 1024 * 1024, 1} },
.block_erase = dummy_erase,
},
}
};
/*
* Check processing units array contents for various fixed size tests.
*
* @ad pointer to a generated action descriptor structure
* @expected_pu pointer to the expected values in processing_units array
* @num_blocks number of blocks expected to be added to the array
*
* returns zero on success, on error - 1 based block number where the error
* was detected.
*/
static int check_pus(struct action_descriptor *ad,
const struct processing_unit *expected_pu,
size_t num_blocks)
{
size_t i;
struct processing_unit pu = *expected_pu;
for (i = 0; i < num_blocks; i++) {
if (memcmp(ad->processing_units + i, &pu, sizeof(pu)))
return i + 1;
/*
* In case more than one processing units are present, they
* are expected to be 8 blocks apart.
*/
pu.offset = pu.offset + pu.block_size * 8;
}
if (ad->processing_units[i].num_blocks)
return i + 1;
return 0;
}
/*
* Test one erase operation.
*
* The caller prepares old and new buffers and the created action descriptor
* is supposed to include a processing unit with a certain number of blocks
* and of certain contents.
*
* @fctx flash context
*
* @oldi pointer to buffer of the flash chip size bytes containing old
* data
*
* @newi pointer to buffer of the flash chip size bytes containing new
* data
* @expected_pu pointer to the expected contents of the processing_units array
* @num_blocks number of blocks in the array
* @test_num test number (for reporting purposes)
*/
static int test_one_erase(struct flashctx *fctx,
uint8_t *oldi,
uint8_t *newi,
const struct processing_unit *expected_pu,
size_t num_blocks,
int test_num)
{
int result = 0;
struct action_descriptor *ad;
msg_pinfo("%s test %d...", __func__, test_num);
ad = prepare_action_descriptor(fctx, oldi, newi);
if (ad) {
int rv = check_pus(ad, expected_pu, num_blocks);
if (rv) {
result = 1;
msg_pinfo("failed\n");
fprintf(stderr, "\%s: test %d failed on processing unit %d!\n",
__func__, test_num, rv - 1);
} else {
msg_pinfo("passed\n");
}
free(ad);
} else {
msg_pinfo("failed\n");
fprintf(stderr, "%s:%d action description creation failed!\n",
__func__, __LINE__);
result = 1;
}
return result;
}
/*
* Swap two eraser structures in the flash descriptor to verify that the
* erasers are sorted properly.
*/
static void swap_erasers(struct flashctx *fctx, unsigned a, unsigned b)
{
struct block_eraser swap_eraser;
/* Modify descriptor to verify erases sort by size works. */
swap_eraser = fctx->chip->block_erasers[a];
fctx->chip->block_erasers[a] = fctx->chip->block_erasers[b];
fctx->chip->block_erasers[b] = swap_eraser;
}
/* Verify that largest erase size is selected when appropriate. */
static int test_largest_erase(uint8_t *oldi, uint8_t *newi,
struct flashctx *fctx, size_t chip_size)
{
int result = 0;
unsigned i;
size_t block_count;
memset(oldi, ~flash_erase_value(fctx), chip_size);
memset(newi, flash_erase_value(fctx), chip_size);
{
struct processing_unit expected_pu = { chip_size, 0, 1, 4, 0 };
result += test_one_erase(fctx, oldi, newi, &expected_pu, 1, 1);
}
swap_erasers(fctx, 0, 4);
{
struct processing_unit expected_pu = { chip_size, 0, 1, 3, 0 };
result += test_one_erase(fctx, oldi, newi, &expected_pu, 1, 2);
}
/* Restore erasers order. */
swap_erasers(fctx, 0, 4);
/*
* Modify old contents such that enough smaller blocks require
* erasing.
*/
/* Let's corrupt 6 out every 8 blocks. */
memset(oldi, flash_erase_value(fctx), chip_size);
block_count = chip_size/SMALLEST_BLOCK_SZ;
for (i = 0; i < block_count; i++)
if ((i % 8) < 6)
oldi[i * SMALLEST_BLOCK_SZ] =
~flash_erase_value(fctx);
{
struct processing_unit expected_pu = { chip_size, 0, 1, 4, 0 };
result += test_one_erase(fctx, oldi, newi, &expected_pu, 1, 3);
}
/* Now, let's corrupt less than 6 out of every 8 blocks. */
memset(oldi, flash_erase_value(fctx), chip_size);
block_count = chip_size/SMALLEST_BLOCK_SZ;
for (i = 0; i < block_count; i++)
if ((i % 8) < 5)
oldi[i * SMALLEST_BLOCK_SZ] =
~flash_erase_value(fctx);
{
struct processing_unit expected_pu = { SMALLEST_BLOCK_SZ,
0, 5, 0, 1 };
result += test_one_erase(fctx, oldi, newi,
&expected_pu, 256, 4);
}
return result;
}
/* Verify that smallest erase size is selected when appropriate. */
static int test_smallest_erase(uint8_t *oldi, uint8_t *newi,
struct flashctx *fctx, size_t chip_size)
{
int result = 0;
memset(oldi, flash_erase_value(fctx), chip_size);
memset(newi, flash_erase_value(fctx), chip_size);
oldi[0] = ~oldi[0];
{
struct processing_unit expected_pu = {
SMALLEST_BLOCK_SZ, 0, 1, 0, 0
};
result += test_one_erase(fctx, oldi, newi, &expected_pu, 1, 5);
}
oldi[0] = ~oldi[0];
oldi[chip_size/2] = ~oldi[chip_size/2];
{
struct processing_unit expected_pu = {
SMALLEST_BLOCK_SZ, chip_size/2, 1, 0, 1
};
result += test_one_erase(fctx, oldi, newi, &expected_pu, 1, 6);
}
return result;
}
/* Verify a case when variable sizes need to be erased. */
static int test_assorted_erase(uint8_t *oldi, uint8_t *newi,
struct flashctx *fctx, size_t chip_size)
{
int i;
int j;
int k;
struct action_descriptor *ad;
int rv;
const struct processing_unit pus[] = {
{
.block_size = 0x1000,
.offset = 0x0,
.num_blocks = 0x5,
.block_eraser_index = 0x0,
.block_region_index = 0x1
}, {
.block_size = 0x1000,
.offset = 0x103000,
.num_blocks = 0x5,
.block_eraser_index = 0x0,
.block_region_index = 0x1
}, {
.block_size = 0x1000,
.offset = 0x10b000,
.num_blocks = 0x5,
.block_eraser_index = 0x0,
.block_region_index = 0x1
}, {
.block_size = 0x1000,
.offset = 0x608000,
.num_blocks = 0x4,
.block_eraser_index = 0x0,
.block_region_index = 0x1
}, {
.block_size = 0x1000,
.offset = 0x618000,
.num_blocks = 0x4,
.block_eraser_index = 0x0,
.block_region_index = 0x1
}, {
.block_size = 0x8000,
.offset = 0x600000,
.num_blocks = 0x1,
.block_eraser_index = 0x1,
.block_region_index = 0x0
}, {
.block_size = 0x8000,
.offset = 0x610000,
.num_blocks = 0x1,
.block_eraser_index = 0x1,
.block_region_index = 0x0
}, {
.block_size = 0x10000,
.offset = 0x210000,
.num_blocks = 0x1,
.block_eraser_index = 0x2,
.block_region_index = 0x0
}, {
.block_size = 0x1000,
.offset = 0x40000,
.num_blocks = 0x0,
.block_eraser_index = 0x0,
.block_region_index = 0x1
}
};
msg_pinfo("Test assorted sizes...");
memset(oldi, flash_erase_value(fctx), chip_size);
memset(newi, flash_erase_value(fctx), chip_size);
/* Five smallest blocks at the bottom. */
for (i = 0; i < 5; i++)
oldi[i * SMALLEST_BLOCK_SZ + i] = ~oldi[1];
/* Couple of second smallest block sizes at 1MB+ */
i = 0x100000;
for (j = 0; j < 2; j++) {
/*
* Setting up less than 6 smallest blocks, should be no
* folding into larger block.
*/
for (k = 3;
k < SECOND_SMALLEST_BLOCK_SZ / SMALLEST_BLOCK_SZ;
k++) {
int index = i +
j * SECOND_SMALLEST_BLOCK_SZ +
k * SMALLEST_BLOCK_SZ + j + k;
oldi[index] = ~oldi[1];
}
}
i = 0x200000;
for (j = 2; j < 4; j++) {
/*
* Setting up 6 smallest blocks, should fold into larger block.
*/
for (k = 2;
k < SECOND_SMALLEST_BLOCK_SZ / SMALLEST_BLOCK_SZ;
k++) {
int index = i +
j * SECOND_SMALLEST_BLOCK_SZ +
k * SMALLEST_BLOCK_SZ + j + k;
oldi[index] = ~oldi[1];
}
}
/* Couple of sparsely changed larger block sizes at 6MB */
i = 0x600000;
for (j = 0; j < 2; j++) {
for (k = 2; k < 4; k++) {
int index = i +
j * LARGER_BLOCK_SZ +
k * SMALLEST_BLOCK_SZ + j + k;
memset(oldi + index, ~oldi[1], SMALLEST_BLOCK_SZ * 8);
}
}
ad = prepare_action_descriptor(fctx, oldi, newi);
rv = memcmp(ad->processing_units, pus, sizeof(pus));
free(ad);
if (rv)
msg_pinfo("failed\n");
else
msg_pinfo("passed\n");
return rv;
}
static int run_tests(uint8_t *oldi, uint8_t *newi,
struct flashctx *fctx, size_t chip_size)
{
int result = 0;
result += test_largest_erase(oldi, newi, fctx, chip_size);
result += test_smallest_erase(oldi, newi, fctx, chip_size);
result += test_assorted_erase(oldi, newi, fctx, chip_size);
return result;
}
int test_action_descriptor(void)
{
struct flashctx fctx;
size_t chip_size;
int rv = 1;
void *oldimage, *newimage;
memset(&fctx, 0, sizeof(fctx));
fctx.chip = &test_chip;
/* Cache it here to avoid extra calculations. */
chip_size = test_chip.total_size * 1024;
oldimage = malloc(chip_size);
newimage = malloc(chip_size);
if (oldimage && newimage)
rv = run_tests(oldimage, newimage, &fctx, chip_size);
else
fprintf(stderr, "ERROR: malloc failed for %zd bytes\n",
2 * chip_size);
free(oldimage);
free(newimage);
return rv;
}