Пример #1
0
bool VFL_ReadScatteredPagesInVb(u32* virtualPageNumber, int count, u8* main, SpareData* spare)
{
	int i;
	int ret;

	VFLData1.field_8 += count;
	VFLData1.field_20++;

	for(i = 0; i < count; i++) {
		u32 dwVpn = virtualPageNumber[i] + (NANDGeometry->pagesPerSuBlk * FTLData->field_4);
		u16 virtualBlock;
		u16 virtualPage;
		u16 physicalBlock;

		virtual_page_number_to_virtual_address(dwVpn, &ScatteredBankNumberBuffer[i], &virtualBlock, &virtualPage);
		physicalBlock = virtual_block_to_physical_block(ScatteredBankNumberBuffer[i], virtualBlock);
		ScatteredPageNumberBuffer[i] = physicalBlock * NANDGeometry->pagesPerBlock + virtualPage;
#ifdef IPHONE_DEBUG
		LOG("ftl: vfl_read (scattered): vpn: %u, bank  %d, page %u\n", virtualPageNumber[i], ScatteredBankNumberBuffer[i], ScatteredPageNumberBuffer[i]);
#endif
	}

	ret = nand_read_multiple(ScatteredBankNumberBuffer, ScatteredPageNumberBuffer, main, spare, count);

	if(ret != 0)
		return false;
	else
		return true;
}
Пример #2
0
static int VFL_ReadScatteredPagesInVb(uint32_t* virtualPageNumber, int count, uint8_t* main, SpareData* spare, int* refresh_page) {
	VFLData1.field_8 += count;
	VFLData1.field_20++;

	if(refresh_page) {
		*refresh_page = FALSE;
	}

	int i = 0;
	for(i = 0; i < count; i++) {
		uint32_t dwVpn = virtualPageNumber[i] + (Data->pagesPerSubBlk * Data2->field_4);

		uint16_t virtualBlock;
		uint16_t virtualPage;
		uint16_t physicalBlock;

		virtual_page_number_to_virtual_address(dwVpn, &ScatteredBankNumberBuffer[i], &virtualBlock, &virtualPage);
		physicalBlock = virtual_block_to_physical_block(ScatteredBankNumberBuffer[i], virtualBlock);
		ScatteredPageNumberBuffer[i] = physicalBlock * Data->pagesPerBlock + virtualPage;
	}

	int ret = nand_read_multiple(ScatteredBankNumberBuffer, ScatteredPageNumberBuffer, main, spare, count);
	if(Data->field_2F <= 0 && refresh_page != NULL) {
		bufferPrintf("ftl: VFL_ReadScatteredPagesInVb mark page for refresh\r\n");
		*refresh_page = TRUE;
	}

	if(ret != 0)
		return FALSE;
	else
		return TRUE;
}
Пример #3
0
int VFL_Erase(u16 block) {
	u16 physicalBlock;
	int ret;
	int bank;
	int i;

	block = block + FTLData->field_4;

	for(bank = 0; bank < NANDGeometry->banksTotal; ++bank) {
		if(vfl_check_remap_scheduled(bank, block))
		{
			vfl_remap_block(bank, block);
			vfl_mark_remap_done(bank, block);
			vfl_commit_cxt(bank);
		}

		physicalBlock = virtual_block_to_physical_block(bank, block);

		for(i = 0; i < 3; ++i)
		{
			ret = nand_erase(bank, physicalBlock);
			if(ret == 0)
				break;
		}

		if(ret) {
			LOG("ftl: block erase failed for bank %d, block %d\n", bank, block);
			// FIXME: properly handle this
			return ret;
		}
	}

	return 0;
}
Пример #4
0
int VFL_Read(uint32_t virtualPageNumber, uint8_t* buffer, uint8_t* spare, int empty_ok, int* refresh_page) {
	if(refresh_page) {
		*refresh_page = FALSE;
	}

	VFLData1.field_8++;
	VFLData1.field_20++;

	uint32_t dwVpn = virtualPageNumber + (Data->pagesPerSubBlk * Data2->field_4);
	if(dwVpn >= Data->pagesTotal) {
		bufferPrintf("ftl: dwVpn overflow: %d\r\n", dwVpn);
		return ERROR_ARG;
	}

	if(dwVpn < Data->pagesPerSubBlk) {
		bufferPrintf("ftl: dwVpn underflow: %d\r\n", dwVpn);
	}

	uint16_t virtualBank;
	uint16_t virtualBlock;
	uint16_t virtualPage;
	uint16_t physicalBlock;

	virtual_page_number_to_virtual_address(dwVpn, &virtualBank, &virtualBlock, &virtualPage);
	physicalBlock = virtual_block_to_physical_block(virtualBank, virtualBlock);

	int page = physicalBlock * Data->pagesPerBlock + virtualPage;

	int ret = nand_read(virtualBank, page, buffer, spare, TRUE, TRUE);

	if(!empty_ok && ret == ERROR_EMPTYBLOCK) {
		ret = ERROR_NAND;
	}

	if(refresh_page) {
		if((Data->field_2F <= 0 && ret == 0) || ret == ERROR_NAND) {
			bufferPrintf("ftl: setting refresh_page to TRUE due to the following factors: Data->field_2F = %x, ret = %d\r\n", Data->field_2F, ret);
			*refresh_page = TRUE;
		}
	}

	if(ret == ERROR_ARG || ret == ERROR_NAND) {
		nand_bank_reset(virtualBank, 100);
		ret = nand_read(virtualBank, page, buffer, spare, TRUE, TRUE);
		if(!empty_ok && ret == ERROR_EMPTYBLOCK) {
			return ERROR_NAND;
		}

		if(ret == ERROR_ARG || ret == ERROR_NAND)
			return ret;
	}

	if(ret == ERROR_EMPTYBLOCK) {
		if(spare) {
			memset(spare, 0xFF, sizeof(SpareData));
		}
	}

	return ret;
}
Пример #5
0
int VFL_Read(u32 virtualPageNumber, u8* buffer, u8* spare, bool empty_ok)
{
	u16 virtualBank;
	u16 virtualBlock;
	u16 virtualPage;
	u16 physicalBlock;
	u32 dwVpn;

	int page;
	int ret;

	VFLData1.field_8++;
	VFLData1.field_20++;

	dwVpn = virtualPageNumber + (NANDGeometry->pagesPerSuBlk * FTLData->field_4);
	if(dwVpn >= NANDGeometry->pagesTotal) {
		LOG("ftl: dwVpn overflow: %d\n", dwVpn);
		return -EINVAL;
	}

	if(dwVpn < NANDGeometry->pagesPerSuBlk) {
		LOG("ftl: dwVpn underflow: %d\n", dwVpn);
	}

	virtual_page_number_to_virtual_address(dwVpn, &virtualBank, &virtualBlock, &virtualPage);
	physicalBlock = virtual_block_to_physical_block(virtualBank, virtualBlock);

	page = physicalBlock * NANDGeometry->pagesPerBlock + virtualPage;

#ifdef IPHONE_DEBUG
	LOG("ftl: vfl_read: vpn: %u, bank %d, page %u\n", virtualPageNumber, virtualBank, page);
#endif

	ret = nand_read(virtualBank, page, buffer, spare, true, true);

	if(!empty_ok && ret == ERROR_EMPTYBLOCK)
	{
		ret = -EIO;
	}

	if(ret == -EINVAL || ret == -EIO) {
		nand_bank_reset(virtualBank, 100);
		ret = nand_read(virtualBank, page, buffer, spare, true, true);
		if(!empty_ok && ret == ERROR_EMPTYBLOCK) {
			return -EIO;
		}

		if(ret == -EINVAL || ret == -EIO)
			return ret;
	}

	if(ret == ERROR_EMPTYBLOCK) {
		if(spare) {
			memset(spare, 0xFF, sizeof(SpareData));
		}
	}

	return ret;
}
Пример #6
0
int VFL_Write(u32 virtualPageNumber, u8* buffer, u8* spare)
{
	u16 virtualBank;
	u16 virtualBlock;
	u16 virtualPage;
	u16 physicalBlock;

	u32 dwVpn;

	int page;
	int ret;

	dwVpn = virtualPageNumber + (NANDGeometry->pagesPerSuBlk * FTLData->field_4);
	if(dwVpn >= NANDGeometry->pagesTotal) {
		LOG("ftl: dwVpn overflow: %d\n", dwVpn);
		return -EINVAL;
	}

	if(dwVpn < NANDGeometry->pagesPerSuBlk) {
		LOG("ftl: dwVpn underflow: %d\n", dwVpn);
	}

	virtual_page_number_to_virtual_address(dwVpn, &virtualBank, &virtualBlock, &virtualPage);
	physicalBlock = virtual_block_to_physical_block(virtualBank, virtualBlock);

	page = physicalBlock * NANDGeometry->pagesPerBlock + virtualPage;

#ifdef IPHONE_DEBUG
	LOG("ftl: vfl_write: vpn: %u, bank %d, page %u\n", virtualPageNumber, virtualBank, page);
#endif

		ret = nand_read(virtualBank, page, PageBuffer, SpareBuffer, true, true);
	if(ret != ERROR_EMPTYBLOCK)
	{
		LOG("ftl: WTF trying to write to a non-blank page! vpn = %u bank = %d page = %u\r\n", virtualPageNumber, virtualBank, page);
		return -1;
	}

	ret = nand_write(virtualBank, page, buffer, spare, true);
	if(ret == 0)
		return 0;

	++pstVFLCxt[virtualBank].field_16;
	vfl_gen_checksum(virtualBank);
	vfl_schedule_block_for_remap(virtualBank, virtualBlock);

	return -1;
}
Пример #7
0
static error_t virtual_page_number_to_physical(vfl_vsvfl_device_t *_vfl, uint32_t _vpNum, uint32_t* _ce, uint32_t* _page) {
	uint32_t ce, vBank, ret, bank_offset, pBlock;

	vBank = _vpNum % _vfl->geometry.banks_total;
	ce = vBank % _vfl->geometry.num_ce;

	ret = virtual_block_to_physical_block(_vfl, vBank, _vpNum / _vfl->geometry.pages_per_sublk, &pBlock);

	if(FAILED(ret))
		return ret;

	pBlock = remap_block(_vfl, ce, pBlock, 0);

	bank_offset = _vfl->geometry.bank_address_space * (pBlock / _vfl->geometry.blocks_per_bank);

	*_ce = ce;
	*_page = _vfl->geometry.pages_per_block_2 * (bank_offset + (pBlock % _vfl->geometry.blocks_per_bank))
			+ ((_vpNum % _vfl->geometry.pages_per_sublk) / _vfl->geometry.banks_total);

	return SUCCESS;
}
Пример #8
0
static uint32_t remap_block(vfl_vsvfl_device_t *_vfl, uint32_t _ce, uint32_t _block, uint32_t *_isGood) {
	DebugPrintf("vsvfl: remap_block: CE %d, block %d\r\n", _ce, _block);

	if(vfl_is_good_block(_vfl->bbt[_ce], _block))
		return _block;

	DebugPrintf("vsvfl: remapping block...\r\n");

	if(_isGood)
		_isGood = 0;

	int pwDesPbn;
	for(pwDesPbn = 0; pwDesPbn < _vfl->geometry.blocks_per_ce - _vfl->contexts[_ce].reserved_block_pool_start * _vfl->geometry.banks_per_ce; pwDesPbn++)
	{
		if(_vfl->contexts[_ce].reserved_block_pool_map[pwDesPbn] == _block)
		{
			uint32_t vBank, vBlock, pBlock;

			/*
			if(pwDesPbn >= _vfl->geometry.blocks_per_ce)
				bufferPrintf("ftl: Destination physical block for remapping is greater than number of blocks per CE!");
			*/

			vBank = _ce + _vfl->geometry.num_ce * (pwDesPbn / (_vfl->geometry.blocks_per_bank_vfl - _vfl->contexts[_ce].reserved_block_pool_start));
			vBlock = _vfl->contexts[_ce].reserved_block_pool_start + (pwDesPbn % (_vfl->geometry.blocks_per_bank_vfl - _vfl->contexts[_ce].reserved_block_pool_start));

			if(FAILED(virtual_block_to_physical_block(_vfl, vBank, vBlock, &pBlock)))
				system_panic("vfl: failed to convert virtual reserved block to physical\r\n");

			return pBlock;
		}
	}

	bufferPrintf("vfl: failed to remap CE %d block 0x%04x\r\n", _ce, _block);
	return _block;
}
Пример #9
0
static error_t vfl_vsvfl_open(vfl_device_t *_vfl, nand_device_t *_nand)
{
    vfl_vsvfl_device_t *vfl = CONTAINER_OF(vfl_vsvfl_device_t, vfl, _vfl);

    if(vfl->device || !_nand)
        return EINVAL;

    vfl->device = _nand;
    error_t ret = vfl_vsvfl_setup_geometry(vfl);
    if(FAILED(ret))
        return ret;

    bufferPrintf("vsvfl: Opening %p.\r\n", _nand);

    vfl->contexts = malloc(vfl->geometry.num_ce * sizeof(vfl_vsvfl_context_t));
    memset(vfl->contexts, 0, vfl->geometry.num_ce * sizeof(vfl_vsvfl_context_t));

    vfl->pageBuffer = (uint32_t*) malloc(vfl->geometry.pages_per_block * sizeof(uint32_t));
    vfl->chipBuffer = (uint16_t*) malloc(vfl->geometry.pages_per_block * sizeof(uint16_t));
    vfl->blockBuffer = (uint16_t*) malloc(vfl->geometry.banks_total * sizeof(uint16_t));

    uint32_t ce = 0;
    for(ce = 0; ce < vfl->geometry.num_ce; ce++) {
        vfl->bbt[ce] = (uint8_t*) malloc(CEIL_DIVIDE(vfl->geometry.blocks_per_ce, 8));

        bufferPrintf("vsvfl: Checking CE %d.\r\n", ce);

        if(FAILED(nand_device_read_special_page(_nand, ce, "DEVICEINFOBBT\0\0\0",
                                                vfl->bbt[ce], CEIL_DIVIDE(vfl->geometry.blocks_per_ce, 8))))
        {
            bufferPrintf("vsvfl: Failed to find DEVICEINFOBBT!\r\n");
            return EIO;
        }

        if(ce >= vfl->geometry.num_ce)
            return EIO;

        vfl_vsvfl_context_t *curVFLCxt = &vfl->contexts[ce];
        uint8_t* pageBuffer = malloc(vfl->geometry.bytes_per_page);
        uint8_t* spareBuffer = malloc(vfl->geometry.bytes_per_spare);
        if(pageBuffer == NULL || spareBuffer == NULL) {
            bufferPrintf("ftl: cannot allocate page and spare buffer\r\n");
            return ENOMEM;
        }

        // Any VFLCxt page will contain an up-to-date list of all blocks used to store VFLCxt pages. Find any such
        // page in the system area.

        int i;
        for(i = vfl->geometry.reserved_blocks; i < vfl->geometry.fs_start_block; i++) {
            // so pstBBTArea is a bit array of some sort
            if(!(vfl->bbt[ce][i / 8] & (1 << (i  & 0x7))))
                continue;

            if(SUCCEEDED(nand_device_read_single_page(vfl->device, ce, i, 0, pageBuffer, spareBuffer, 0)))
            {
                memcpy(curVFLCxt->vfl_context_block, ((vfl_vsvfl_context_t*)pageBuffer)->vfl_context_block,
                       sizeof(curVFLCxt->vfl_context_block));
                break;
            }
        }

        if(i == vfl->geometry.fs_start_block) {
            bufferPrintf("vsvfl: cannot find readable VFLCxtBlock\r\n");
            free(pageBuffer);
            free(spareBuffer);
            return EIO;
        }

        // Since VFLCxtBlock is a ringbuffer, if blockA.page0.spare.usnDec < blockB.page0.usnDec, then for any page a
        // in blockA and any page b in blockB, a.spare.usNDec < b.spare.usnDec. Therefore, to begin finding the
        // page/VFLCxt with the lowest usnDec, we should just look at the first page of each block in the ring.
        int minUsn = 0xFFFFFFFF;
        int VFLCxtIdx = 4;
        for(i = 0; i < 4; i++) {
            uint16_t block = curVFLCxt->vfl_context_block[i];
            if(block == 0xFFFF)
                continue;

            if(FAILED(nand_device_read_single_page(vfl->device, ce, block, 0, pageBuffer, spareBuffer, 0)))
                continue;

            vfl_vsvfl_spare_data_t *spareData = (vfl_vsvfl_spare_data_t*)spareBuffer;

            if(spareData->meta.usnDec > 0 && spareData->meta.usnDec <= minUsn) {
                minUsn = spareData->meta.usnDec;
                VFLCxtIdx = i;
            }
        }

        if(VFLCxtIdx == 4) {
            bufferPrintf("vsvfl: cannot find readable VFLCxtBlock index in spares\r\n");
            free(pageBuffer);
            free(spareBuffer);
            return EIO;
        }

        // VFLCxts are stored in the block such that they are duplicated 8 times. Therefore, we only need to
        // read every 8th page, and nand_readvfl_cxt_page will try the 7 subsequent pages if the first was
        // no good. The last non-blank page will have the lowest spare.usnDec and highest usnInc for VFLCxt
        // in all the land (and is the newest).
        int page = 8;
        int last = 0;
        for(page = 8; page < vfl->geometry.pages_per_block; page += 8) {
            if(nand_device_read_single_page(vfl->device, ce, curVFLCxt->vfl_context_block[VFLCxtIdx], page, pageBuffer, spareBuffer, 0) != 0) {
                break;
            }

            last = page;
        }

        if(nand_device_read_single_page(vfl->device, ce, curVFLCxt->vfl_context_block[VFLCxtIdx], last, pageBuffer, spareBuffer, 0) != 0) {
            bufferPrintf("vsvfl: cannot find readable VFLCxt\n");
            free(pageBuffer);
            free(spareBuffer);
            return -1;
        }

        // Aha, so the upshot is that this finds the VFLCxt and copies it into vfl->contexts
        memcpy(&vfl->contexts[ce], pageBuffer, sizeof(vfl_vsvfl_context_t));

        // This is the newest VFLCxt across all CEs
        if(curVFLCxt->usn_inc >= vfl->current_version) {
            vfl->current_version = curVFLCxt->usn_inc;
        }

        free(pageBuffer);
        free(spareBuffer);

        // Verify the checksum
        if(vfl_check_checksum(vfl, ce) == FALSE)
        {
            bufferPrintf("vsvfl: VFLCxt has bad checksum.\r\n");
            return EIO;
        }
    }

    // retrieve some global parameters from the latest VFL across all CEs.
    vfl_vsvfl_context_t *latestCxt = get_most_updated_context(vfl);

    // Then we update the VFLCxts on every ce with that information.
    for(ce = 0; ce < vfl->geometry.num_ce; ce++) {
        // Don't copy over own data.
        if(&vfl->contexts[ce] != latestCxt) {
            // Copy the data, and generate the new checksum.
            memcpy(vfl->contexts[ce].control_block, latestCxt->control_block, sizeof(latestCxt->control_block));
            vfl->contexts[ce].usable_blocks_per_bank = latestCxt->usable_blocks_per_bank;
            vfl->contexts[ce].reserved_block_pool_start = latestCxt->reserved_block_pool_start;
            vfl->contexts[ce].ftl_type = latestCxt->ftl_type;
            memcpy(vfl->contexts[ce].field_6CA, latestCxt->field_6CA, sizeof(latestCxt->field_6CA));

            vfl_gen_checksum(vfl, ce);
        }
    }

    // Vendor-specific virtual-from/to-physical functions.
    // Note: support for some vendors is still missing.
    nand_device_t *nand = vfl->device;
    uint32_t vendorType = vfl->contexts[0].vendor_type;

    if(!vendorType)
        if(FAILED(nand_device_get_info(nand, diVendorType, &vendorType, sizeof(vendorType))))
            return EIO;

    switch(vendorType) {
    case 0x10001:
        vfl->geometry.banks_per_ce = 1;
        vfl->virtual_to_physical = virtual_to_physical_10001;
        break;

    case 0x100010:
    case 0x100014:
    case 0x120014:
        vfl->geometry.banks_per_ce = 2;
        vfl->virtual_to_physical = virtual_to_physical_100014;
        break;

    case 0x150011:
        vfl->geometry.banks_per_ce = 2;
        vfl->virtual_to_physical = virtual_to_physical_150011;
        break;

    default:
        bufferPrintf("vsvfl: unsupported vendor 0x%06x\r\n", vendorType);
        return EIO;
    }

    if(FAILED(nand_device_set_info(nand, diVendorType, &vendorType, sizeof(vendorType))))
        return EIO;

    vfl->geometry.pages_per_sublk = vfl->geometry.pages_per_block * vfl->geometry.banks_per_ce * vfl->geometry.num_ce;
    vfl->geometry.banks_total = vfl->geometry.num_ce * vfl->geometry.banks_per_ce;
    vfl->geometry.blocks_per_bank_vfl = vfl->geometry.blocks_per_ce / vfl->geometry.banks_per_ce;

    uint32_t banksPerCE = vfl->geometry.banks_per_ce;
    if(FAILED(nand_device_set_info(nand, diBanksPerCE_VFL, &banksPerCE, sizeof(banksPerCE))))
        return EIO;

    bufferPrintf("vsvfl: detected chip vendor 0x%06x\r\n", vendorType);

    // Now, discard the old scfg bad-block table, and set it using the VFL context's reserved block pool map.
    uint32_t bank, i;
    uint32_t num_reserved = vfl->contexts[0].reserved_block_pool_start;
    uint32_t num_non_reserved = vfl->geometry.blocks_per_bank_vfl - num_reserved;

    for(ce = 0; ce < vfl->geometry.num_ce; ce++) {
        memset(vfl->bbt[ce], 0xFF, CEIL_DIVIDE(vfl->geometry.blocks_per_ce, 8));

        for(bank = 0; bank < banksPerCE; bank++) {
            for(i = 0; i < num_non_reserved; i++) {
                uint16_t mapEntry = vfl->contexts[ce].reserved_block_pool_map[bank * num_non_reserved + i];
                uint32_t pBlock;

                if(mapEntry == 0xFFF0)
                    continue;

                if(mapEntry < vfl->geometry.blocks_per_ce) {
                    pBlock = mapEntry;
                } else if(mapEntry > 0xFFF0) {
                    virtual_block_to_physical_block(vfl, ce + bank * vfl->geometry.num_ce, num_reserved + i, &pBlock);
                } else {
                    system_panic("vsvfl: bad map table: CE %d, entry %d, value 0x%08x\r\n",
                                 ce, bank * num_non_reserved + i, mapEntry);
                }

                vfl->bbt[ce][pBlock / 8] &= ~(1 << (pBlock % 8));
            }
        }
    }

    bufferPrintf("vsvfl: VFL successfully opened!\r\n");

    return SUCCESS;
}
Пример #10
0
static error_t vfl_vsvfl_erase_single_block(vfl_device_t *_vfl, uint32_t _vbn, int _replaceBadBlock) {
    vfl_vsvfl_device_t *vfl = CONTAINER_OF(vfl_vsvfl_device_t, vfl, _vfl);
    uint32_t bank;

    // In order to erase a single virtual block, we have to erase the matching
    // blocks across all banks.
    for (bank = 0; bank < vfl->geometry.banks_total; bank++) {
        uint32_t pBlock, pCE, blockRemapped;

        // Find the physical block before bad-block remapping.
        virtual_block_to_physical_block(vfl, bank, _vbn, &pBlock);
        pCE = bank % vfl->geometry.num_ce;
        vfl->blockBuffer[bank] = pBlock;

        if (is_block_in_scrub_list(vfl, pCE, pBlock)) {
            // TODO: this.
            system_panic("vsvfl: scrub list support not yet!\r\n");
        }

        // Remap the block and calculate its physical number (considering bank address space).
        blockRemapped = remap_block(vfl, pCE, pBlock, 0);
        vfl->blockBuffer[bank] = blockRemapped % vfl->geometry.blocks_per_bank
                                 + (blockRemapped / vfl->geometry.blocks_per_bank) * vfl->geometry.bank_address_space;
    }

    // TODO: H2FMI erase multiple blocks. Currently we erase the blocks one by one.
    // Actually, the block buffer is used for erase multiple blocks, so we won't use it here.
    uint32_t status = EINVAL;

    for (bank = 0; bank < vfl->geometry.banks_total; bank++) {
        uint32_t pBlock, pCE, tries;

        virtual_block_to_physical_block(vfl, bank, _vbn, &pBlock);
        pCE = bank % vfl->geometry.num_ce;

        // Try to erase each block at most 3 times.
        for (tries = 0; tries < 3; tries++) {
            uint32_t blockRemapped, bankStart, blockOffset;

            blockRemapped = remap_block(vfl, pCE, pBlock, 0);
            bankStart = (blockRemapped / vfl->geometry.blocks_per_bank) * vfl->geometry.bank_address_space;
            blockOffset = blockRemapped % vfl->geometry.blocks_per_bank;

            status = nand_device_erase_single_block(vfl->device, pCE, bankStart + blockOffset);
            if (status == 0)
                break;

            // TODO: add block map support.
            //mark_bad_block(vfl, pCE, pBlock, status);
            bufferPrintf("vfl: failed erasing physical block %d on bank %d. status: 0x%08x\r\n",
                         blockRemapped, bank, status);

            if (!_replaceBadBlock)
                return EINVAL;

            // TODO: complete bad block replacement.
            system_panic("vfl: found a bad block. we don't treat those for now. sorry!\r\n");
        }
    }

    if (status)
        system_panic("vfl: failed to erase virtual block %d!\r\n", _vbn);

    return 0;
}