static error_t vsvfl_write_vfl_cxt_to_flash(vfl_vsvfl_device_t *_vfl, uint32_t _ce) { if(_ce >= _vfl->geometry.num_ce) return EINVAL; if(!vfl_check_checksum(_vfl, _ce)) system_panic("vsvfl_write_vfl_cxt_to_flash: failed checksum\r\n"); uint8_t* pageBuffer = memalign(0x40, _vfl->geometry.bytes_per_page); uint8_t* spareBuffer = memalign(0x40, _vfl->geometry.bytes_per_spare); if(pageBuffer == NULL || spareBuffer == NULL) { bufferPrintf("vfl: cannot allocate page and spare buffer\r\n"); return ENOMEM; } memset(pageBuffer, 0x0, _vfl->geometry.bytes_per_page); vfl_vsvfl_context_t *curVFLCxt = &_vfl->contexts[_ce]; curVFLCxt->usn_inc = _vfl->current_version++; uint32_t curPage = curVFLCxt->usn_page; curVFLCxt->usn_page += 8; curVFLCxt->usn_dec -= 1; vfl_gen_checksum(_vfl, _ce); memcpy(pageBuffer, curVFLCxt, 0x800); int i; for (i = 0; i < 8; i++) { memset(spareBuffer, 0xFF, _vfl->geometry.bytes_per_spare); ((uint32_t*)spareBuffer)[0] = curVFLCxt->usn_dec; spareBuffer[8] = 0; spareBuffer[9] = 0x80; uint32_t bankStart = (curVFLCxt->vfl_context_block[curVFLCxt->usn_block] / _vfl->geometry.blocks_per_bank) * _vfl->geometry.bank_address_space; uint32_t blockOffset = curVFLCxt->vfl_context_block[curVFLCxt->usn_block] % _vfl->geometry.blocks_per_bank; int status = nand_device_write_single_page(_vfl->device, _ce, 0, (bankStart + blockOffset) * _vfl->geometry.pages_per_block_2 + curPage + i, pageBuffer, spareBuffer); if(FAILED(status)) { bufferPrintf("vfl_write_vfl_cxt_to_flash: Failed write\r\n"); free(pageBuffer); free(spareBuffer); // vsvfl_mark_page_as_bad(_ce, (bankStart + blockOffset) * _vfl->geometry.pages_per_block_2 + curPage + i, status); return EIO; } } int fails = 0; for (i = 0; i < 8; i++) { uint32_t bankStart = (curVFLCxt->vfl_context_block[curVFLCxt->usn_block] / _vfl->geometry.blocks_per_bank) * _vfl->geometry.bank_address_space; uint32_t blockOffset = curVFLCxt->vfl_context_block[curVFLCxt->usn_block] % _vfl->geometry.blocks_per_bank; if(FAILED(nand_device_read_single_page(_vfl->device, _ce, 0, (bankStart + blockOffset) * _vfl->geometry.pages_per_block_2 + curPage + i, pageBuffer, spareBuffer, 0))) { //vsvfl_store_block_map_single_page(_ce, (bankStart + blockOffset) * _vfl->geometry.pages_per_block_2 + curPage + i); fails++; continue; } if(memcmp(pageBuffer, curVFLCxt, 0x6E0) || ((uint32_t*)spareBuffer)[0] != curVFLCxt->usn_dec || spareBuffer[8] || spareBuffer[9] != 0x80) fails++; } free(pageBuffer); free(spareBuffer); if(fails > 3) return EIO; else return SUCCESS; }
static error_t vfl_vsvfl_read_single_page(vfl_device_t *_vfl, uint32_t dwVpn, uint8_t* buffer, uint8_t* spare, int empty_ok, int* refresh_page, uint32_t disable_aes) { vfl_vsvfl_device_t *vfl = CONTAINER_OF(vfl_vsvfl_device_t, vfl, _vfl); if(refresh_page) *refresh_page = FALSE; //VFLData1.field_8++; //VFLData1.field_20++; uint32_t pCE = 0, pPage = 0; int ret; ret = virtual_page_number_to_physical(vfl, dwVpn, &pCE, &pPage); if(FAILED(ret)) { bufferPrintf("vfl_vsvfl_read_single_page: virtual_page_number_to_physical returned an error (dwVpn %d)!\r\n", dwVpn); return ret; } // Hack to get reading by absolute page number. ret = nand_device_read_single_page(vfl->device, pCE, 0, pPage, buffer, spare, disable_aes); if(!empty_ok && ret == ENOENT) ret = EIO; else if(empty_ok && ret == ENOENT) return 1; if(ret == EINVAL || ret == EIO) { ret = nand_device_read_single_page(vfl->device, pCE, 0, pPage, buffer, spare, disable_aes); if(!empty_ok && ret == ENOENT) return EIO; if(ret == EINVAL || ret == EIO) return ret; } if(ret == ENOENT && spare) memset(spare, 0xFF, vfl->geometry.bytes_per_spare); return ret; }
error_t nand_device_read_special_page(nand_device_t *_dev, uint32_t _ce, char _page[16], uint8_t *_buffer, size_t _amt) { uint32_t bytesPerPage, blocksPerCE, pagesPerBlock; error_t ret = nand_device_get_info(_dev, diBytesPerPage, &bytesPerPage, sizeof(bytesPerPage)); if(FAILED(ret)) return EINVAL; ret = nand_device_get_info(_dev, diBlocksPerCE, &blocksPerCE, sizeof(blocksPerCE)); if(FAILED(ret)) return EINVAL; ret = nand_device_get_info(_dev, diPagesPerBlock, &pagesPerBlock, sizeof(pagesPerBlock)); if(FAILED(ret)) return EINVAL; uint8_t* buffer = memalign(DMA_ALIGN, bytesPerPage); int lowestBlock = blocksPerCE - (blocksPerCE / 10); int block; for(block = blocksPerCE - 1; block >= lowestBlock; block--) { int page; int badBlockCount = 0; for(page = 0; page < pagesPerBlock; page++) { if(badBlockCount > 2) { DebugPrintf("vfl: read_special_page - too many bad pages, skipping block %d\r\n", block); break; } int ret = nand_device_read_single_page(_dev, _ce, block, page, buffer, NULL); if(ret != 0) { if(ret == 1) { DebugPrintf("vfl: read_special_page - found 'badBlock' on ce %d, page %d\r\n", _ce, (block * pagesPerBlock) + page); badBlockCount++; } DebugPrintf("vfl: read_special_page - skipping ce %d, page %d\r\n", _ce, (block * pagesPerBlock) + page); continue; } if(memcmp(buffer, _page, sizeof(_page)) == 0) { if(_buffer) { size_t amt = ((size_t*)buffer)[13]; if(amt > _amt) amt = _amt; memcpy(_buffer, buffer + 0x38, amt); } free(buffer); return SUCCESS; } else DebugPrintf("vfl: did not find signature on ce %d, page %d\r\n", _ce, (block * pagesPerBlock) + page); } } free(buffer); return ENOENT; }
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; }