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; }
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; }
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; }
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; }
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; }
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; }
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; }
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; }
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; }
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; }