| Line No. | Rev | Author | Line |
|---|---|---|---|
| 1 | 6 | kaklik | /*! \file fat.c \brief FAT16/32 file system driver. */ |
| 2 | //***************************************************************************** |
||
| 3 | // |
||
| 4 | // File Name : 'fat.c' |
||
| 5 | // Title : FAT16/32 file system driver |
||
| 6 | // Author : Pascal Stang |
||
| 7 | // Date : 11/07/2000 |
||
| 8 | // Revised : 12/12/2000 |
||
| 9 | // Version : 0.3 |
||
| 10 | // Target MCU : ATmega103 (should work for Atmel AVR Series) |
||
| 11 | // Editor Tabs : 4 |
||
| 12 | // |
||
| 13 | // This code is based in part on work done by Jesper Hansen for his |
||
| 14 | // YAMPP MP3 player project. |
||
| 15 | // |
||
| 16 | // NOTE: This code is currently below version 1.0, and therefore is considered |
||
| 17 | // to be lacking in some functionality or documentation, or may not be fully |
||
| 18 | // tested. Nonetheless, you can expect most functions to work. |
||
| 19 | // |
||
| 20 | // This code is distributed under the GNU Public License |
||
| 21 | // which can be found at http://www.gnu.org/licenses/gpl.txt |
||
| 22 | // |
||
| 23 | //***************************************************************************** |
||
| 24 | |||
| 25 | |||
| 26 | #include <avr/io.h> |
||
| 27 | #include <avr/pgmspace.h> |
||
| 28 | #include <string.h> |
||
| 29 | |||
| 30 | #include "ata.h" |
||
| 31 | #include "rprintf.h" |
||
| 32 | #include "debug.h" |
||
| 33 | |||
| 34 | #include "fat.h" |
||
| 35 | #include "fatconf.h" |
||
| 36 | |||
| 37 | // globals |
||
| 38 | // buffers |
||
| 39 | unsigned char *SectorBuffer = (unsigned char *) FAT_SECTOR_BUFFER_ADDR; |
||
| 40 | unsigned char *FileNameBuffer = (unsigned char *) FAT_FILENAME_BUFFER_ADDR; |
||
| 41 | unsigned char *PathNameBuffer = (unsigned char *) FAT_PATHNAME_BUFFER_ADDR; |
||
| 42 | |||
| 43 | // filesystem constants/metrics |
||
| 44 | struct partrecord PartInfo; |
||
| 45 | unsigned char Fat32Enabled; |
||
| 46 | unsigned long FirstDataSector; |
||
| 47 | unsigned short BytesPerSector; |
||
| 48 | unsigned short SectorsPerCluster; |
||
| 49 | unsigned long FirstFATSector; |
||
| 50 | unsigned long RootDirStartCluster; |
||
| 51 | |||
| 52 | // operating variables |
||
| 53 | unsigned long CurrentDirStartCluster; //< current directory starting cluster |
||
| 54 | struct FileInfoStruct FileInfo; //< file information for last file accessed |
||
| 55 | unsigned long FatInCache = 0; |
||
| 56 | |||
| 57 | |||
| 58 | /*************************************************************************/ |
||
| 59 | /*************************************************************************/ |
||
| 60 | |||
| 61 | |||
| 62 | unsigned long fatClustToSect(unsigned long clust) |
||
| 63 | { |
||
| 64 | return ((clust-2) * SectorsPerCluster) + FirstDataSector; |
||
| 65 | } |
||
| 66 | |||
| 67 | unsigned int fatClusterSize(void) |
||
| 68 | { |
||
| 69 | // return the number of sectors in a disk cluster |
||
| 70 | return SectorsPerCluster; |
||
| 71 | } |
||
| 72 | |||
| 73 | unsigned char fatInit( unsigned char device) |
||
| 74 | { |
||
| 75 | //struct partrecord *pr; |
||
| 76 | struct bpb710 *bpb; |
||
| 77 | |||
| 78 | // read partition table |
||
| 79 | // TODO.... error checking |
||
| 80 | ataReadSectors(DRIVE0, 0, 1, SectorBuffer); |
||
| 81 | // map first partition record |
||
| 82 | // save partition information to global PartInfo |
||
| 83 | PartInfo = *((struct partrecord *) ((struct partsector *) SectorBuffer)->psPart); |
||
| 84 | // PartInfo = *pr; |
||
| 85 | |||
| 86 | // Read the Partition BootSector |
||
| 87 | // **first sector of partition in PartInfo.prStartLBA |
||
| 88 | ataReadSectors( DRIVE0, PartInfo.prStartLBA, 1, SectorBuffer ); |
||
| 89 | bpb = (struct bpb710 *) ((struct bootsector710 *) SectorBuffer)->bsBPB; |
||
| 90 | |||
| 91 | // setup global disk constants |
||
| 92 | FirstDataSector = PartInfo.prStartLBA; |
||
| 93 | if(bpb->bpbFATsecs) |
||
| 94 | { |
||
| 95 | // bpbFATsecs is non-zero and is therefore valid |
||
| 96 | FirstDataSector += bpb->bpbResSectors + bpb->bpbFATs * bpb->bpbFATsecs; |
||
| 97 | } |
||
| 98 | else |
||
| 99 | { |
||
| 100 | // bpbFATsecs is zero, real value is in bpbBigFATsecs |
||
| 101 | FirstDataSector += bpb->bpbResSectors + bpb->bpbFATs * bpb->bpbBigFATsecs; |
||
| 102 | } |
||
| 103 | SectorsPerCluster = bpb->bpbSecPerClust; |
||
| 104 | BytesPerSector = bpb->bpbBytesPerSec; |
||
| 105 | FirstFATSector = bpb->bpbResSectors + PartInfo.prStartLBA; |
||
| 106 | |||
| 107 | switch (PartInfo.prPartType) |
||
| 108 | { |
||
| 109 | case PART_TYPE_DOSFAT16: |
||
| 110 | case PART_TYPE_FAT16: |
||
| 111 | case PART_TYPE_FAT16LBA: |
||
| 112 | // first directory cluster is 2 by default (clusters range 2->big) |
||
| 113 | RootDirStartCluster = CLUST_FIRST; |
||
| 114 | // push data sector pointer to end of root directory area |
||
| 115 | //FirstDataSector += (bpb->bpbRootDirEnts)/DIRENTRIES_PER_SECTOR; |
||
| 116 | Fat32Enabled = FALSE; |
||
| 117 | break; |
||
| 118 | case PART_TYPE_FAT32LBA: |
||
| 119 | case PART_TYPE_FAT32: |
||
| 120 | // bpbRootClust field exists in FAT32 bpb710, but not in lesser bpb's |
||
| 121 | RootDirStartCluster = bpb->bpbRootClust; |
||
| 122 | // push data sector pointer to end of root directory area |
||
| 123 | // need this? FirstDataSector += (bpb->bpbRootDirEnts)/DIRENTRIES_PER_SECTOR; |
||
| 124 | Fat32Enabled = TRUE; |
||
| 125 | break; |
||
| 126 | default: |
||
| 127 | rprintfProgStrM("Found: No Partition!\r\n"); |
||
| 128 | //return 1; |
||
| 129 | break; |
||
| 130 | } |
||
| 131 | |||
| 132 | // set current directory to root (\) |
||
| 133 | CurrentDirStartCluster = RootDirStartCluster; |
||
| 134 | PathNameBuffer[0] = '\\'; |
||
| 135 | PathNameBuffer[1] = 0; |
||
| 136 | |||
| 137 | |||
| 138 | // do debug |
||
| 139 | #ifdef DEBUG_FAT |
||
| 140 | switch (PartInfo.prPartType) |
||
| 141 | { |
||
| 142 | case PART_TYPE_DOSFAT16: |
||
| 143 | rprintfProgStrM("Found: DOSFAT 16\r\n"); |
||
| 144 | break; |
||
| 145 | case PART_TYPE_FAT16: |
||
| 146 | rprintfProgStrM("Found: FAT16\r\n"); |
||
| 147 | break; |
||
| 148 | case PART_TYPE_FAT16LBA: |
||
| 149 | rprintfProgStrM("Found: FAT16 LBA\r\n"); |
||
| 150 | break; |
||
| 151 | case PART_TYPE_FAT32LBA: |
||
| 152 | rprintfProgStrM("Found: FAT32 LBA\r\n"); |
||
| 153 | break; |
||
| 154 | case PART_TYPE_FAT32: |
||
| 155 | rprintfProgStrM("Found: FAT32\r\n"); |
||
| 156 | //return 1; |
||
| 157 | break; |
||
| 158 | default: |
||
| 159 | rprintfProgStrM("Found: No Partition!\r\n"); |
||
| 160 | //return 1; |
||
| 161 | break; |
||
| 162 | } |
||
| 163 | |||
| 164 | rprintfProgStrM("First sector : "); rprintfu32(PartInfo.prStartLBA); rprintfCRLF(); |
||
| 165 | rprintfProgStrM("Size : "); rprintfu32(PartInfo.prSize); rprintfCRLF(); |
||
| 166 | rprintfProgStrM("bytes/sector : "); rprintfu16(bpb->bpbBytesPerSec); rprintfCRLF(); |
||
| 167 | rprintfProgStrM("sectors/cluster : "); rprintfu08(bpb->bpbSecPerClust); rprintfCRLF(); |
||
| 168 | rprintfProgStrM("reserved sectors: "); rprintfu16(bpb->bpbResSectors); rprintfCRLF(); |
||
| 169 | rprintfProgStrM("FatSectors : "); rprintfu16(bpb->bpbFATsecs); rprintfCRLF(); |
||
| 170 | rprintfProgStrM("BigFatSectors : "); rprintfu32(bpb->bpbBigFATsecs); rprintfCRLF(); |
||
| 171 | rprintfProgStrM("Number of Fats : "); rprintfu08(bpb->bpbFATs); rprintfCRLF(); |
||
| 172 | rprintfProgStrM("First Fat Sector: "); rprintfu32(FirstFATSector); rprintfCRLF(); |
||
| 173 | rprintfProgStrM("First Data Sect : "); rprintfu32(FirstDataSector); rprintfCRLF(); |
||
| 174 | rprintfProgStrM("RootDirStartClus: "); rprintfu32(RootDirStartCluster); rprintfCRLF(); |
||
| 175 | #endif |
||
| 176 | |||
| 177 | return 0; |
||
| 178 | } |
||
| 179 | |||
| 180 | ////////////////////////////////////////////////////////////// |
||
| 181 | |||
| 182 | unsigned char fatGetDirEntry(unsigned short entry) |
||
| 183 | { |
||
| 184 | unsigned long sector; |
||
| 185 | struct direntry *de = 0; // avoid compiler warning by initializing |
||
| 186 | struct winentry *we; |
||
| 187 | unsigned char haveLongNameEntry; |
||
| 188 | unsigned char gotEntry; |
||
| 189 | unsigned short b; |
||
| 190 | int i,index; |
||
| 191 | char *fnbPtr; |
||
| 192 | unsigned short entrycount = 0; |
||
| 193 | |||
| 194 | // read dir data |
||
| 195 | sector = fatClustToSect(CurrentDirStartCluster); |
||
| 196 | |||
| 197 | haveLongNameEntry = 0; |
||
| 198 | gotEntry = 0; |
||
| 199 | |||
| 200 | index = 16; // crank it up |
||
| 201 | |||
| 202 | //while(entrycount < entry) |
||
| 203 | while(1) |
||
| 204 | { |
||
| 205 | if(index == 16) // time for next sector ? |
||
| 206 | { |
||
| 207 | ataReadSectors( DRIVE0, sector++, 1, SectorBuffer); |
||
| 208 | de = (struct direntry *) SectorBuffer; |
||
| 209 | index = 0; |
||
| 210 | } |
||
| 211 | |||
| 212 | // check the status of this directory entry slot |
||
| 213 | if(de->deName[0] == 0x00) |
||
| 214 | { |
||
| 215 | // slot is empty and this is the end of directory |
||
| 216 | gotEntry = 0; |
||
| 217 | break; |
||
| 218 | } |
||
| 219 | else if(de->deName[0] == 0xE5) |
||
| 220 | { |
||
| 221 | // this is an empty slot |
||
| 222 | // do nothing and move to the next one |
||
| 223 | } |
||
| 224 | else |
||
| 225 | { |
||
| 226 | // this is a valid and occupied entry |
||
| 227 | // is it a part of a long file/dir name? |
||
| 228 | if(de->deAttributes == ATTR_LONG_FILENAME) |
||
| 229 | { |
||
| 230 | // we have a long name entry |
||
| 231 | // cast this directory entry as a "windows" (LFN: LongFileName) entry |
||
| 232 | we = (struct winentry *) de; |
||
| 233 | |||
| 234 | b = WIN_ENTRY_CHARS*( (we->weCnt-1) & 0x0f); // index into string |
||
| 235 | fnbPtr = &FileNameBuffer[b]; |
||
| 236 | for (i=0;i<5;i++) *fnbPtr++ = we->wePart1[i*2]; // copy first part |
||
| 237 | for (i=0;i<6;i++) *fnbPtr++ = we->wePart2[i*2]; // second part |
||
| 238 | for (i=0;i<2;i++) *fnbPtr++ = we->wePart3[i*2]; // and third part |
||
| 239 | if (we->weCnt & WIN_LAST) *fnbPtr = 0; // in case dirnamelength is multiple of 13, add termination |
||
| 240 | if ((we->weCnt & 0x0f) == 1) haveLongNameEntry = 1; // flag that we have a complete long name entry set |
||
| 241 | } |
||
| 242 | else |
||
| 243 | { |
||
| 244 | // we have a short name entry |
||
| 245 | |||
| 246 | // check if this is the short name entry corresponding |
||
| 247 | // to the end of a multi-part long name entry |
||
| 248 | if(haveLongNameEntry) |
||
| 249 | { |
||
| 250 | // a long entry name has been collected |
||
| 251 | if(entrycount == entry) |
||
| 252 | { |
||
| 253 | // desired entry has been found, break out |
||
| 254 | gotEntry = 1; |
||
| 255 | break; |
||
| 256 | } |
||
| 257 | // otherwise |
||
| 258 | haveLongNameEntry = 0; // clear long name flag |
||
| 259 | entrycount++; // increment entry counter |
||
| 260 | } |
||
| 261 | else |
||
| 262 | { |
||
| 263 | // entry is a short name (8.3 format) without a |
||
| 264 | // corresponding multi-part long name entry |
||
| 265 | fnbPtr = FileNameBuffer; |
||
| 266 | for (i=0;i<8;i++) *fnbPtr++ = de->deName[i]; // copy name |
||
| 267 | *fnbPtr++ = '.'; // insert '.' |
||
| 268 | for (i=0;i<3;i++) *fnbPtr++ = de->deExtension[i]; // copy extension |
||
| 269 | *fnbPtr = 0; // null-terminate |
||
| 270 | |||
| 271 | if(entrycount == entry) |
||
| 272 | { |
||
| 273 | // desired entry has been found, break out |
||
| 274 | gotEntry = 1; |
||
| 275 | break; |
||
| 276 | } |
||
| 277 | // otherwise |
||
| 278 | entrycount++; // increment entry counter |
||
| 279 | } |
||
| 280 | } |
||
| 281 | } |
||
| 282 | // next directory entry |
||
| 283 | de++; |
||
| 284 | // next index |
||
| 285 | index++; |
||
| 286 | } |
||
| 287 | |||
| 288 | // we have a file/dir to return |
||
| 289 | // store file/dir starting cluster (start of data) |
||
| 290 | FileInfo.StartCluster = (unsigned long) ((unsigned long)de->deHighClust << 16) + de->deStartCluster; |
||
| 291 | // store file/dir size |
||
| 292 | // (note: size field for subdirectory entries is always zero) |
||
| 293 | FileInfo.Size = de->deFileSize; |
||
| 294 | // store file/dir attributes |
||
| 295 | FileInfo.Attr = de->deAttributes; |
||
| 296 | // store file/dir creation time |
||
| 297 | FileInfo.CreateTime = de->deCTime[0] | de->deCTime[1]<<8; |
||
| 298 | // store file/dir creation date |
||
| 299 | FileInfo.CreateTime = de->deCDate[0] | de->deCDate[1]<<8; |
||
| 300 | |||
| 301 | return gotEntry; |
||
| 302 | } |
||
| 303 | |||
| 304 | // change directory into |
||
| 305 | unsigned char fatChangeDirectory(unsigned short entry) |
||
| 306 | { |
||
| 307 | // get the requested directory entry |
||
| 308 | if( fatGetDirEntry(entry) ) |
||
| 309 | { |
||
| 310 | // make sure the entry is a directory |
||
| 311 | if(FileInfo.Attr & ATTR_DIRECTORY) |
||
| 312 | { |
||
| 313 | // change directories into this directory |
||
| 314 | // check to see if we are changing back to root directory |
||
| 315 | if(FileInfo.StartCluster) |
||
| 316 | { |
||
| 317 | // standard change directory |
||
| 318 | CurrentDirStartCluster = FileInfo.StartCluster; |
||
| 319 | } |
||
| 320 | else |
||
| 321 | { |
||
| 322 | // if startCluster pointer is zero, |
||
| 323 | // a change to the root directory is intended |
||
| 324 | // change directory to root |
||
| 325 | CurrentDirStartCluster = RootDirStartCluster; |
||
| 326 | } |
||
| 327 | // TODO: handle pathname properly for going up a directory |
||
| 328 | // set path string |
||
| 329 | strcat(PathNameBuffer, FileNameBuffer); |
||
| 330 | strcat(PathNameBuffer, "\\"); |
||
| 331 | // return success |
||
| 332 | return TRUE; |
||
| 333 | } |
||
| 334 | else |
||
| 335 | { |
||
| 336 | // not a directory, cannot CD into a file! |
||
| 337 | return FALSE; |
||
| 338 | } |
||
| 339 | } |
||
| 340 | else |
||
| 341 | { |
||
| 342 | // not a valid entry, cannot CD! |
||
| 343 | return FALSE; |
||
| 344 | } |
||
| 345 | } |
||
| 346 | |||
| 347 | void fatPrintDirEntry(void) |
||
| 348 | { |
||
| 349 | // print a formatted dir-style output for most recent file |
||
| 350 | // print date |
||
| 351 | rprintfNum(10, 2, FALSE, '0', (FileInfo.CreateDate&DD_MONTH_MASK)>>DD_MONTH_SHIFT ); // month |
||
| 352 | rprintfChar('/'); |
||
| 353 | rprintfNum(10, 2, FALSE, '0', (FileInfo.CreateDate&DD_DAY_MASK)>>DD_DAY_SHIFT ); // day |
||
| 354 | rprintfChar('/'); |
||
| 355 | rprintfNum(10, 4, FALSE, '0', (FileInfo.CreateDate&DD_YEAR_MASK)>>DD_YEAR_SHIFT ); // year |
||
| 356 | rprintfChar(' '); |
||
| 357 | |||
| 358 | // print time |
||
| 359 | rprintfNum(10, 2, FALSE, '0', (FileInfo.CreateTime&DT_HOURS_MASK)>>DT_HOURS_SHIFT ); // month |
||
| 360 | rprintfChar(':'); |
||
| 361 | rprintfNum(10, 2, FALSE, '0', (FileInfo.CreateTime&DT_MINUTES_MASK)>>DT_MINUTES_SHIFT ); // day |
||
| 362 | rprintfChar(':'); |
||
| 363 | rprintfNum(10, 2, FALSE, '0', 2*(FileInfo.CreateTime&DT_2SECONDS_MASK)>>DT_2SECONDS_SHIFT ); // seconds |
||
| 364 | rprintfChar(' '); |
||
| 365 | |||
| 366 | // print attributes |
||
| 367 | if(FileInfo.Attr & ATTR_VOLUME) rprintfChar('V'); else rprintfChar('-'); |
||
| 368 | if(FileInfo.Attr & ATTR_DIRECTORY) rprintfChar('D'); else rprintfChar('-'); |
||
| 369 | if(FileInfo.Attr & ATTR_READONLY) rprintfChar('R'); else rprintfChar('-'); |
||
| 370 | if(FileInfo.Attr & ATTR_HIDDEN) rprintfChar('H'); else rprintfChar('-'); |
||
| 371 | if(FileInfo.Attr & ATTR_SYSTEM) rprintfChar('S'); else rprintfChar('-'); |
||
| 372 | if(FileInfo.Attr & ATTR_ARCHIVE) rprintfChar('A'); else rprintfChar('-'); |
||
| 373 | rprintfChar(' '); |
||
| 374 | |||
| 375 | // print filesize |
||
| 376 | rprintfNum(10, 8, FALSE, ' ', FileInfo.Size); // filesize |
||
| 377 | rprintfChar(' '); |
||
| 378 | |||
| 379 | // print filename |
||
| 380 | rprintfStr(FileNameBuffer); |
||
| 381 | } |
||
| 382 | |||
| 383 | void fatDumpDirSlot(unsigned short slot) |
||
| 384 | { |
||
| 385 | unsigned long sector; |
||
| 386 | // load correct sector |
||
| 387 | sector = fatClustToSect(CurrentDirStartCluster); |
||
| 388 | sector += slot/DIRENTRIES_PER_SECTOR; |
||
| 389 | // print the entry as a hex table |
||
| 390 | debugPrintHexTable(32, SectorBuffer+(slot<<5) ); |
||
| 391 | } |
||
| 392 | |||
| 393 | struct FileInfoStruct* fatGetFileInfo(void) |
||
| 394 | { |
||
| 395 | return &FileInfo; |
||
| 396 | } |
||
| 397 | |||
| 398 | // return the size of the last directory entry |
||
| 399 | unsigned long fatGetFilesize(void) |
||
| 400 | { |
||
| 401 | return FileInfo.Size; |
||
| 402 | } |
||
| 403 | |||
| 404 | // return the long name of the last directory entry |
||
| 405 | char* fatGetFilename(void) |
||
| 406 | { |
||
| 407 | return FileNameBuffer; |
||
| 408 | } |
||
| 409 | |||
| 410 | // return the directory of the last directory entry |
||
| 411 | char* fatGetDirname(void) |
||
| 412 | { |
||
| 413 | return PathNameBuffer; |
||
| 414 | } |
||
| 415 | |||
| 416 | // load a clusterfull of data |
||
| 417 | void fatLoadCluster(unsigned long cluster, unsigned char *buffer) |
||
| 418 | { |
||
| 419 | register unsigned char i; |
||
| 420 | // read cluster |
||
| 421 | //while ( ataReadSectors( DRIVE0, clust2sect(cluster), SectorsPerCluster, buffer) != 0); |
||
| 422 | for(i=0; i<SectorsPerCluster; i++) |
||
| 423 | { |
||
| 424 | ataReadSectors( DRIVE0, fatClustToSect(cluster)+i, 1, buffer+(i<<9) ); |
||
| 425 | // temporary fix for wierd misaligned cluster problem |
||
| 426 | // (only when using FAT16?) |
||
| 427 | // ataReadSectors( DRIVE0, fatClustToSect(cluster+8)+i, 1, buffer+(i<<9) ); |
||
| 428 | } |
||
| 429 | } |
||
| 430 | |||
| 431 | |||
| 432 | // find next cluster in the FAT chain |
||
| 433 | unsigned long fatNextCluster(unsigned long cluster) |
||
| 434 | { |
||
| 435 | unsigned long nextCluster; |
||
| 436 | unsigned long fatMask; |
||
| 437 | unsigned long fatOffset; |
||
| 438 | unsigned long sector; |
||
| 439 | unsigned int offset; |
||
| 440 | |||
| 441 | // get fat offset in bytes |
||
| 442 | if(Fat32Enabled) |
||
| 443 | { |
||
| 444 | // four FAT bytes (32 bits) for every cluster |
||
| 445 | fatOffset = cluster << 2; |
||
| 446 | // set the FAT bit mask |
||
| 447 | fatMask = FAT32_MASK; |
||
| 448 | } |
||
| 449 | else |
||
| 450 | { |
||
| 451 | // two FAT bytes (16 bits) for every cluster |
||
| 452 | fatOffset = cluster << 1; |
||
| 453 | // set the FAT bit mask |
||
| 454 | fatMask = FAT16_MASK; |
||
| 455 | } |
||
| 456 | |||
| 457 | // calculate the FAT sector that we're interested in |
||
| 458 | sector = FirstFATSector + (fatOffset / BytesPerSector); |
||
| 459 | // calculate offset of the our entry within that FAT sector |
||
| 460 | offset = fatOffset % BytesPerSector; |
||
| 461 | |||
| 462 | // if we don't already have this FAT chunk loaded, go get it |
||
| 463 | if (sector != FatInCache) |
||
| 464 | { |
||
| 465 | // read sector of FAT table |
||
| 466 | while (ataReadSectors( DRIVE0, sector, 1, (unsigned char*)FAT_CACHE_ADDR) != 0); |
||
| 467 | FatInCache = sector; |
||
| 468 | } |
||
| 469 | |||
| 470 | // read the nextCluster value |
||
| 471 | nextCluster = (*((unsigned long*) &((char*)FAT_CACHE_ADDR)[offset])) & fatMask; |
||
| 472 | |||
| 473 | // check to see if we're at the end of the chain |
||
| 474 | if (nextCluster == (CLUST_EOFE & fatMask)) |
||
| 475 | nextCluster = 0; |
||
| 476 | |||
| 477 | #ifdef DEBUG_FAT |
||
| 478 | rprintfProgStrM(">"); |
||
| 479 | rprintfu32(nextCluster); |
||
| 480 | rprintfCRLF(); |
||
| 481 | #endif |
||
| 482 | |||
| 483 | return nextCluster; |
||
| 484 | } |
Powered by WebSVN v2.8.3