/* * nftldump.c: Dumping the content of NFTL partitions on a "Physical Disk" * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * ToDo: * 1. UnitSizeFactor != 0xFF cases * 2. test, test, and test !!! */ #define PROGRAM_NAME "nftldump" #define _XOPEN_SOURCE 500 /* For pread */ #include #include #include #include #include #include #include #include #include #include #include #include #include static struct NFTLMediaHeader MedHead[2]; static mtd_info_t meminfo; static struct nftl_oob oobbuf; static struct mtd_oob_buf oob = {0, 16, (unsigned char *)&oobbuf}; static int fd, ofd = -1;; static int NumMedHeads; static unsigned char BadUnitTable[MAX_ERASE_ZONES]; #if __BYTE_ORDER == __LITTLE_ENDIAN #define SWAP16(x) do { ; } while(0) #define SWAP32(x) do { ; } while(0) #else #define SWAP16(x) do { x = swab16(x); } while(0) #define SWAP32(x) do { x = swab32(x); } while(0) #endif /* VUCtable, store the Erase Unit Number of the first Erase Unit in the chain */ static unsigned short *VUCtable; /* FixMe: make this dynamic allocated */ #define ERASESIZE 0x2000 #define NUMVUNITS ((40*1024*1024) / ERASESIZE) static union nftl_uci UCItable[NUMVUNITS][3]; static unsigned short nextEUN(unsigned short curEUN) { return UCItable[curEUN][0].a.ReplUnitNum; } static unsigned int find_media_headers(void) { int i; static unsigned long ofs = 0; NumMedHeads = 0; while (ofs < meminfo.size) { pread(fd, &MedHead[NumMedHeads], sizeof(struct NFTLMediaHeader), ofs); if (!strncmp(MedHead[NumMedHeads].DataOrgID, "ANAND", 6)) { SWAP16(MedHead[NumMedHeads].NumEraseUnits); SWAP16(MedHead[NumMedHeads].FirstPhysicalEUN); SWAP32(MedHead[NumMedHeads].FormattedSize); if (NumMedHeads == 0) { printf("NFTL Media Header found at offset 0x%08lx:\n", ofs); printf("NumEraseUnits: %d\n", MedHead[NumMedHeads].NumEraseUnits); printf("FirstPhysicalEUN: %d\n", MedHead[NumMedHeads].FirstPhysicalEUN); printf("Formatted Size: %d\n", MedHead[NumMedHeads].FormattedSize); printf("UnitSizeFactor: 0x%x\n", MedHead[NumMedHeads].UnitSizeFactor); /* read BadUnitTable, I don't know why pread() does not work for larger (7680 bytes) chunks */ for (i = 0; i < MAX_ERASE_ZONES; i += 512) pread(fd, &BadUnitTable[i], 512, ofs + 512 + i); } else printf("Second NFTL Media Header found at offset 0x%08lx\n",ofs); NumMedHeads++; } ofs += meminfo.erasesize; if (NumMedHeads == 2) { if (strncmp((char *)&MedHead[0], (char *)&MedHead[1], sizeof(struct NFTLMediaHeader)) != 0) { printf("warning: NFTL Media Header is not consistent with " "Spare NFTL Media Header\n"); } break; } } /* allocate Virtual Unit Chain table for this NFTL partition */ VUCtable = calloc(MedHead[0].NumEraseUnits, sizeof(unsigned short)); return NumMedHeads; } static void dump_erase_units(void) { int i, j; unsigned long ofs; for (i = MedHead[0].FirstPhysicalEUN; i < MedHead[0].FirstPhysicalEUN + MedHead[0].NumEraseUnits; i++) { /* For each Erase Unit */ ofs = i * meminfo.erasesize; /* read the Unit Control Information */ for (j = 0; j < 3; j++) { oob.start = ofs + (j * 512); if (ioctl(fd, MEMREADOOB, &oob)) printf("MEMREADOOB at %lx: %s\n", (unsigned long) oob.start, strerror(errno)); memcpy(&UCItable[i][j], &oobbuf.u, 8); } if (UCItable[i][1].b.EraseMark != cpu_to_le16(0x3c69)) { printf("EraseMark not present in unit %d: %x\n", i, UCItable[i][1].b.EraseMark); } else { /* a properly formatted unit */ SWAP16(UCItable[i][0].a.VirtUnitNum); SWAP16(UCItable[i][0].a.ReplUnitNum); SWAP16(UCItable[i][0].a.SpareVirtUnitNum); SWAP16(UCItable[i][0].a.SpareReplUnitNum); SWAP32(UCItable[i][1].b.WearInfo); SWAP16(UCItable[i][1].b.EraseMark); SWAP16(UCItable[i][1].b.EraseMark1); SWAP16(UCItable[i][2].c.FoldMark); SWAP16(UCItable[i][2].c.FoldMark1); if (!(UCItable[i][0].a.VirtUnitNum & 0x8000)) { /* If this is the first in a chain, store the EUN in the VUC table */ if (VUCtable[UCItable[i][0].a.VirtUnitNum & 0x7fff]) { printf("Duplicate start of chain for VUC %d: " "Unit %d replaces Unit %d\n", UCItable[i][0].a.VirtUnitNum & 0x7fff, i, VUCtable[UCItable[i][0].a.VirtUnitNum & 0x7fff]); } VUCtable[UCItable[i][0].a.VirtUnitNum & 0x7fff] = i; } } switch (BadUnitTable[i]) { case ZONE_BAD_ORIGINAL: printf("Unit %d is marked as ZONE_BAD_ORIGINAL\n", i); continue; case ZONE_BAD_MARKED: printf("Unit %d is marked as ZONE_BAD_MARKED\n", i); continue; } /* ZONE_GOOD */ if (UCItable[i][0].a.VirtUnitNum == 0xffff) printf("Unit %d is free\n", i); else printf("Unit %d is in chain %d and %s a replacement\n", i, UCItable[i][0].a.VirtUnitNum & 0x7fff, UCItable[i][0].a.VirtUnitNum & 0x8000 ? "is" : "is not"); } } static void dump_virtual_units(void) { int i, j; char readbuf[512]; for (i = 0; i < (MedHead[0].FormattedSize / meminfo.erasesize); i++) { unsigned short curEUN = VUCtable[i]; printf("Virtual Unit #%d: ", i); if (!curEUN) { printf("Not present\n"); continue; } printf("%d", curEUN); /* walk through the Virtual Unit Chain */ while ((curEUN = nextEUN(curEUN)) != 0xffff) { printf(", %d", curEUN & 0x7fff); } printf("\n"); if (ofd != -1) { /* Actually write out the data */ for (j = 0; j < meminfo.erasesize / 512; j++) { /* For each sector in the block */ unsigned short lastgoodEUN = 0xffff, thisEUN = VUCtable[i]; unsigned int status; if (thisEUN == 0xffff) thisEUN = 0; while (thisEUN && (thisEUN & 0x7fff) != 0x7fff) { oob.start = (thisEUN * ERASESIZE) + (j * 512); ioctl(fd, MEMREADOOB, &oob); status = oobbuf.b.Status | oobbuf.b.Status1; switch (status) { case SECTOR_FREE: /* This is still free. Don't look any more */ thisEUN = 0; break; case SECTOR_USED: /* SECTOR_USED. This is a good one. */ lastgoodEUN = thisEUN; break; } /* Find the next erase unit in this chain, if any */ if (thisEUN) thisEUN = nextEUN(thisEUN) & 0x7fff; } if (lastgoodEUN == 0xffff) memset(readbuf, 0, 512); else pread(fd, readbuf, 512, (lastgoodEUN * ERASESIZE) + (j * 512)); write(ofd, readbuf, 512); } } } } int main(int argc, char **argv) { if (argc < 2) { printf("Usage: %s []\n", PROGRAM_NAME); exit(1); } fd = open(argv[1], O_RDONLY); if (fd == -1) { perror("open flash"); exit (1); } if (argc > 2) { ofd = open(argv[2], O_WRONLY | O_TRUNC | O_CREAT, 0644); if (ofd == -1) perror ("open outfile"); } /* get size information of the MTD device */ if (ioctl(fd, MEMGETINFO, &meminfo) != 0) { perror("ioctl(MEMGETINFO)"); close(fd); return 1; } while (find_media_headers() != 0) { dump_erase_units(); dump_virtual_units(); free(VUCtable); } exit(0); }