Example #1
0
static BOOL32 check_format_mark(void)
{
	// This function reads a flash page from (bank #0, block #0) in order to check whether the SSD is formatted or not.

#ifdef __GNUC__
	extern UINT32 size_of_firmware_image;
	UINT32 firmware_image_pages = (((UINT32) (&size_of_firmware_image)) + BYTES_PER_FW_PAGE - 1) / BYTES_PER_FW_PAGE;
#else
	extern UINT32 Image$$ER_CODE$$RO$$Length;
	extern UINT32 Image$$ER_RW$$RW$$Length;
	UINT32 firmware_image_bytes = ((UINT32) &Image$$ER_CODE$$RO$$Length) + ((UINT32) &Image$$ER_RW$$RW$$Length);
	UINT32 firmware_image_pages = (firmware_image_bytes + BYTES_PER_FW_PAGE - 1) / BYTES_PER_FW_PAGE;
#endif

	UINT32 format_mark_page_offset = FW_PAGE_OFFSET + firmware_image_pages;
	UINT32 temp;

	flash_clear_irq();	// clear any flash interrupt flags that might have been set

	SETREG(FCP_CMD, FC_COL_ROW_READ_OUT);
	SETREG(FCP_BANK, REAL_BANK(0));
	SETREG(FCP_OPTION, FO_E);
	SETREG(FCP_DMA_ADDR, FTL_BUF_ADDR); 	// flash -> DRAM
	SETREG(FCP_DMA_CNT, BYTES_PER_SECTOR);
	SETREG(FCP_COL, 0);
	SETREG(FCP_ROW_L(0), format_mark_page_offset);
	SETREG(FCP_ROW_H(0), format_mark_page_offset);

	// At this point, we do not have to check Waiting Room status before issuing a command,
	// because scan list loading has been completed just before this function is called.
	SETREG(FCP_ISSUE, NULL);

	// wait for the FC_COL_ROW_READ_OUT command to be accepted by bank #0
	while ((GETREG(WR_STAT) & 0x00000001) != 0);

	// wait until bank #0 finishes the read operation
	while (BSP_FSM(0) != BANK_IDLE);

	// Now that the read operation is complete, we can check interrupt flags.
	temp = BSP_INTR(0) & FIRQ_ALL_FF;

	// clear interrupt flags
	CLR_BSP_INTR(0, 0xFF);

	if (temp != 0)
	{
		return FALSE;	// the page contains all-0xFF (the format mark does not exist.)
	}
	else
	{
		return TRUE;	// the page contains something other than 0xFF (it must be the format mark)
	}
}
Example #2
0
void ftl_isr(void)
{
	// interrupt service routine for flash interrupts

	UINT32 bank;
	UINT32 bsp_intr_flag;

	for (bank = 0; bank < NUM_BANKS; bank++)
	{
		while (BSP_FSM(bank) != BANK_IDLE);

		bsp_intr_flag = BSP_INTR(bank);

		if (bsp_intr_flag == 0)
		{
			continue;
		}

		UINT32 fc = GETREG(BSP_CMD(bank));

		CLR_BSP_INTR(bank, bsp_intr_flag);

		if (bsp_intr_flag & FIRQ_DATA_CORRUPT)
		{
			g_read_fail_count++;
		}

		if (bsp_intr_flag & (FIRQ_BADBLK_H | FIRQ_BADBLK_L))
		{
			if (fc == FC_COL_ROW_IN_PROG || fc == FC_IN_PROG || fc == FC_PROG)
			{
				g_program_fail_count++;
			}
			else
			{
				ASSERT(fc == FC_ERASE);
				g_erase_fail_count++;
			}
		}
	}

	// clear the flash interrupt flag at the interrupt controller
	SETREG(APB_INT_STS, INTR_FLASH);
}
Example #3
0
// BSP interrupt service routine
void ftl_isr(void)
{
    UINT32 bank;
    UINT32 bsp_intr_flag;

    uart_print("BSP interrupt occured...");
    // interrupt pending clear (ICU)
    SETREG(APB_INT_STS, INTR_FLASH);

    for (bank = 0; bank < NUM_BANKS; bank++) {
        while (BSP_FSM(bank) != BANK_IDLE);
        // get interrupt flag from BSP
        bsp_intr_flag = BSP_INTR(bank);

        if (bsp_intr_flag == 0) {
            continue;
        }
        UINT32 fc = GETREG(BSP_CMD(bank));
        // BSP clear
        CLR_BSP_INTR(bank, bsp_intr_flag);

        // interrupt handling
		if (bsp_intr_flag & FIRQ_DATA_CORRUPT) {
            uart_printf("BSP interrupt at bank: 0x%x", bank);
            uart_print("FIRQ_DATA_CORRUPT occured...");
		}
		if (bsp_intr_flag & (FIRQ_BADBLK_H | FIRQ_BADBLK_L)) {
            uart_printf("BSP interrupt at bank: 0x%x", bank);
			if (fc == FC_COL_ROW_IN_PROG || fc == FC_IN_PROG || fc == FC_PROG) {
                uart_print("find runtime bad block when block program...");
			}
			else {
                uart_printf("find runtime bad block when block erase...vblock #: %d", GETREG(BSP_ROW_H(bank)) / PAGES_PER_BLK);
				ASSERT(fc == FC_ERASE);
			}
		}
    }
}
Example #4
0
void ftl_open(void)
{
	sanity_check();

	// STEP 1 - read scan lists from NAND flash

	scan_list_t* scan_list = (scan_list_t*) SCAN_LIST_ADDR;
	UINT32 bank;
	UINT32 bad_block, i , j ;
	// Since we are going to check the flash interrupt flags within this function, ftl_isr() should not be called.
	disable_irq();

	flash_clear_irq();	// clear any flash interrupt flags that might have been set
	
	for (bank = 0; bank < NUM_BANKS; bank++)
	{
		//g_misc_meta[bank].g_merge_buff_sect = 0;
		SETREG(FCP_CMD, FC_COL_ROW_READ_OUT);			// FC_COL_ROW_READ_OUT = sensing and data output
		SETREG(FCP_OPTION, FO_E);						// scan list was written in 1-plane mode by install.exe, so there is no FO_P
		SETREG(FCP_DMA_ADDR, scan_list + bank);			// target address should be DRAM or SRAM (see flash.h for rules)
		SETREG(FCP_DMA_CNT, SCAN_LIST_SIZE);			// number of bytes for data output
		SETREG(FCP_COL, 0);
		SETREG(FCP_ROW_L(bank), SCAN_LIST_PAGE_OFFSET);	// scan list was written to this position by install.exe
		SETREG(FCP_ROW_H(bank), SCAN_LIST_PAGE_OFFSET);	// Tutorial FTL always uses the same row addresses for high chip and low chip

		flash_issue_cmd(bank, RETURN_ON_ISSUE);			// Take a look at the source code of flash_issue_cmd() now.
	}

	// This while() statement waits the last issued command to be accepted.
	// If bit #0 of WR_STAT is one, a flash command is in the Waiting Room, because the target bank has not accepted it yet.
	while ((GETREG(WR_STAT) & 0x00000001) != 0);

	// Now, FC_COL_ROW_READ_OUT commands are accepted by all the banks.
	// Before checking whether scan lists are corrupted or not, we have to wait the completion of read operations.
	// This code shows how to wait for ALL the banks to become idle.
	while (GETREG(MON_CHABANKIDLE) != 0);

	// Now we can check the flash interrupt flags.

	for (bank = 0; bank < NUM_BANKS; bank++)
	{
		UINT32 num_entries = NULL;
		UINT32 result = OK;

		if (BSP_INTR(bank) & FIRQ_DATA_CORRUPT)
		{
			// Too many bits are corrupted so that they cannot be corrected by ECC.
			result = FAIL;
		}
		else
		{
			// Even though the scan list is not corrupt, we have to check whether its contents make sense.

			UINT32 i;

			num_entries = read_dram_16(&(scan_list[bank].num_entries));

			if (num_entries > SCAN_LIST_ITEMS)
			{
				result = FAIL;	// We cannot trust this scan list. Perhaps a software bug.
			}
			else
			{
				for (i = 0; i < num_entries; i++)
				{
					UINT16 entry = read_dram_16(&(scan_list[bank].list[i]));
					UINT16 pblk_offset = entry & 0x7FFF;

					if (pblk_offset == 0 || pblk_offset >= PBLKS_PER_BANK)
					{
						#if OPTION_REDUCED_CAPACITY == FALSE
						result = FAIL;	// We cannot trust this scan list. Perhaps a software bug.
						#endif
					}
					else
					{
						// Bit position 15 of scan list entry is high-chip/low-chip flag.
						// Remove the flag in order to make is_bad_block() simple.

						write_dram_16(&(scan_list[bank].list[i]), pblk_offset);
					}
				}
			}
		}

		if (result == FAIL)
		{
			mem_set_dram(scan_list + bank, 0, SCAN_LIST_SIZE);
			g_misc_meta[bank].g_scan_list_entries = 0;
		}
		else
		{
			write_dram_16(&(scan_list[bank].num_entries), 0);
			g_misc_meta[bank].g_scan_list_entries = num_entries;
		}
	}

	// STEP 2 - If necessary, do low-level format
	// format() should be called after loading scan lists, because format() calls is_bad_block().
	init_meta_data();

	// save non bad block list for metadata block
	// block#0 : list, block#1 : misc meta
	// block#2 ~ map table meta and data
	for(i = 0 ;i < NUM_BANKS;i++){
		bad_block = 2;
		for(j = 0 ;j < NUM_BANKS_MAX;j++){
			while(is_bad_block(i, bad_block) && j < VBLKS_PER_BANK)
			{
				bad_block++;
			}
			g_bad_list[i][j] = bad_block++;
		}
		g_free_start[i] = g_bad_list[i][NUM_BANKS_MAX-1] + 1;
	}
	//if (check_format_mark() == FALSE)
	if( TRUE)
	{
		// When ftl_open() is called for the first time (i.e. the SSD is powered up the first time)
		// format() is called.

		format();
	}
	else{
		loading_misc_meta();
	}


	//*Red//
	// STEP 3 - initialize sector mapping table pieces
	// The page mapping table is too large to fit in SRAM and DRAM.
	// gyuhwa
//	init_metadata();
	// STEP 4 - initialize global variables that belong to FTL

	g_ftl_read_buf_id = 0;
	g_ftl_write_buf_id = 0;

	for (bank = 0; bank < NUM_BANKS; bank++)
	{
		g_misc_meta[bank].g_target_row = PAGES_PER_VBLK * (g_free_start[bank]);
	}

	flash_clear_irq();

	// This example FTL can handle runtime bad block interrupts and read fail (uncorrectable bit errors) interrupts

	SETREG(INTR_MASK, FIRQ_DATA_CORRUPT | FIRQ_BADBLK_L | FIRQ_BADBLK_H);
	SETREG(FCONF_PAUSE, FIRQ_DATA_CORRUPT | FIRQ_BADBLK_L | FIRQ_BADBLK_H);

	enable_irq();
}
Example #5
0
void loading_misc_meta()
{
	/*int i;
	flash_finish();

	disable_irq();
	flash_clear_irq();

	for(i = 0 ;i < NUM_BANKS;i++){
		SETREG(FCP_CMD, FC_COL_ROW_READ_OUT);	
		SETREG(FCP_DMA_CNT, sizeof(misc_metadata));
		SETREG(FCP_COL, 0);
		SETREG(FCP_DMA_ADDR, FTL_BUF_ADDR);
		//SETREG(FCP_DMA_ADDR, &(g_misc_meta[i]));
		SETREG(FCP_OPTION, FO_P | FO_E );		
		SETREG(FCP_ROW_L(i), PAGES_PER_VBLK);
		SETREG(FCP_ROW_H(i), PAGES_PER_VBLK);
		flash_issue_cmd(i, RETURN_ON_ISSUE);
		flash_finish();
		CLR_BSP_INTR(i,0xff);
		mem_copy(&(g_misc_meta[i]),FTL_BUF_ADDR,sizeof(misc_metadata));
	}

	enable_irq();*/
	UINT32 load_flag = 0;
	UINT32 bank, page_num;
	UINT32 load_cnt = 0;

	flash_finish();

	disable_irq();
	flash_clear_irq();	// clear any flash interrupt flags that might have been set

	// scan valid metadata in descending order from last page offset
	for (page_num = PAGES_PER_VBLK - 1; page_num != ((UINT32) -1); page_num--)
	{
		for (bank = 0; bank < NUM_BANKS; bank++)
		{
			if (load_flag & (0x1 << bank))
			{
				continue;
			}
			// read valid metadata from misc. metadata area
			nand_page_ptread(bank,
					1,
					page_num,
					0,
					((sizeof(misc_metadata) + BYTES_PER_SECTOR -1 ) / BYTES_PER_SECTOR),	
					FTL_BUF_ADDR,
					RETURN_ON_ISSUE);
			flash_finish();
			mem_copy(&g_misc_meta[bank], FTL_BUF_ADDR, sizeof(misc_metadata));
		}

		for (bank = 0; bank < NUM_BANKS; bank++)
		{
			if (!(load_flag & (0x1 << bank)) && !(BSP_INTR(bank) & FIRQ_ALL_FF))
			{
				load_flag = load_flag | (0x1 << bank);
				load_cnt++;
			}
			CLR_BSP_INTR(bank, 0xFF);
		}
	}
	ASSERT(load_cnt == NUM_BANKS);

	enable_irq();
}
Example #6
0
void init_jasmine(void)
{
	UINT32 i, bank;
	extern UINT32 Image$$ER_ZI$$ZI$$Base;
	extern UINT32 Image$$ER_ZI$$ZI$$Length;

	// PLL initialization

	SETREG(CLKSelCon, USE_BYPASS_CLK);

	SETREG(PllCon, PLL_PD); 					// power down
	delay(600);									// at least 500ns
	SETREG(PllCon, PLL_CLK_CONFIG | PLL_PD);	// change settings
	delay(600);									// at least 1us
	SETREG(PllCon, PLL_CLK_CONFIG);				// power up
	while ((GETREG(PllCon) & PLL_LD) == 0); 	// wait lock

	SETREG(CLKSelCon, USE_PLL_CLK);

	// reset hardware modules

	SETREG(PMU_ResetCon, RESET_SDRAM | RESET_BM | RESET_SATA | RESET_FLASH);

	// GPIO bits
	// There are 7 GPIO bits from 0 to 6.
	// 0: This bit is connected to J2 (Factory Mode jumper). The ROM firmware configures it as input mode.
	// While main firmware is running, it can be freely used for arbitrary purpose. Beware that a "1" output while
	// the Factory Mode jumper is set to Normal position (tied to ground) can lead to circuit damage.
	// A "0" output while the jumper is tied to Vcc will also lead to circuit damage.
	// 1: This bit is connected to J3 (Boot ROM). The controller hardware checks its status upon reset.
	// After the reset is done, you can remove the jumper and use the pin as output.
	// 2 through 5: The IO pins for these bits are shared between MAX3232C (UART chip) and J4.
	// In order to use J4, you have to turn on the switches of SW4 and turn off the switches 1 through 4 in SW2.
	// In order to use UART, you have to turn off the switches of SW4 and turn on the switches 1 through 4 in SW2.
	// 6: This bit is connected to D4 (LED) via SW2.

	#if OPTION_UART_DEBUG
	SETREG(GPIO_MOD, 0);
	SETREG(GPIO_DIR, BIT3 | BIT4 | BIT6);	// output pins: 3(UART_TXD), 4(UART_RTS), 6(LED)
	#else
	SETREG(GPIO_MOD, 7);
	SETREG(GPIO_DIR, BIT2 | BIT3 | BIT4 | BIT5 | BIT6);
	#endif

	SETREG(GPIO_REG, 0);					// initial state of LED is "off"

	// ZI region is zero-filled by hardware.
	mem_set_sram((UINT32) &Image$$ER_ZI$$ZI$$Base, 0x00000000, (UINT32) &Image$$ER_ZI$$ZI$$Length);

	SETREG(PHY_DEBUG, 0x40000139);
	while((GETREG(PHY_DEBUG) & BIT30) == 1);

	SETREG(SDRAM_INIT, SDRAM_PARAM_MAIN_FW_INIT);
	SETREG(SDRAM_REFRESH, SDRAM_PARAM_MAIN_FW_REFRESH);
	SETREG(SDRAM_TIMING, SDRAM_PARAM_MAIN_FW_TIMING);
	SETREG(SDRAM_MRS, SDRAM_PARAM_MAIN_FW_MRS);
	SETREG(SDRAM_CTRL, SDRAM_INITIALIZE);		// initialization of SDRAM begins now
	while (GETREG(SDRAM_STATUS) & 0x00000010);	// wait until the initialization completes (200us)

	for (i = 0; i < DRAM_SIZE / MU_MAX_BYTES; i++)
	{
		mem_set_dram(DRAM_BASE + i * MU_MAX_BYTES, 0x00000000, MU_MAX_BYTES);
	}

	#if OPTION_UART_DEBUG
	uart_init();
	uart_print("Welcome to OpenSSD");
	#endif

	SETREG(SDRAM_ECC_MON, 0xFFFFFFFF);

	// configure SDRAM interrupt
	SETREG(SDRAM_INTCTRL, SDRAM_INT_ENABLE);

	// clear interrupt flags in DRAM controller
	SETREG(SDRAM_INTSTATUS, 0xFFFFFFFF);

	// configure ICU
	SETREG(APB_ICU_CON, INTR_SATA);	// SATA = FIQ, other = IRQ
	SETREG(APB_INT_MSK, INTR_SATA | INTR_FLASH | INTR_SDRAM | INTR_TIMER_1 | INTR_TIMER_2 | INTR_TIMER_3);

	// clear interrupt flags in ICU
	SETREG(APB_INT_STS, 0xFFFFFFFF);

	flash_reset();

	SETREG(FCONF_PAUSE, 0);
	SETREG(INTR_MASK, 0);

	for (bank = 0; bank < NUM_BANKS; bank++)
	{
		flash_clear_irq();

		SETREG(FCP_CMD, FC_COL_ROW_READ_OUT);
		SETREG(FCP_OPTION, 0x06);				// FO_E
		SETREG(FCP_DMA_ADDR, g_temp_mem);
		SETREG(FCP_DMA_CNT, BYTES_PER_SECTOR);
		SETREG(FCP_COL, 0);
		SETREG(FCP_ROW_L(bank), STAMP_PAGE_OFFSET);
		SETREG(FCP_ROW_H(bank), STAMP_PAGE_OFFSET);

		flash_issue_cmd(bank, RETURN_WHEN_DONE);

		if ( (BSP_INTR(bank) & 0xFE)== 0 )
			break;
	}

	#if OPTION_FTL_TEST == FALSE
	sata_reset();
	#endif

    ftl_open();

	#if OPTION_FTL_TEST == TRUE
	extern void ftl_test();
	ftl_test();
    led(1);
    while (1);
    #endif
}
Example #7
0
// misc + VCOUNT
static void load_misc_metadata(void)
{
    UINT32 misc_meta_bytes = NUM_MISC_META_SECT * BYTES_PER_SECTOR;
    UINT32 vcount_bytes    = NUM_VCOUNT_SECT * BYTES_PER_SECTOR;
    UINT32 vcount_addr     = VCOUNT_ADDR;
    UINT32 vcount_boundary = VCOUNT_ADDR + VCOUNT_BYTES;

    UINT32 load_flag = 0;
    UINT32 bank, page_num;
    UINT32 load_cnt = 0;

    flash_finish();

	disable_irq();
	flash_clear_irq();	// clear any flash interrupt flags that might have been set

    // scan valid metadata in descending order from last page offset
    for (page_num = PAGES_PER_BLK - 1; page_num != ((UINT32) -1); page_num--)
    {
        for (bank = 0; bank < NUM_BANKS; bank++)
        {
            if (load_flag & (0x1 << bank))
            {
                continue;
            }
            // read valid metadata from misc. metadata area
            nand_page_ptread(bank,
                             MISCBLK_VBN,
                             page_num,
                             0,
                             NUM_MISC_META_SECT + NUM_VCOUNT_SECT,
                             FTL_BUF(bank),
                             RETURN_ON_ISSUE);
        }
        flash_finish();

        for (bank = 0; bank < NUM_BANKS; bank++)
        {
            if (!(load_flag & (0x1 << bank)) && !(BSP_INTR(bank) & FIRQ_ALL_FF))
            {
                load_flag = load_flag | (0x1 << bank);
                load_cnt++;
            }
            CLR_BSP_INTR(bank, 0xFF);
        }
    }
    ASSERT(load_cnt == NUM_BANKS);

    for (bank = 0; bank < NUM_BANKS; bank++)
    {
        // misc. metadata
        mem_copy(&g_misc_meta[bank], FTL_BUF(bank), sizeof(misc_metadata));

        // vcount metadata
        if (vcount_addr <= vcount_boundary)
        {
            mem_copy(vcount_addr, FTL_BUF(bank) + misc_meta_bytes, vcount_bytes);
            vcount_addr += vcount_bytes;

        }
    }
	enable_irq();
}
Example #8
0
static void build_bad_blk_list(void)
{
	UINT32 bank, num_entries, result, vblk_offset;
	scan_list_t* scan_list = (scan_list_t*) TEMP_BUF_ADDR;

	mem_set_dram(BAD_BLK_BMP_ADDR, NULL, BAD_BLK_BMP_BYTES);

	disable_irq();

	flash_clear_irq();

	for (bank = 0; bank < NUM_BANKS; bank++)
	{
		SETREG(FCP_CMD, FC_COL_ROW_READ_OUT);
		SETREG(FCP_BANK, REAL_BANK(bank));
		SETREG(FCP_OPTION, FO_E);
		SETREG(FCP_DMA_ADDR, (UINT32) scan_list);
		SETREG(FCP_DMA_CNT, SCAN_LIST_SIZE);
		SETREG(FCP_COL, 0);
		SETREG(FCP_ROW_L(bank), SCAN_LIST_PAGE_OFFSET);
		SETREG(FCP_ROW_H(bank), SCAN_LIST_PAGE_OFFSET);

		SETREG(FCP_ISSUE, NULL);
		while ((GETREG(WR_STAT) & 0x00000001) != 0);
		while (BSP_FSM(bank) != BANK_IDLE);

		num_entries = NULL;
		result = OK;

		if (BSP_INTR(bank) & FIRQ_DATA_CORRUPT)
		{
			result = FAIL;
		}
		else
		{
			UINT32 i;

			num_entries = read_dram_16(&(scan_list->num_entries));

			if (num_entries > SCAN_LIST_ITEMS)
			{
				result = FAIL;
			}
			else
			{
				for (i = 0; i < num_entries; i++)
				{
					UINT16 entry = read_dram_16(scan_list->list + i);
					UINT16 pblk_offset = entry & 0x7FFF;

					if (pblk_offset == 0 || pblk_offset >= PBLKS_PER_BANK)
					{
						#if OPTION_REDUCED_CAPACITY == FALSE
						result = FAIL;
						#endif
					}
					else
					{
						write_dram_16(scan_list->list + i, pblk_offset);
					}
				}
			}
		}

		if (result == FAIL)
		{
			num_entries = 0;  // We cannot trust this scan list. Perhaps a software bug.
		}
		else
		{
			write_dram_16(&(scan_list->num_entries), 0);
		}

		g_bad_blk_count[bank] = 0;

		for (vblk_offset = 1; vblk_offset < VBLKS_PER_BANK; vblk_offset++)
		{
			BOOL32 bad = FALSE;

			#if OPTION_2_PLANE
			{
				UINT32 pblk_offset;

				pblk_offset = vblk_offset * NUM_PLANES;

                // fix bug@jasmine v.1.1.0
				if (mem_search_equ_dram(scan_list, sizeof(UINT16), num_entries + 1, pblk_offset) < num_entries + 1)
				{
					bad = TRUE;
				}

				pblk_offset = vblk_offset * NUM_PLANES + 1;

                // fix bug@jasmine v.1.1.0
				if (mem_search_equ_dram(scan_list, sizeof(UINT16), num_entries + 1, pblk_offset) < num_entries + 1)
				{
					bad = TRUE;
				}
			}
			#else
			{
                // fix bug@jasmine v.1.1.0
				if (mem_search_equ_dram(scan_list, sizeof(UINT16), num_entries + 1, vblk_offset) < num_entries + 1)
				{
					bad = TRUE;
				}
			}
			#endif

			if (bad)
			{
				g_bad_blk_count[bank]++;
				set_bit_dram(BAD_BLK_BMP_ADDR + bank*(VBLKS_PER_BANK/8 + 1), vblk_offset);
			}
		}
	}
}
Example #9
0
void test_nand_blocks(void)
{
	// This function is a utility that writes random data to flash pages and verifies them.
	// This function takes a long time to complete.

	UINT32 bank, vblk_offset, page_offset, data, bad;

	#define write_buffer_base	DRAM_BASE
	#define read_buffer_base	(DRAM_BASE + BYTES_PER_VBLK)

	disable_irq();
	flash_clear_irq();

	mem_set_sram(g_test_result, 0, sizeof(g_test_result));

	// Configure the flash controller so that any FIRQ_* does not lead to pause state.
	SETREG(FCONF_PAUSE, 0);

	// STEP 1 - prepare random data

	srand(10);

	for (page_offset = 0; page_offset < PAGES_PER_VBLK; page_offset++)
	{
		data = (rand() & 0xFFFF) | (rand() << 16);
		mem_set_dram(write_buffer_base + page_offset * BYTES_PER_PAGE, data, BYTES_PER_PAGE);
	}

	for (vblk_offset = 1; vblk_offset < VBLKS_PER_BANK; vblk_offset++)
	{
		// STEP 2 - erase a block at each bank

		for (bank = 0; bank < NUM_BANKS; bank++)
		{
			UINT32 rbank = REAL_BANK(bank);

			SETREG(FCP_CMD, FC_ERASE);
			SETREG(FCP_BANK, rbank);
			SETREG(FCP_OPTION, FO_P);
			SETREG(FCP_ROW_L(bank), vblk_offset * PAGES_PER_VBLK);
			SETREG(FCP_ROW_H(bank), vblk_offset * PAGES_PER_VBLK);

			while ((GETREG(WR_STAT) & 0x00000001) != 0);
			SETREG(FCP_ISSUE, NULL);
		}

		// STEP 3 - write to every pages of the erased block

		for (page_offset = 0; page_offset < PAGES_PER_VBLK; page_offset++)
		{
			for (bank = 0; bank < NUM_BANKS; bank++)
			{
				UINT32 rbank = REAL_BANK(bank);

				SETREG(FCP_CMD, FC_COL_ROW_IN_PROG);
				SETREG(FCP_BANK, rbank);
				SETREG(FCP_OPTION, FO_P | FO_E | FO_B_W_DRDY);
				SETREG(FCP_DMA_ADDR, write_buffer_base + page_offset * BYTES_PER_PAGE);
				SETREG(FCP_DMA_CNT, BYTES_PER_PAGE);
				SETREG(FCP_COL, 0);
				SETREG(FCP_ROW_L(bank), vblk_offset * PAGES_PER_VBLK + page_offset);
				SETREG(FCP_ROW_H(bank), vblk_offset * PAGES_PER_VBLK + page_offset);

				while ((GETREG(WR_STAT) & 0x00000001) != 0);
				SETREG(FCP_ISSUE, NULL);
			}
		}

		// STEP 4 - check the FC_ERASE and FC_COL_ROW_IN_PROG results.

		bad = 0;

		while (GETREG(MON_CHABANKIDLE) != 0);

		for (bank = 0; bank < NUM_BANKS; bank++)
		{
			if (BSP_INTR(bank) & (FIRQ_BADBLK_H | FIRQ_BADBLK_L))
			{
				bad |= (1 << bank);
				CLR_BSP_INTR(bank, 0xFF);
				g_test_result[bank].erase_prog_fail++;
			}
		}

		// STEP 5 - read and verify
		// We check ECC/CRC results for verification.

		for (page_offset = 0; page_offset < PAGES_PER_VBLK; page_offset++)
		{
			for (bank = 0; bank < NUM_BANKS; bank++)
			{
				UINT32 rbank = REAL_BANK(bank);

				if (bad & (1 << bank))
					continue;

				SETREG(FCP_CMD, FC_COL_ROW_READ_OUT);
				SETREG(FCP_BANK, rbank);
				SETREG(FCP_OPTION, FO_P | FO_E);
				SETREG(FCP_DMA_ADDR, read_buffer_base + bank * BYTES_PER_PAGE);
				SETREG(FCP_DMA_CNT, BYTES_PER_PAGE);
				SETREG(FCP_COL, 0);
				SETREG(FCP_ROW_L(bank), vblk_offset * PAGES_PER_VBLK + page_offset);
				SETREG(FCP_ROW_H(bank), vblk_offset * PAGES_PER_VBLK + page_offset);

				while ((GETREG(WR_STAT) & 0x00000001) != 0);
				SETREG(FCP_ISSUE, NULL);
			}
		}

		// STEP 6 - check the FC_COL_ROW_READ_OUT results

		while (GETREG(MON_CHABANKIDLE) != 0);

		for (bank = 0; bank < NUM_BANKS; bank++)
		{
			if (BSP_INTR(bank) & FIRQ_DATA_CORRUPT)
			{
				bad |= (1 << bank);
				CLR_BSP_INTR(bank, 0xFF);
				g_test_result[bank].read_fail++;
			}
		}

		// STEP 7 - erase the blocks, but not the bad ones

		for (bank = 0; bank < NUM_BANKS; bank++)
		{
			UINT32 rbank = REAL_BANK(bank);

			if (bad & (1 << bank))
				continue;

			SETREG(FCP_CMD, FC_ERASE);
			SETREG(FCP_BANK, rbank);
			SETREG(FCP_OPTION, FO_P);
			SETREG(FCP_ROW_L(bank), vblk_offset * PAGES_PER_VBLK);
			SETREG(FCP_ROW_H(bank), vblk_offset * PAGES_PER_VBLK);

			while ((GETREG(WR_STAT) & 0x00000001) != 0);
			SETREG(FCP_ISSUE, NULL);
		}
	}

	// Now that bad blocks contain non-0xFF data, it is a good time to use install.exe to scan bad blocks.
}