FreeCalypso > hg > tcs211-fcmodem
comparison chipsetsw/drivers/drv_app/ffs/board/fsck.c @ 0:509db1a7b7b8
initial import: leo2moko-r1
| author | Space Falcon <falcon@ivan.Harhan.ORG> |
|---|---|
| date | Mon, 01 Jun 2015 03:24:05 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:509db1a7b7b8 |
|---|---|
| 1 /****************************************************************************** | |
| 2 * Flash File System (ffs) | |
| 3 * Idea, design and coding by Mads Meisner-Jensen, mmj@ti.com | |
| 4 * | |
| 5 * FFS file system integrity checking, journalling, init and exit | |
| 6 * | |
| 7 * $Id: fsck.c 1.3.1.1.1.33 Thu, 08 Jan 2004 15:05:23 +0100 tsj $ | |
| 8 * | |
| 9 ******************************************************************************/ | |
| 10 | |
| 11 #ifndef TARGET | |
| 12 #include "ffs.cfg" | |
| 13 #endif | |
| 14 | |
| 15 #include <string.h> | |
| 16 #include <assert.h> | |
| 17 | |
| 18 #include "ffs/ffs.h" | |
| 19 #include "ffs/board/core.h" | |
| 20 #include "ffs/board/drv.h" | |
| 21 #include "ffs/board/ffstrace.h" | |
| 22 | |
| 23 /****************************************************************************** | |
| 24 * Functions | |
| 25 ******************************************************************************/ | |
| 26 | |
| 27 bref_t blocks_fsck(void); | |
| 28 iref_t inodes_fsck(void); | |
| 29 | |
| 30 /****************************************************************************** | |
| 31 * Init and Exit | |
| 32 ******************************************************************************/ | |
| 33 | |
| 34 effs_t ffs_initialize(void) | |
| 35 { | |
| 36 bref_t b; | |
| 37 struct inode_s *ip; | |
| 38 int i; | |
| 39 | |
| 40 tlw(led_set(0)); | |
| 41 tlw(led_on(LED_INIT)); | |
| 42 ttw(str(TTrInit, "initialize {" NL)); | |
| 43 tw(tr(TR_BEGIN, TrFsck, "ffs_initialize() {\n")); | |
| 44 | |
| 45 // default to non-initialized ffs | |
| 46 fs.root = 0; | |
| 47 fs.debug[0] = fs.debug[1] = fs.debug[2] = fs.debug[3] = 0; | |
| 48 fs.testflags = 0; | |
| 49 | |
| 50 tlw(led_on(LED_DRV_INIT)); | |
| 51 fs.initerror = ffsdrv_init(); // read manufacturer and device ID | |
| 52 tlw(led_off(LED_DRV_INIT)); | |
| 53 if (fs.initerror < 0) { | |
| 54 tlw(led_off(0)); | |
| 55 tw(tr(TR_END, TrFsck, "} %d\n", fs.initerror)); | |
| 56 ttw(ttr(TTrInit, "} %d" NL, fs.initerror)); | |
| 57 return fs.initerror; | |
| 58 } | |
| 59 | |
| 60 for (i = 0; i < 2; i++) | |
| 61 { | |
| 62 tlw(led_on(LED_BLOCKS_FSCK)); | |
| 63 fs.initerror = EFFS_INVALID; | |
| 64 fs.initerror = b = blocks_fsck(); | |
| 65 tlw(led_off(LED_BLOCKS_FSCK)); | |
| 66 if (fs.initerror < 0) { | |
| 67 tlw(led_off(0)); | |
| 68 tw(tr(TR_END, TrFsck, "} %d\n", fs.initerror)); | |
| 69 ttw(ttr(TTrInit, "} %d" NL, fs.initerror)); | |
| 70 return fs.initerror; | |
| 71 } | |
| 72 | |
| 73 tlw(led_on(LED_INODES_FSCK)); | |
| 74 fs.initerror = EFFS_INVALID; | |
| 75 fs.initerror = inodes_fsck(); | |
| 76 tlw(led_off(LED_INODES_FSCK)); | |
| 77 if (fs.initerror < 0) { | |
| 78 tlw(led_off(0)); | |
| 79 tw(tr(TR_END, TrFsck, "} %d\n", fs.initerror)); | |
| 80 ttw(ttr(TTrInit, "} %d" NL, fs.initerror)); | |
| 81 return fs.initerror; | |
| 82 } | |
| 83 | |
| 84 // parse the fs options in the root inode's name | |
| 85 ip = inode_addr(fs.root); | |
| 86 fs_params_init(addr2name(offset2addr(location2offset(ip->location)))); | |
| 87 | |
| 88 if ((fs.initerror = journal_init(fs.ijournal)) == 0) | |
| 89 break; | |
| 90 } | |
| 91 | |
| 92 // Init all file_descriptors to zero | |
| 93 memset(fs.fd, 0, sizeof(struct file_descriptor_s) * fs.fd_max); | |
| 94 | |
| 95 // If blocks_fsck() found a block that needs cleaning, we do it, now | |
| 96 // that all the file system has been initialized. | |
| 97 if (b > 0) { | |
| 98 block_clean(b - 1); | |
| 99 block_free(b - 1); | |
| 100 } | |
| 101 | |
| 102 statistics_init(); | |
| 103 | |
| 104 // In target, we do this before entering the task event loop... | |
| 105 // Otherwise we would in some cases impose a long reboot delay if we did | |
| 106 // it here. If we test in target it is nessesary to call | |
| 107 // blocks_reclaim() anyway because we re-init ffs. | |
| 108 | |
| 109 #if (TARGET == 1) //NOTEME: can this be done in another/better way? | |
| 110 #if (WITH_TFFS == 1) | |
| 111 blocks_reclaim(); | |
| 112 #endif | |
| 113 #else | |
| 114 blocks_reclaim(); | |
| 115 #endif | |
| 116 tlw(led_off(LED_INIT)); | |
| 117 tw(tr(TR_END, TrFsck, "} %d\n", EFFS_OK)); | |
| 118 ttw(str(TTrInit, "} 0" NL)); | |
| 119 | |
| 120 return EFFS_OK; | |
| 121 } | |
| 122 | |
| 123 void fs_params_init(const char *p) | |
| 124 { | |
| 125 uint8 opt, digit; | |
| 126 uint32 n; | |
| 127 int numdatablocks; | |
| 128 | |
| 129 tw(tr(TR_BEGIN, TrFsck, "fsparams_init('%s') {\n", p)); | |
| 130 | |
| 131 // Compiled default values | |
| 132 fs.filename_max = FFS_FILENAME_MAX; | |
| 133 fs.path_depth_max = FFS_PATH_DEPTH_MAX; | |
| 134 fs.fd_max = FFS_FD_MAX; | |
| 135 fs.journal_size = FFS_JOURNAL_SIZE_IN256THS; | |
| 136 fs.flags = 0; | |
| 137 fs.testflags = 0; | |
| 138 | |
| 139 // Flag that it not has been changed by an input arg. | |
| 140 fs.block_files_max = 0; | |
| 141 | |
| 142 // The default lost bytes percentage of a block before it is reclaimed | |
| 143 // is approx. 90%. | |
| 144 fs.lost_threshold = (256 - 256/10); | |
| 145 | |
| 146 // If we only have two blocks, we cannot make any reclaims and thus we | |
| 147 // have a write-once FFS system. | |
| 148 fs.blocks_free_min = (dev.numblocks > 2 ? 1 : 0); | |
| 149 | |
| 150 // Don't count free and inodes blocks | |
| 151 numdatablocks = dev.numblocks - fs.blocks_free_min - 1; | |
| 152 | |
| 153 // Abselute max number of inodes. | |
| 154 fs.inodes_max = dev.blocksize / sizeof(struct inode_s); | |
| 155 if (fs.inodes_max > FFS_INODES_MAX) | |
| 156 fs.inodes_max = FFS_INODES_MAX; | |
| 157 | |
| 158 // MUST be true: objects_max <= inodes_max - block_files_max, this is do | |
| 159 // to the fact that we always need to have block_files_max number of | |
| 160 // inodes left when we run a data reclaim. | |
| 161 fs.objects_max = fs.inodes_max / 2; | |
| 162 | |
| 163 // Find a suitable chunk_size | |
| 164 if (dev.numblocks*dev.blocksize > 1024*1024) | |
| 165 fs.chunk_size_max = 8192; | |
| 166 else | |
| 167 fs.chunk_size_max = (2048 > (dev.blocksize / 8) | |
| 168 ? (dev.blocksize / 8) | |
| 169 : 2048); | |
| 170 fs.fd_buf_size = fs.chunk_size_max; | |
| 171 | |
| 172 fs.journal_size = fs.journal_size * dev.blocksize / 256; | |
| 173 if (fs.journal_size < FFS_JOURNAL_SIZE_MIN) | |
| 174 fs.journal_size = FFS_JOURNAL_SIZE_MIN; | |
| 175 | |
| 176 // Set it just below the same amount as entries in one journal file | |
| 177 fs.block_files_max = (fs.journal_size / sizeof(struct journal_s) | |
| 178 - FFS_JOURNAL_MARGIN - 2); | |
| 179 | |
| 180 // MUST be true: block_files_max < objects_max / 2. But if we want | |
| 181 // to reach objects_max must block_files_max >= objects_max / number | |
| 182 // of datablocks, however a big block_files_max require higher | |
| 183 // reserved_space. | |
| 184 if (fs.block_files_max > fs.objects_max / 2) | |
| 185 fs.block_files_max = fs.objects_max / 2 - 4; | |
| 186 | |
| 187 // Are we able to reach objects_max? If not then lower the number | |
| 188 if (fs.objects_max > numdatablocks * fs.block_files_max) | |
| 189 fs.objects_max = numdatablocks * fs.block_files_max + 10; | |
| 190 | |
| 191 // Absolute minimum is RESERVED_LOW the rest is 'workspace' which is | |
| 192 // needed to have a reasonable performance. | |
| 193 fs.reserved_space = dev.blocksize / 2 + | |
| 194 numdatablocks * dev.blocksize / 16 + RESERVED_LOW; | |
| 195 | |
| 196 // skip to first char following second slash in name | |
| 197 n = 0; | |
| 198 while (*p) { | |
| 199 if (*p++ == '/') { | |
| 200 n++; | |
| 201 if (n == 2) | |
| 202 break; | |
| 203 } | |
| 204 } | |
| 205 if (n == 2) { | |
| 206 // while still options to process... | |
| 207 while (*p) { | |
| 208 opt = *p++; // save option letter for later | |
| 209 // collect option value... | |
| 210 n = 0; | |
| 211 while ((digit = *p)) { | |
| 212 if (digit >= '0' && digit <= '9') { | |
| 213 n = 10 * n + digit - '0'; | |
| 214 p++; | |
| 215 } | |
| 216 else | |
| 217 break; | |
| 218 } | |
| 219 switch (opt) { | |
| 220 case 'b': dev.numblocks = n; break; | |
| 221 case 'm': fs.blocks_free_min = n; break; | |
| 222 case 'i': fs.inodes_max = n; break; | |
| 223 case 'o': fs.objects_max = n; break; | |
| 224 case 'n': fs.filename_max = n; break; | |
| 225 case 'f': fs.block_files_max = n; break; | |
| 226 case 'd': fs.fd_max = n; break; | |
| 227 case 's': fs.fd_buf_size = n; break; | |
| 228 case 't': fs.lost_threshold = n; break; | |
| 229 case 'z': fs.flags = n; break; | |
| 230 case 'j': fs.journal_size = n; break; | |
| 231 case 'c': fs.chunk_size_max = n; break; | |
| 232 case 'r': fs.reserved_space = n; break; | |
| 233 // d = &fs.path_depth_max; // really necessary? | |
| 234 default: | |
| 235 break; | |
| 236 } | |
| 237 } | |
| 238 } | |
| 239 | |
| 240 // Now recompute a few parameters based on adjusted values. | |
| 241 | |
| 242 // No journal file thuse no reserved space. | |
| 243 if (fs.journal_size == 0) { | |
| 244 fs.block_files_max = fs.objects_max / 2; | |
| 245 fs.reserved_space = 0; | |
| 246 fs.block_files_reserved = 0; | |
| 247 } | |
| 248 | |
| 249 else { | |
| 250 // If journal size is less than minimum must it have been changed by an | |
| 251 // input arg, recalculate. | |
| 252 if (fs.journal_size < FFS_JOURNAL_SIZE_MIN) | |
| 253 fs.journal_size = fs.journal_size * dev.blocksize / 256; | |
| 254 | |
| 255 if (fs.reserved_space < RESERVED_LOW) | |
| 256 fs.reserved_space = fs.reserved_space * dev.blocksize / 256; | |
| 257 | |
| 258 // Only one reserved is needed however we want a margin and set it to two | |
| 259 fs.block_files_reserved = 2; | |
| 260 } | |
| 261 | |
| 262 // Don't count free blocks, inode block, reserved space, block headers | |
| 263 // and the size of one filename. | |
| 264 fs.filesize_max = numdatablocks * dev.blocksize - fs.reserved_space - | |
| 265 numdatablocks * BHEADER_SIZE - FFS_FILENAME_MAX; | |
| 266 | |
| 267 // Furthermore don't count the overhead from each chunk (alignment) | |
| 268 fs.filesize_max -= ((fs.filesize_max / fs.chunk_size_max) * dev.atomsize | |
| 269 + dev.atomsize); | |
| 270 | |
| 271 // NOTEME: chunk_size_min is never used | |
| 272 fs.chunk_size_min = numdatablocks / fs.objects_max; | |
| 273 | |
| 274 tw(tr(TR_FUNC, TrFsck, "dev.numblocks = %d\n", dev.numblocks)); | |
| 275 tw(tr(TR_FUNC, TrFsck, "fs.blocks_free_min = %d\n", fs.blocks_free_min)); | |
| 276 tw(tr(TR_FUNC, TrFsck, "fs.inodes_max = %d\n", fs.inodes_max)); | |
| 277 tw(tr(TR_FUNC, TrFsck, "fs.objects_max = %d\n", fs.objects_max)); | |
| 278 tw(tr(TR_FUNC, TrFsck, "fs.block_files_max = %d\n", fs.block_files_max)); | |
| 279 tw(tr(TR_FUNC, TrFsck, "fs.block_files_reserved = %d\n", fs.block_files_reserved)); | |
| 280 tw(tr(TR_FUNC, TrFsck, "fs.chunk_size_max = %d\n", fs.chunk_size_max)); | |
| 281 tw(tr(TR_FUNC, TrFsck, "fs.filename_max = %d\n", fs.filename_max)); | |
| 282 tw(tr(TR_FUNC, TrFsck, "fs.lost_threshold = %d\n", fs.lost_threshold)); | |
| 283 tw(tr(TR_FUNC, TrFsck, "fs.path_depth_max = %d\n", fs.path_depth_max)); | |
| 284 tw(tr(TR_FUNC, TrFsck, "fs.journal_size = %d\n", fs.journal_size)); | |
| 285 tw(tr(TR_FUNC, TrFsck, "fs.reserved_space = %d\n", fs.reserved_space)); | |
| 286 tw(tr(TR_FUNC, TrFsck, "fs.fd_max = %d\n", fs.fd_max)); | |
| 287 tw(tr(TR_FUNC, TrFsck, "fs.fd_buf_size = 0x%02x\n", fs.fd_buf_size)); | |
| 288 tw(tr(TR_FUNC, TrFsck, "fs.flags = 0x%02x\n", fs.flags)); | |
| 289 tw(tr(TR_END, TrFsck, "}\n")); | |
| 290 } | |
| 291 | |
| 292 // TODO: Finish pending commits/writes. | |
| 293 effs_t ffs_exit(void) | |
| 294 { | |
| 295 tw(tr(TR_FUNC, TrFsck, "exit() 0\n")); | |
| 296 | |
| 297 | |
| 298 return EFFS_OK; | |
| 299 } | |
| 300 | |
| 301 #if 0 // Not used in this version | |
| 302 // Purely for core internal use; Read a file. | |
| 303 effs_t file_read_int(const char *path, void *src, int size) | |
| 304 { | |
| 305 if (fs.initerror != EFFS_OK) | |
| 306 return fs.initerror; | |
| 307 | |
| 308 return object_read(path, src, size, 0); | |
| 309 } | |
| 310 | |
| 311 // Purely for core internal use; Update a file. | |
| 312 effs_t file_update(const char *path, void *src, int size) | |
| 313 { | |
| 314 char *name; | |
| 315 iref_t i, dir; | |
| 316 | |
| 317 if (fs.initerror != EFFS_OK) | |
| 318 return fs.initerror; | |
| 319 | |
| 320 if ((i = object_lookup(path, &name, &dir)) < 0) | |
| 321 return i; | |
| 322 | |
| 323 journal_begin(i); | |
| 324 | |
| 325 if ((i = object_create(name, src, size, -dir)) < 0) | |
| 326 return i; | |
| 327 | |
| 328 journal_end(0); | |
| 329 | |
| 330 return EFFS_OK; | |
| 331 } | |
| 332 #endif | |
| 333 | |
| 334 /****************************************************************************** | |
| 335 * blocks_fsck() | |
| 336 ******************************************************************************/ | |
| 337 | |
| 338 blocksize_t block_used(bref_t b) | |
| 339 { | |
| 340 blocksize_t used; | |
| 341 uint32 *p, *q; | |
| 342 | |
| 343 tlw(led_toggle(LED_BLOCKS_FSCK)); | |
| 344 | |
| 345 // We search backwards through block to find the last used byte and | |
| 346 // thus the total number of used bytes. Note that this code depends | |
| 347 // on the fact that an erased flash location is 0xFF! | |
| 348 p = (uint32 *) offset2addr(dev.binfo[b].offset); | |
| 349 for (q = p + dev.blocksize/4 - 4; q > p; q -= 4) { | |
| 350 if ( ~(q[0] & q[1] & q[2] & q[3]) ) | |
| 351 break; | |
| 352 } | |
| 353 | |
| 354 if ( ~(q[0] & q[1] & q[2] & q[3]) ) | |
| 355 q += 4; | |
| 356 used = atomalign((char *) q - (char *) p); | |
| 357 | |
| 358 tw(tr(TR_FUNC, TrFsckLow, "ffs_block_used(%d) %d\n", b, used)); | |
| 359 | |
| 360 return used; | |
| 361 } | |
| 362 | |
| 363 | |
| 364 age_t age_distance(age_t x, age_t y) | |
| 365 { | |
| 366 age_t a = x - y; | |
| 367 | |
| 368 if (a > 0x8000) | |
| 369 a = -a; | |
| 370 | |
| 371 tw(tr(TR_FUNC, TrFsckLow, "age_distance(%d, %d) %d\n", x, y, a)); | |
| 372 | |
| 373 return a; | |
| 374 } | |
| 375 | |
| 376 // For each ffs block, we initialise the basic bstat array information, | |
| 377 // namely the number of used bytes. Also, we locate the inodes block and if | |
| 378 // a previous operation was interrupted by a powerfail, we clean it up. | |
| 379 // | |
| 380 // We return EFFS_OK if all is fine. If a positive integer is returned, it | |
| 381 // denotes a block that needs to be cleaned by block_clean() once FFS | |
| 382 // has been properly intialized (we actually return the block number + 1 | |
| 383 // because otherwise it would clash with EFFS_OK return code). If no inodes | |
| 384 // block is found or another error occurs, we return the error code. | |
| 385 bref_t blocks_fsck(void) | |
| 386 { | |
| 387 bref_t b, b_to_clean, b_inode_lost; | |
| 388 int age_valid; | |
| 389 age_t age_min, age_max, age_dist, age_dist_min, age_dist_max; | |
| 390 struct block_header_s *bhp; | |
| 391 struct block_header_old_s *obhp; | |
| 392 | |
| 393 ttw(str(TTrInitLow, "blocks_fsck {" NL)); | |
| 394 tw(tr(TR_BEGIN, TrFsck, "blocks_fsck() {\n")); | |
| 395 | |
| 396 // initialize ages to the illegal/unset value | |
| 397 age_min = age_max = age_dist = 0; | |
| 398 | |
| 399 fs.format = 0; | |
| 400 fs.inodes = -1; | |
| 401 fs.newinodes = -1; | |
| 402 b_inode_lost = -1; | |
| 403 b_to_clean = EFFS_OK; | |
| 404 | |
| 405 for (b = 0; b < dev.numblocks; b++) | |
| 406 { | |
| 407 tlw(led_toggle(LED_DRV_INIT)); | |
| 408 | |
| 409 // read block flags from flash | |
| 410 bhp = (struct block_header_s *) offset2addr(dev.binfo[b].offset); | |
| 411 obhp = (struct block_header_old_s *) bhp; | |
| 412 | |
| 413 bstat[b].used = dev.blocksize; | |
| 414 bstat[b].lost = bstat[b].used; | |
| 415 bstat[b].flags = bhp->flags; | |
| 416 bstat[b].objects = 0; | |
| 417 | |
| 418 age_valid = 0; | |
| 419 | |
| 420 if (bhp->magic_low != BLOCK_MAGIC_LOW || | |
| 421 bhp->magic_high != BLOCK_MAGIC_HIGH) { | |
| 422 // The block magic as bad! It *could* be because the flash | |
| 423 // memory map is incorrect or because another application has | |
| 424 // spuriously written to the flash or ... who knows what. First | |
| 425 // we check to see if the reason is that we are dealing with a | |
| 426 // (really) old ffs format version. | |
| 427 if (obhp->magic_low == OLD_BLOCK_MAGIC_LOW && | |
| 428 obhp->magic_high == OLD_FFS_FORMAT_VERSION) { | |
| 429 tw(tr(TR_FUNC, TrFsck, "OLD ")); | |
| 430 fs.format = obhp->magic_high; | |
| 431 // We simulate that all the blocks are data blocks, in order | |
| 432 // to have some well-defined state that preformat() can work | |
| 433 // on. Later we will return EFFS_BADFORMAT and otherwise | |
| 434 // leave everything as it is, *without* modifying anything! | |
| 435 bstat[b].flags = BF_IS_DATA; | |
| 436 } | |
| 437 else { | |
| 438 // Quickly test if block is in empty state. We do not make a | |
| 439 // full check with block_used() because that takes too | |
| 440 // long --- we let preformat() do that. | |
| 441 if (bhp->magic_low == FLASH_NULL16 && | |
| 442 bhp->magic_high == FLASH_NULL16 && | |
| 443 bhp->age == FLASH_NULL16 && | |
| 444 bhp->version == FLASH_NULL16 && | |
| 445 bhp->flags == FLASH_NULL16) | |
| 446 { | |
| 447 bstat[b].used = 0; | |
| 448 bstat[b].lost = 0; | |
| 449 bstat[b].flags = BF_IS_EMPTY; | |
| 450 tw(tr(TR_FUNC, TrFsck, "EMPTY ")); | |
| 451 } | |
| 452 else { | |
| 453 // If the block is not free, it is probably corrupted. | |
| 454 // Thus we reset its age and free it. | |
| 455 tw(tr(TR_FUNC, TrFsck, "magic = 0x%08x\n", | |
| 456 bhp->magic_low | (bhp->magic_high << 16))); | |
| 457 ffsdrv.write_halfword(&bhp->age, 0); | |
| 458 block_free(b); | |
| 459 tw(tr(TR_FUNC, TrFsck, "BAD ")); | |
| 460 } | |
| 461 } | |
| 462 } | |
| 463 else { | |
| 464 fs.format = bhp->version; | |
| 465 age_valid = 1; | |
| 466 | |
| 467 if (!is_block(b, BF_IS_FREE)) { | |
| 468 bstat[b].used = block_used(b); | |
| 469 bstat[b].lost = bstat[b].used - BHEADER_SIZE; | |
| 470 } | |
| 471 | |
| 472 if (is_block(b, BF_IS_FREE)) { | |
| 473 // The only case where we do not call block_used() is | |
| 474 // when the block is truly free. | |
| 475 bstat[b].used = 0; | |
| 476 bstat[b].lost = 0; | |
| 477 tw(tr(TR_FUNC, TrFsck, "FREE ")); | |
| 478 ttw(ttr(TTrInitLow, "FREE" NL)); | |
| 479 | |
| 480 } | |
| 481 else if (is_block(b, BF_IS_DATA)) { | |
| 482 tw(tr(TR_FUNC, TrFsck, "DATA ")); | |
| 483 ttw(ttr(TTrInitLow, "DATA" NL)); | |
| 484 } | |
| 485 else if (is_block(b, BF_IS_CLEANING)) { | |
| 486 // Here we schedule a block_clean(). Note that we can | |
| 487 // and do not execute the block cleaning now, as the info | |
| 488 // that block_clean() needs is not at all ready at this | |
| 489 // point in the initialization. So we set a flag and then | |
| 490 // clean the block at the end of ffs_initialize() | |
| 491 tw(tr(TR_FUNC, TrFsck, "CLEANING ")); | |
| 492 ttw(ttr(TTrInitLow, "CLEANING" NL)); | |
| 493 b_to_clean = b + 1; | |
| 494 } | |
| 495 else if (is_block(b, BF_IS_COPYING)) { | |
| 496 tw(tr(TR_FUNC, TrFsck, "COPYING ")); | |
| 497 ttw(ttr(TTrInitLow, "COPYING" NL)); | |
| 498 fs.newinodes = b; | |
| 499 } | |
| 500 else if (is_block(b, BF_IS_INODES)) { | |
| 501 tw(tr(TR_FUNC, TrFsck, "INODES ")); | |
| 502 ttw(ttr(TTrInitLow, "INODES" NL)); | |
| 503 fs.inodes = b; | |
| 504 } | |
| 505 else if (is_block(b, BF_IS_INODES_LOST)) { | |
| 506 tw(tr(TR_FUNC, TrFsck, "INODESLOST")); | |
| 507 ttw(ttr(TTrInitLow, "INODESLOST" NL)); | |
| 508 b_inode_lost = b; | |
| 509 } | |
| 510 else { | |
| 511 block_free(b); | |
| 512 tw(tr(TR_FUNC, TrFsck, "INVALID ")); | |
| 513 ttw(ttr(TTrInitLow, "INVALID" NL)); | |
| 514 } | |
| 515 } | |
| 516 | |
| 517 tw(tr(TR_NULL, TrFsck, " %2d: (0x%05x) %02x, used = %6d\n", | |
| 518 b, dev.binfo[b].offset, bstat[b].flags & 0xFF, bstat[b].used)); | |
| 519 | |
| 520 if (age_valid) { | |
| 521 if (age_min == 0) { | |
| 522 // Initialize minimum and maximum block ages | |
| 523 age_min = age_max = bhp->age; | |
| 524 tw(tr(TR_FUNC, TrFsckLow, "age_min/max = %d\n", age_min)); | |
| 525 } | |
| 526 else { | |
| 527 age_dist_min = age_distance(bhp->age, age_min); | |
| 528 age_dist_max = age_distance(bhp->age, age_max); | |
| 529 if (age_dist_min > age_dist || | |
| 530 age_dist_max > age_dist) { | |
| 531 if (age_dist_max > age_dist_min) { | |
| 532 age_dist = age_dist_max; | |
| 533 age_min = bhp->age; | |
| 534 tw(tr(TR_FUNC, TrFsckLow, "age_min = %d (dist = %d)\n", | |
| 535 age_min, age_dist)); | |
| 536 } | |
| 537 else { | |
| 538 age_dist = age_dist_min; | |
| 539 age_max = bhp->age; | |
| 540 tw(tr(TR_FUNC, TrFsckLow, "age_max = %d (dist = %d)\n", | |
| 541 age_max, age_dist)); | |
| 542 } | |
| 543 } | |
| 544 } | |
| 545 } | |
| 546 } | |
| 547 tlw(led_off(LED_DRV_INIT)); | |
| 548 tw(tr(TR_FUNC, TrFsck, "age min, max, max-min = %d, %d, %d\n", | |
| 549 age_min, age_max, (uint16) (age_max-age_min))); | |
| 550 // If age_max is untouched is is because all blocks were in the 'Empty' | |
| 551 // state. In this case we let the age be as it is (0xFFFF). | |
| 552 if (age_max == 0) | |
| 553 age_max = age_min = BLOCK_AGE_MAX; | |
| 554 | |
| 555 // Handle age wrap around thus ensuring fs.age_max is set correctly. We | |
| 556 // have to type-cast the whole computation, otherwise it will be | |
| 557 // incorrect. | |
| 558 if ((age_t) (age_max - age_min) > 0x8000) { | |
| 559 age_dist = age_max; | |
| 560 age_max = age_min; | |
| 561 age_min = age_dist; | |
| 562 } | |
| 563 | |
| 564 // save maximum age found for the case of a bad block that is going to | |
| 565 // be reclaimed later on by blocks_reclaim() | |
| 566 fs.age_max = age_max; | |
| 567 | |
| 568 tw(tr(TR_FUNC, TrFsck, "fs.format = 0x%04x\n", fs.format)); | |
| 569 tw(tr(TR_FUNC, TrFsck, "fs.inodes, newinodes = %d, %d\n", | |
| 570 fs.inodes, fs.newinodes)); | |
| 571 ttw(ttr(TTrInit, "fs.inodes, newinodes = %d, %d" NL, | |
| 572 fs.inodes, fs.newinodes)); | |
| 573 tw(tr(TR_FUNC, TrFsck, "age min, max = %d, %d\n", age_min, age_max)); | |
| 574 | |
| 575 // If any blocks were in the EMPTY state, now is the time to bring them | |
| 576 // into the FREE state. Note that we must only do this *after* | |
| 577 // fs.age_max has been initialized. | |
| 578 for (b = 0; b < dev.numblocks; b++) { | |
| 579 if (is_block(b, BF_IS_EMPTY)) { | |
| 580 if ((bstat[b].used = block_used(b)) == 0) | |
| 581 block_preformat(b, 0); | |
| 582 else | |
| 583 block_free(b); | |
| 584 } | |
| 585 } | |
| 586 | |
| 587 if (fs.inodes >= 0) { | |
| 588 // The 'old' inode block is still valid thus we keep it. | |
| 589 if (fs.newinodes >= 0) | |
| 590 // The copying of inodes to the new block was not finished thus | |
| 591 // we free the block | |
| 592 block_free(fs.newinodes); | |
| 593 inodes_set(fs.inodes); | |
| 594 } | |
| 595 else { | |
| 596 // Copying must have been finished | |
| 597 if (fs.newinodes >= 0 && b_inode_lost >= 0) { | |
| 598 // The inode reclaim did finish but currently there is no valid | |
| 599 // inode block thus the operation must be finished by committing | |
| 600 // the new block as the valid inode block. | |
| 601 fs.inodes = b_inode_lost; | |
| 602 block_commit(); | |
| 603 | |
| 604 } | |
| 605 else { | |
| 606 // No old or new Inode block! | |
| 607 tw(tr(TR_END, TrFsck, "} %d\n", EFFS_NOFORMAT)); | |
| 608 ttw(ttr(TTrInitLow, "} %d" NL, EFFS_NOFORMAT)); | |
| 609 return EFFS_NOFORMAT; | |
| 610 } | |
| 611 } | |
| 612 | |
| 613 if ((fs.format >> 8) != (FFS_FORMAT_VERSION >> 8)) { | |
| 614 tw(tr(TR_END, TrFsck, "} %d\n", EFFS_BADFORMAT)); | |
| 615 ttw(ttr(TTrInitLow, "} %d" NL, EFFS_BADFORMAT)); | |
| 616 return EFFS_BADFORMAT; | |
| 617 } | |
| 618 | |
| 619 // FIXME: Insert age sanity check; age distance must not be too big (> 2 | |
| 620 // * FFS_AGE_DISTANCE)? | |
| 621 | |
| 622 tw(tr(TR_END, TrFsck, "} %d\n", b_to_clean)); | |
| 623 ttw(ttr(TTrInitLow, "} %d" NL, b_to_clean)); | |
| 624 | |
| 625 return b_to_clean; | |
| 626 } | |
| 627 | |
| 628 // Set fs.inodes and fs.inodes_addr | |
| 629 void inodes_set(iref_t i) | |
| 630 { | |
| 631 fs.inodes = i; | |
| 632 fs.inodes_addr = (struct inode_s *) | |
| 633 (offset2addr(dev.binfo[fs.inodes].offset) | |
| 634 + dev.atomsize - sizeof(struct inode_s)); | |
| 635 } | |
| 636 | |
| 637 | |
| 638 /****************************************************************************** | |
| 639 * inodes_fsck() | |
| 640 ******************************************************************************/ | |
| 641 | |
| 642 // Now for each inode in the inodes block, update the bstat array | |
| 643 // information: free, used, objects. Also, locate the root inode. We could | |
| 644 // optimize this a little, because bstat[binodes].used gives an inidication | |
| 645 // of how many inodes are actually present in the system. | |
| 646 iref_t inodes_fsck(void) | |
| 647 { | |
| 648 iref_t i; | |
| 649 struct inode_s *ip; | |
| 650 char *addr; | |
| 651 bref_t block; | |
| 652 | |
| 653 ttw(str(TTrInitLow, "inodes_fsck {" NL)); | |
| 654 tw(tr(TR_BEGIN, TrFsck, "inodes_fsck() {\n")); | |
| 655 tw(tr(TR_FUNC, TrFsck, "inodes in block %d:\n", fs.inodes)); | |
| 656 | |
| 657 // the fields of the bstat entry for the inodes have the meaning: | |
| 658 // used = total number of used inodes (valid, erased, invalid) | |
| 659 // lost = total number of lost inodes (erased, invalid) | |
| 660 // objects = index of first free inode (used by inode_alloc()) | |
| 661 | |
| 662 fs.root = 0; // default to root inode not found | |
| 663 fs.ijournal = 0; // default to journal file inode not found | |
| 664 bstat[fs.inodes].objects = 1; | |
| 665 bstat[fs.inodes].used = 0; | |
| 666 bstat[fs.inodes].lost = 0; | |
| 667 fs.sequence = 0; // just for debug (fun) | |
| 668 | |
| 669 // we must set some default value for this, so we set it to max possible! | |
| 670 fs.inodes_max = dev.blocksize / sizeof(struct inode_s); | |
| 671 | |
| 672 ip = inode_addr(1); | |
| 673 tw(tr(TR_FUNC, TrFsck, " i addr cld sib seq upd flag size name\n")); | |
| 674 for (i = 1; i < fs.inodes_max; i++, ip++) | |
| 675 { | |
| 676 // just for debug (fun) | |
| 677 if (ip->sequence > fs.sequence) | |
| 678 fs.sequence = ip->sequence; | |
| 679 | |
| 680 // compute block index and total data space occupied | |
| 681 block = offset2block(location2offset(ip->location)); | |
| 682 | |
| 683 // Only scan used inodes. blocks_fsck() accounted all used space as | |
| 684 // also being lost space, so now we subtract from the lost space, | |
| 685 // the space used by valid objects | |
| 686 if (ip->location != FLASH_NULL32) | |
| 687 { | |
| 688 bstat[fs.inodes].used++; | |
| 689 | |
| 690 tw(tr(TR_FUNC, TrFsck, "%3d 0x%05X %3d %3d %4d %3d %s%s%s%s%s%s %6d %s\n", | |
| 691 i, | |
| 692 location2offset(ip->location), | |
| 693 ip->child, ip->sibling, | |
| 694 ip->sequence, ip->updates, | |
| 695 is_object(ip, OT_DIR) ? "d" : "", | |
| 696 is_object(ip, OT_LINK) ? "l" : "", | |
| 697 is_object(ip, OT_FILE) ? "f" : "", | |
| 698 is_object(ip, OT_SEGMENT) ? "s" : "", | |
| 699 is_object(ip, OT_ERASED) ? " " : "", | |
| 700 IS_BIT_SET(ip->flags, OF_READONLY) && !is_object(ip, OT_ERASED) ? | |
| 701 "r" : " ", | |
| 702 ip->size, | |
| 703 // Erased chunks do not have any name so we can not trace erased objects! | |
| 704 (ip->size && !is_object(ip, OT_SEGMENT) && !is_object(ip, OT_ERASED) ? | |
| 705 addr2name(offset2addr(location2offset(ip->location))) : "") | |
| 706 )); | |
| 707 | |
| 708 if (is_object_valid(ip)) { | |
| 709 // This inode is valid, so we account the data space as used | |
| 710 // and the inode as used too. | |
| 711 bstat[block].lost -= ip->size; | |
| 712 bstat[block].objects++; | |
| 713 // test if this is the root inode. store index if it is. | |
| 714 if (!is_object(ip, OT_SEGMENT)) { | |
| 715 addr = addr2name(offset2addr(location2offset(ip->location))); | |
| 716 if (*addr == '/') | |
| 717 fs.root = i; | |
| 718 else if (*addr == '.' && | |
| 719 ffs_strcmp(addr, FFS_JOURNAL_NAME) == 0) { | |
| 720 fs.ijournal = i; | |
| 721 } | |
| 722 } | |
| 723 } | |
| 724 else if (is_object(ip, OT_ERASED)) { | |
| 725 // this inode's data is deleted, so we account the data | |
| 726 // space as used and lost and the inode as lost too. | |
| 727 bstat[fs.inodes].lost++; | |
| 728 } | |
| 729 else { | |
| 730 // This is an invalid object, so we account the data space | |
| 731 // as used and lost and the inode as lost too. NOTEME: error | |
| 732 // what should we do? Perhaps we should record semi-lost | |
| 733 // inodes? Can we safely account for it here if this is an | |
| 734 // object to be recovered because another inode.copied is | |
| 735 // referring to this? Will used/lost etc. be updated | |
| 736 // correctly then? | |
| 737 bstat[fs.inodes].lost++; | |
| 738 tw(tr(TR_NULL, TrFsck, "(invalid = %d)\n", ip->flags & OT_MASK)); | |
| 739 } | |
| 740 } | |
| 741 } | |
| 742 ttw(ttr(TTrInit, "fs.root=%d, journal=%d" NL, fs.root, fs.ijournal)); | |
| 743 tw(tr(TR_END, TrFsck, "} used: %d, lost: %d, root: %d, journal: %d\n", | |
| 744 bstat[fs.inodes].used, bstat[fs.inodes].lost, fs.root, fs.ijournal)); | |
| 745 | |
| 746 fs.sequence++; | |
| 747 | |
| 748 tw(tr_bstat()); | |
| 749 | |
| 750 if (fs.root == 0) { | |
| 751 ttw(ttr(TTrInitLow, "} %d" NL, EFFS_NOFORMAT)); | |
| 752 return EFFS_NOFORMAT; | |
| 753 } | |
| 754 | |
| 755 ttw(str(TTrInitLow, "} 0" NL)); | |
| 756 | |
| 757 return EFFS_OK; | |
| 758 } | |
| 759 | |
| 760 | |
| 761 /****************************************************************************** | |
| 762 * Preformat and format | |
| 763 ******************************************************************************/ | |
| 764 | |
| 765 // Prepare all blocks for fs_format(). Because ffs_is_formattable() has | |
| 766 // already been called prior to this function, we know that no sector erase | |
| 767 // is in progress! The blocks are prepared by putting them into the 'Free' | |
| 768 // state. | |
| 769 effs_t fs_preformat(void) | |
| 770 { | |
| 771 bref_t b; | |
| 772 | |
| 773 ttw(str(TTrFormat, "preformat {" NL)); | |
| 774 tw(tr(TR_BEGIN, TrFormat, "fs_preformat() {\n")); | |
| 775 | |
| 776 // Mark ffs as being non-formatted from now on. | |
| 777 fs.root = 0; | |
| 778 | |
| 779 // We must initialize bstat[fs.inodes].used and inodes_high, such that | |
| 780 // inodes_reclaim() isn't triggered in reclaim() on the following | |
| 781 // fs_format(). | |
| 782 inodes_set(0); | |
| 783 bstat[fs.inodes].used = 0; | |
| 784 bstat[fs.inodes].lost = 0; | |
| 785 bstat[fs.inodes].objects = 0; | |
| 786 | |
| 787 // While format is in progress, we make FFS inaccessible to other | |
| 788 // functions... | |
| 789 fs.initerror = EFFS_NOFORMAT; | |
| 790 | |
| 791 if (dev.manufact == 0) { | |
| 792 b = EFFS_NODEVICE; | |
| 793 } | |
| 794 else { | |
| 795 for (b = 0; b < dev.numblocks; b++) { | |
| 796 if (is_block(b, BF_IS_EMPTY)) { | |
| 797 if ((bstat[b].used = block_used(b)) == 0) | |
| 798 block_preformat(b, 0); | |
| 799 else | |
| 800 block_free(b); | |
| 801 } | |
| 802 else if (!is_block(b, BF_IS_FREE)) { | |
| 803 block_free(b); | |
| 804 } | |
| 805 } | |
| 806 b = EFFS_OK; | |
| 807 } | |
| 808 | |
| 809 tw(tr(TR_END, TrFormat, "} %d\n", b)); | |
| 810 ttw(ttr(TTrFormat, "} %d" NL, b)); | |
| 811 | |
| 812 return b; | |
| 813 } | |
| 814 | |
| 815 // Preformat a single block thus taking it from the 'Empty' state into | |
| 816 // 'Free' state. | |
| 817 void block_preformat(bref_t b, age_t age) | |
| 818 { | |
| 819 int set_age_max; | |
| 820 struct block_header_s *bhp = | |
| 821 (struct block_header_s *) offset2addr(dev.binfo[b].offset); | |
| 822 | |
| 823 tw(tr(TR_BEGIN, TrFormat, "fs_block_preformat(%d, %d)\n", b, age)); | |
| 824 | |
| 825 if (age == 0) { | |
| 826 age = fs.age_max; | |
| 827 } | |
| 828 else { | |
| 829 // We schedule an update of fs.age_max. Due to proper handling of | |
| 830 // age wrap-around, we can not actually set it now. | |
| 831 set_age_max = (age == fs.age_max); | |
| 832 age++; | |
| 833 if (age == 0) | |
| 834 age++; | |
| 835 if (set_age_max) { | |
| 836 fs.age_max = age; | |
| 837 tw(tr(TR_FUNC, TrFormat, "new fs.age_max = %d\n", fs.age_max)); | |
| 838 } | |
| 839 } | |
| 840 | |
| 841 ffsdrv.write_halfword(&bhp->age, age); | |
| 842 ffsdrv.write_halfword(&bhp->version, FFS_FORMAT_VERSION); | |
| 843 ffsdrv.write_halfword(&bhp->magic_low, BLOCK_MAGIC_LOW); | |
| 844 ffsdrv.write_halfword(&bhp->magic_high, BLOCK_MAGIC_HIGH); | |
| 845 | |
| 846 bstat[b].flags = BF_IS_EMPTY; | |
| 847 bstat[b].used = 0; | |
| 848 bstat[b].lost = 0; | |
| 849 bstat[b].objects = 0; | |
| 850 | |
| 851 block_flags_write(b, BF_FREE); | |
| 852 | |
| 853 tw(tr(TR_END, TrFormat, "")); | |
| 854 } | |
| 855 | |
| 856 // After preformat() has erased two blocks, this function can be called to | |
| 857 // initialize ffs by writing fs data and metadata. Note that ffs_begin() is | |
| 858 // *not* called before this function in ffs.c. Otherwise we would never | |
| 859 // enter this function because fs.root is zero. NOTEME: this is also a bug | |
| 860 // as this means we risk that this operation is started while an erase (or a | |
| 861 // write) is in progress! How the flash device reacts to this is currently | |
| 862 // unknown. | |
| 863 effs_t fs_format(const char *name) | |
| 864 { | |
| 865 bref_t i, b; | |
| 866 | |
| 867 ttw(str(TTrFormat, "format {" NL)); | |
| 868 tw(tr(TR_BEGIN, TrFormat, "fs_format('%s') {\n", name)); | |
| 869 | |
| 870 // Initialize file system parameters. It should be safe to change these | |
| 871 // now, as the format cannot fail at this point onwards. | |
| 872 fs_params_init(name); | |
| 873 | |
| 874 // Make the first block be the inodes block | |
| 875 if ((fs.inodes = block_alloc(1, BF_COPYING)) < 0) | |
| 876 return EFFS_AGAIN; | |
| 877 block_flags_write(fs.inodes, BF_INODES); | |
| 878 inodes_set(fs.inodes); | |
| 879 | |
| 880 // Make all block as data blocks except from the free_min and inode block | |
| 881 for (i = 0; i < dev.numblocks - fs.blocks_free_min - 1; i++) | |
| 882 if ((b = block_alloc(0, BF_DATA)) < 0) | |
| 883 return EFFS_AGAIN; | |
| 884 | |
| 885 // Restart object sequencing (debug feature only) | |
| 886 fs.sequence = 0; | |
| 887 | |
| 888 // Create root directory | |
| 889 journal_begin(0); | |
| 890 if ((fs.root = object_create(name, 0, 0, 0)) < 0) { | |
| 891 tw(tr(TR_END, TrFormat, "} %d\n", fs.root)); | |
| 892 return fs.root; | |
| 893 } | |
| 894 journal_commit(OT_DIR); | |
| 895 | |
| 896 if ((fs.ijournal = journal_create(0)) < 0) { | |
| 897 tw(tr(TR_END, TrFormat, "} %d\n", fs.ijournal)); | |
| 898 return fs.ijournal; | |
| 899 } | |
| 900 | |
| 901 fs.initerror = ffs_initialize(); | |
| 902 | |
| 903 ttw(ttr(TTrFormat, "} %d" NL, fs.initerror)); | |
| 904 tw(tr(TR_END, TrFormat, "} %d\n", fs.initerror)); | |
| 905 | |
| 906 return fs.initerror; | |
| 907 } | |
| 908 | |
| 909 // Check if we are ready to preformat (flag = 0) or format (flag = 1) | |
| 910 // | |
| 911 // For a format, we must first ensure no blocks are valid e.g. a preformat | |
| 912 // has already been run. Next, we must ensure we have preformatted all | |
| 913 // blocks e.g. all blocks are in the 'Free' state. This is actually the same | |
| 914 // thing but it sure helps the user because it yields a more precise error | |
| 915 // code when the format fails. In future we might be able to start a format | |
| 916 // when only two blocks have been preformatted, but this is harder because | |
| 917 // we have to make sure not to read from the physical sector that we are | |
| 918 // erasing, and this is exactly what ffs_ffs_initialize() currently does | |
| 919 // (when it is called at the end of format()). | |
| 920 // | |
| 921 // For a preformat, we must ensure an erase is not in progress (because we | |
| 922 // don't know how the device will react to a new erase when an erase is | |
| 923 // currently suspended). | |
| 924 effs_t is_formattable(int8 flag) | |
| 925 { | |
| 926 bref_t i, free, valid; | |
| 927 effs_t error = EFFS_OK; | |
| 928 | |
| 929 tw(tr(TR_FUNC, TrFormat, "is_formattable() ")); | |
| 930 | |
| 931 // Count the number of valid and free blocks. These numbers will later | |
| 932 // be checked to see if we are really ready for a (pre)format(). Note | |
| 933 // that we *only* read block flags from the bstat[] array. We must not | |
| 934 // read directly from the flash sectors because an erase might be in | |
| 935 // progress! | |
| 936 for (i = 0, free = 0, valid = 0; i < dev.numblocks; i++) { | |
| 937 if (is_block(i, BF_IS_DATA) || is_block(i, BF_IS_INODES)) | |
| 938 valid++; | |
| 939 if (is_block(i, BF_IS_FREE)) | |
| 940 free++; | |
| 941 } | |
| 942 if (flag == 0) { | |
| 943 // In the case of a preformat, ensure an erase is not in | |
| 944 // progress (because we don't know how the device will react to a new | |
| 945 // erase when an erase is currently suspended). | |
| 946 if (dev.state == DEV_ERASE || dev.state == DEV_ERASE_SUSPEND) { | |
| 947 tw(tr(TR_NULL, TrFormat, "(%d)\n", EFFS_AGAIN)); | |
| 948 return EFFS_AGAIN; | |
| 949 } | |
| 950 } | |
| 951 else { | |
| 952 if (valid > 0) | |
| 953 // Ensure we have preformatted prior to a format. | |
| 954 error = EFFS_NOPREFORMAT; | |
| 955 else if (free < dev.numblocks) | |
| 956 // Ensure all blocks are free before a format(). If not, a | |
| 957 // preformat() is currently in progress. | |
| 958 error = EFFS_AGAIN; | |
| 959 } | |
| 960 | |
| 961 tw(tr(TR_NULL, TrFormat, "(%d)\n", error)); | |
| 962 return error; | |
| 963 } | |
| 964 | |
| 965 | |
| 966 /****************************************************************************** | |
| 967 * Journalling | |
| 968 ******************************************************************************/ | |
| 969 | |
| 970 // The following matrix illustrates how the members of an inode change for | |
| 971 // the various (journalled) operations: | |
| 972 // | |
| 973 // | flags | size | loc | child | siblg | dir | oldi | updates | |
| 974 // ---------+-------+------+-----+-------+-------+-----+------+-------- | |
| 975 // create | new | new | new | - | - | ins | n/a | 0 | |
| 976 // fupdate | o | new | new | o | - | ins | del | old+1 | |
| 977 // relocate | o | o | new | o | - | ins | del | old+1 | |
| 978 // fctrl | new | o | o | o | - | ins | del | old+1 | |
| 979 // remove | n/a | n/a | n/a | n/a | n/a | n/a | del | n/a | |
| 980 // | |
| 981 // - = leave empty (0xFFFF) | |
| 982 // ins = insert/append into directory | |
| 983 // o = old value | |
| 984 // | |
| 985 // We don't have to store child member in the journal entry because either | |
| 986 // it is EMPTY (fs.journal.oldi = 0) or it is retrieved from oldip->child. | |
| 987 | |
| 988 // NOTEME: With journalling implemented, object_relocate might be able just | |
| 989 // to make a simple data copy! | |
| 990 | |
| 991 // block_clean() is safe (without journalling), now that only ip->size is | |
| 992 // set to zero. | |
| 993 | |
| 994 // Begin a new journal. Either a fresh object create (oldi == 0) or an | |
| 995 // update of an existing object (oldi == iref of old object) | |
| 996 void journal_begin(iref_t oldi) | |
| 997 { | |
| 998 tw(tr(TR_FUNC, TrJournal, "journal_begin(%d)\n", oldi)); | |
| 999 | |
| 1000 fs.journal.i = 0; | |
| 1001 fs.journal.state = JOURNAL_IS_EMPTY; | |
| 1002 fs.journal.repli = 0; | |
| 1003 fs.link_child = 1; //Default link child in journal_commit() | |
| 1004 | |
| 1005 if (oldi == 0) { | |
| 1006 fs.journal.flags = 0xFF; | |
| 1007 fs.journal.diri = 0; | |
| 1008 fs.journal.oldi = 0; | |
| 1009 fs.journal.location = 0; | |
| 1010 fs.journal.size = 0; | |
| 1011 } | |
| 1012 else { | |
| 1013 struct inode_s *oldip = inode_addr(oldi); | |
| 1014 fs.journal.flags = oldip->flags; | |
| 1015 fs.journal.diri = oldi; | |
| 1016 fs.journal.oldi = oldi; | |
| 1017 fs.journal.location = oldip->location; | |
| 1018 fs.journal.size = oldip->size; | |
| 1019 } | |
| 1020 } | |
| 1021 | |
| 1022 // NOTEME: We have compressed the macro code because it will NOT compile on | |
| 1023 // Unix otherwise. So until we find out why, we use this as a work-around. | |
| 1024 #if (FFS_TEST == 1) | |
| 1025 #define JOURNAL_TEST(testcase, text) if (fs.testflags == testcase) { tw(tr(TR_END, TrJournal, "} (" text ")\n")); return; } | |
| 1026 #else | |
| 1027 #define JOURNAL_TEST(testcase, text) | |
| 1028 #endif | |
| 1029 | |
| 1030 // NOTEME: Should we empty journal file when we are anyway relocating it in | |
| 1031 // data_reclaim()? | |
| 1032 void journal_end(uint8 type) | |
| 1033 { | |
| 1034 struct inode_s *ip = inode_addr(fs.ijournal); | |
| 1035 struct journal_s *addr = (struct journal_s *) | |
| 1036 offset2addr(location2offset(ip->location) + fs.journal_pos); | |
| 1037 | |
| 1038 tw(tr(TR_BEGIN, TrJournal, "journal_end(0x%x) {\n", type)); | |
| 1039 tw(tr(TR_FUNC, TrJournal, "journal_pos = 0x%04x (%d)\n", fs.journal_pos, | |
| 1040 (fs.journal_pos - JOURNAL_POS_INITIAL) / sizeof(struct journal_s))); | |
| 1041 | |
| 1042 // If this is a create, set the object type | |
| 1043 if (type != 0 && fs.journal.oldi == 0) | |
| 1044 fs.journal.flags = (fs.journal.flags & OF_MASK) | type; | |
| 1045 | |
| 1046 // If there is no journal file, we can do without it, although we | |
| 1047 // certainly don't like it! | |
| 1048 if (fs.ijournal == 0) { | |
| 1049 journal_commit(0); | |
| 1050 tw(tr(TR_END, TrJournal, "} No jounal file\n")); | |
| 1051 return; | |
| 1052 } | |
| 1053 | |
| 1054 JOURNAL_TEST(JOURNAL_TEST_EMPTY, "Oops in JOURNAL_IS_EMPTY"); | |
| 1055 | |
| 1056 // Write RAM journal to journal file. | |
| 1057 if (fs.journal.state == (uint8) JOURNAL_IS_EMPTY) { | |
| 1058 fs.journal.state = JOURNAL_IS_WRITING; | |
| 1059 ffsdrv.write(addr, &fs.journal, sizeof(fs.journal)); | |
| 1060 } | |
| 1061 | |
| 1062 JOURNAL_TEST(JOURNAL_TEST_WRITING, "Oops in JOURNAL_IS_WRITING"); | |
| 1063 | |
| 1064 // Advance journal file's state | |
| 1065 if (fs.journal.state == (uint8) JOURNAL_IS_WRITING) { | |
| 1066 fs.journal.state = JOURNAL_IS_READY; | |
| 1067 ffsdrv_write_byte(&addr->state, fs.journal.state); | |
| 1068 } | |
| 1069 | |
| 1070 JOURNAL_TEST(JOURNAL_TEST_READY, "Oops in JOURNAL_IS_READY"); | |
| 1071 | |
| 1072 journal_commit(0); | |
| 1073 | |
| 1074 JOURNAL_TEST(JOURNAL_TEST_COMMITTING, "Oops in JOURNAL_TEST_COMMITTING"); | |
| 1075 JOURNAL_TEST(JOURNAL_TEST_COMMITTED, "Oops in JOURNAL_COMMITTED"); | |
| 1076 | |
| 1077 // Advance journal file's state | |
| 1078 ffsdrv_write_byte(&addr->state, JOURNAL_IS_DONE); | |
| 1079 | |
| 1080 JOURNAL_TEST(JOURNAL_TEST_DONE, "Oops in JOURNAL_IS_DONE"); | |
| 1081 | |
| 1082 // Advance journal | |
| 1083 fs.journal_pos += sizeof(struct journal_s); | |
| 1084 | |
| 1085 // Unless we are currently relocating the journal file itself, check if | |
| 1086 // journal file is near full and relocate it if it is. | |
| 1087 if (fs.journal_pos >= fs.journal_size - FFS_JOURNAL_MARGIN * | |
| 1088 sizeof(struct journal_s) && fs.journal.oldi != fs.ijournal) { | |
| 1089 tw(tr(TR_FUNC, TrJournal, "Journal file (near) full!\n")); | |
| 1090 journal_create(fs.ijournal); | |
| 1091 } | |
| 1092 | |
| 1093 // Check if we have just committed the journal file itself | |
| 1094 if (fs.journal.oldi == fs.ijournal) { | |
| 1095 fs.journal_pos = JOURNAL_POS_INITIAL; | |
| 1096 fs.ijournal = fs.journal.i; | |
| 1097 tw(tr(TR_FUNC, TrJournal, "Journal file re-created, fs.ijournal = %d\n", | |
| 1098 fs.ijournal)); | |
| 1099 } | |
| 1100 tw(tr(TR_END, TrJournal, "}\n")); | |
| 1101 } | |
| 1102 | |
| 1103 // Write contents of fs.journal to FFS meta data (inodes). Note that we do | |
| 1104 // NOT traverse ip->copied as we used to do in the old | |
| 1105 // object_update_commit(). Also, we do not check if object has been | |
| 1106 // erased after traversing ip->copied. All this code has been removed | |
| 1107 // because we will very soon have full callback functionality and thus the | |
| 1108 // code is redundant. | |
| 1109 void journal_commit(uint8 type) | |
| 1110 { | |
| 1111 struct inode_s *ip = inode_addr(fs.journal.i); | |
| 1112 struct inode_s *oldip = inode_addr(fs.journal.oldi); | |
| 1113 struct inode_s *dp; | |
| 1114 bref_t b; | |
| 1115 | |
| 1116 tw(tr(TR_BEGIN, TrJournal, "journal_commit(%d) {\n", type)); | |
| 1117 tw(tr(TR_FUNC, TrJournal, "i = %d\n", fs.journal.i)); | |
| 1118 ttw(ttr(TTrObj, "jc(){" NL)); | |
| 1119 | |
| 1120 if (fs.journal.i) | |
| 1121 { | |
| 1122 // If this is a create, set the object type | |
| 1123 if (type != 0 && fs.journal.oldi == 0) | |
| 1124 fs.journal.flags = (fs.journal.flags & OF_MASK) | type; | |
| 1125 | |
| 1126 tw(tr(TR_FUNC, TrJournal, "loc = 0x%04x, size = %d\n", | |
| 1127 fs.journal.location, fs.journal.size)); | |
| 1128 ffsdrv.write((uint32 *) &ip->location, (uint32 *) &fs.journal.location, sizeof(location_t)); | |
| 1129 ffsdrv.write_halfword((uint16 *) &ip->size, fs.journal.size); | |
| 1130 | |
| 1131 if (fs.journal.oldi != 0 && fs.link_child != 0) | |
| 1132 // If this is an update, we copy the child member from old | |
| 1133 // inode. We must do this before we validate the new object, | |
| 1134 // otherwise an intermediate readdir() will detect an empty | |
| 1135 // directory! | |
| 1136 ffsdrv.write_halfword((uint16*) &ip->child, oldip->child); | |
| 1137 | |
| 1138 tw(tr(TR_FUNC, TrJournal, "seq = %d\n", fs.sequence)); | |
| 1139 // We must check if sequence is already written because if this | |
| 1140 // commit was inititiated by journal_init(), we don't know exactly | |
| 1141 // what was written | |
| 1142 if (ip->sequence == FLASH_NULL16) | |
| 1143 ffsdrv.write_halfword(&ip->sequence, fs.sequence++); | |
| 1144 if (fs.journal.oldi == 0) | |
| 1145 ffsdrv.write_halfword(&ip->updates, 0); | |
| 1146 else | |
| 1147 ffsdrv.write_halfword(&ip->updates, oldip->updates + 1); | |
| 1148 | |
| 1149 JOURNAL_TEST(JOURNAL_TEST_COMMITTING, "Oops in JOURNAL_TEST_COMMITTING") | |
| 1150 | |
| 1151 // Insert object into directory structure. We must do this before | |
| 1152 // deleting old object, otherwise an intermediate readdir() will | |
| 1153 // fail with EFFS_NOTFOUND. Note that when the root directory is | |
| 1154 // created, fs.journal.diri is zero --- thus the test! | |
| 1155 if (fs.journal.diri != 0) { | |
| 1156 tw(tr(TR_FUNC, TrJournal, "diri = %d ", fs.journal.diri)); | |
| 1157 if (fs.journal.diri < 0) { | |
| 1158 tw(tr(TR_NULL, TrJournal, "child\n")); | |
| 1159 dp = inode_addr(-fs.journal.diri); | |
| 1160 ffsdrv.write_halfword((uint16 *) &dp->child, fs.journal.i); | |
| 1161 } | |
| 1162 else { | |
| 1163 tw(tr(TR_NULL, TrJournal, "sibling\n")); | |
| 1164 dp = inode_addr(fs.journal.diri); | |
| 1165 ffsdrv.write_halfword((uint16 *) &dp->sibling, fs.journal.i); | |
| 1166 } | |
| 1167 } | |
| 1168 | |
| 1169 // The new object is validated before the old object is deleted. | |
| 1170 // This is in order to avoid an interrupting stat or read operation | |
| 1171 // to fail with EFFS_NOTFOUND | |
| 1172 tw(tr(TR_FUNC, TrJournal, "flags = 0x%02x\n", fs.journal.flags)); | |
| 1173 ffsdrv_write_byte(&ip->flags, fs.journal.flags); | |
| 1174 | |
| 1175 // Update bstat[] appropriately | |
| 1176 b = offset2block(location2offset(ip->location)); | |
| 1177 bstat[b].objects++; | |
| 1178 tw(tr(TR_FUNC, TrJournal, "bstat[%d].objects = %d\n", b, bstat[b].objects)); | |
| 1179 } | |
| 1180 | |
| 1181 tw(tr(TR_FUNC, TrJournal, "oldi = %d\n", fs.journal.oldi)); | |
| 1182 if (fs.journal.oldi != 0) | |
| 1183 { | |
| 1184 // If this is an update or an erase, we erase the old object | |
| 1185 ffsdrv_write_byte(&oldip->flags, OT_ERASED); | |
| 1186 | |
| 1187 // Update bstat according to deletion of the old object. | |
| 1188 b = offset2block(location2offset(oldip->location)); | |
| 1189 bstat[b].objects--; | |
| 1190 tw(tr(TR_FUNC, TrJournal, "bstat[%d].objects = %d\n", b, bstat[b].objects)); | |
| 1191 | |
| 1192 // If we moved the data (all cases, except fcontrol), update lost | |
| 1193 if (fs.journal.location != oldip->location) | |
| 1194 bstat[b].lost += oldip->size; | |
| 1195 | |
| 1196 bstat[fs.inodes].lost++; | |
| 1197 | |
| 1198 // If we renamed a file to an existing filename, remove the replaced file. | |
| 1199 if (fs.journal.repli > 0) | |
| 1200 object_remove(fs.journal.repli); // Ignore error! | |
| 1201 } | |
| 1202 | |
| 1203 tw(tr(TR_END, TrJournal, "}\n")); | |
| 1204 ttw(ttr(TTrObj, "}" NL)); | |
| 1205 } | |
| 1206 | |
| 1207 // Save the current journal into "old" journal. We need this because an | |
| 1208 // object_create() can call data_reclaim() which can call object_relocate() | |
| 1209 // which uses the journal system. | |
| 1210 int journal_push(void) | |
| 1211 { | |
| 1212 memcpy(&fs.ojournal, &fs.journal, sizeof(struct journal_s)); | |
| 1213 fs.journal_depth++; | |
| 1214 if (fs.journal_depth > 1) { | |
| 1215 tw(tr(TR_FUNC, TrAll, "FATAL: journal_push() to depth %d\n", | |
| 1216 fs.journal_depth)); | |
| 1217 return -1; | |
| 1218 } | |
| 1219 | |
| 1220 tw(tr(TR_FUNC, TrJournal, "journal_push() to depth %d\n", | |
| 1221 fs.journal_depth)); | |
| 1222 | |
| 1223 return EFFS_OK; | |
| 1224 } | |
| 1225 | |
| 1226 // Recall "old" journal into current journal | |
| 1227 int journal_pop(void) | |
| 1228 { | |
| 1229 tw(tr(TR_FUNC, TrJournal, "journal_pop() from depth %d\n", | |
| 1230 fs.journal_depth)); | |
| 1231 | |
| 1232 fs.journal_depth--; | |
| 1233 if (fs.journal_depth < 0) { | |
| 1234 tw(tr(TR_FUNC, TrAll, "FATAL: journal_pop() to depth %d\n", | |
| 1235 fs.journal_depth)); | |
| 1236 return -1; | |
| 1237 } | |
| 1238 memcpy(&fs.journal, &fs.ojournal, sizeof(struct journal_s)); | |
| 1239 | |
| 1240 return EFFS_OK; | |
| 1241 } | |
| 1242 | |
| 1243 // Initialize the journalling system. Create journal file if it not already | |
| 1244 // exist. Commit/write pending journal if such exists --- return 1 in that | |
| 1245 // case. Otherwise, if journal file is clean (no journals pending) and all | |
| 1246 // is fine, return EFFS_OK. | |
| 1247 effs_t journal_init(iref_t i) | |
| 1248 { | |
| 1249 int j; | |
| 1250 struct inode_s *ip = inode_addr(i); | |
| 1251 struct journal_s *addr; | |
| 1252 | |
| 1253 if (i == 0) { | |
| 1254 // Journal file does not exist, so create it | |
| 1255 if ((i = journal_create(0)) <= 0) { | |
| 1256 fs.ijournal = 0; | |
| 1257 return i; | |
| 1258 } | |
| 1259 } | |
| 1260 | |
| 1261 fs.journal_depth = 0; | |
| 1262 fs.journal_pos = JOURNAL_POS_INITIAL; | |
| 1263 | |
| 1264 addr = (struct journal_s *) | |
| 1265 offset2addr(location2offset(ip->location) + fs.journal_pos); | |
| 1266 | |
| 1267 tw(tr(TR_BEGIN, TrJournal, "journal_init(%d) {\n", i)); | |
| 1268 | |
| 1269 fs.ijournal = i; | |
| 1270 | |
| 1271 // Search for first non-completed journal entry. | |
| 1272 for (j = 0; /* FIXME: limit to end of journal */; j++, addr++) { | |
| 1273 if (addr->state != (uint8) JOURNAL_IS_DONE) | |
| 1274 break; | |
| 1275 } | |
| 1276 tw(tr(TR_FUNC, TrJournal, "entry %d is in state 0x%x\n", j, addr->state)); | |
| 1277 | |
| 1278 fs.journal_pos += j * sizeof(fs.journal); | |
| 1279 i = EFFS_OK; | |
| 1280 | |
| 1281 if (addr->state == (uint8) JOURNAL_IS_EMPTY) { | |
| 1282 tw(tr(TR_FUNC, TrJournal, "Last journal is in EMPTY state\n")); | |
| 1283 // Journal file is proper, so just record position | |
| 1284 } | |
| 1285 else if (addr->state == (uint8) JOURNAL_IS_READY) { | |
| 1286 // Copy the entry into fs.journal. | |
| 1287 tw(tr(TR_FUNC, TrJournal, "Last journal is in READY state\n")); | |
| 1288 memcpy(&fs.journal, addr, sizeof(fs.journal)); | |
| 1289 journal_end(0); | |
| 1290 i = 1; | |
| 1291 } | |
| 1292 else { | |
| 1293 // Journal entry wasn't finished, so just ignore it after updating | |
| 1294 // its state to JOURNAL_IS_DONE. | |
| 1295 tw(tr(TR_FUNC, TrJournal, "Last journal is between EMPTY and READY\n")); | |
| 1296 ffsdrv_write_byte(&addr->state, JOURNAL_IS_DONE); | |
| 1297 fs.journal_pos += sizeof(fs.journal); | |
| 1298 } | |
| 1299 | |
| 1300 if (ip->size != fs.journal_size + atomalign(sizeof(FFS_JOURNAL_NAME) + 1)) { | |
| 1301 tw(tr(TR_FUNC, TrJournal, "Wrong journal size, create new\n")); | |
| 1302 // Journal size do not match default size, so create new. This | |
| 1303 // should only happen if we use an old FFS image with a newer FFS | |
| 1304 // version. | |
| 1305 if ((i = journal_create(fs.ijournal)) <= 0) { | |
| 1306 fs.ijournal = 0; | |
| 1307 return i; | |
| 1308 } | |
| 1309 } | |
| 1310 | |
| 1311 tw(tr(TR_FUNC, TrJournal, "journal_pos = 0x%04x\n", fs.journal_pos)); | |
| 1312 tw(tr(TR_END, TrJournal, "} %d\n", i)); | |
| 1313 | |
| 1314 return i; | |
| 1315 } | |
| 1316 | |
| 1317 // Create the journal file from scratch or relocate an existing one. It is | |
| 1318 // marked read-only just for clarity --- it cannot be deleted anyway! | |
| 1319 // fs_format() calls this function. Note that no data are written in | |
| 1320 // object_create() because the journal file is handled specially in that | |
| 1321 // function. | |
| 1322 iref_t journal_create(iref_t oldi) | |
| 1323 { | |
| 1324 iref_t i; | |
| 1325 | |
| 1326 tw(tr(TR_BEGIN, TrJournal, "journal_create(%d) {\n", oldi)); | |
| 1327 tw(tr(TR_FUNC, TrJournal, "journal file size = %d\n", fs.journal_size)); | |
| 1328 | |
| 1329 if (fs.journal_size == 0) { | |
| 1330 tw(tr(TR_FUNC, TrJournal, "Journal file creation aborted because fs.journal_size = 0 (No journal file wanted)\n")); | |
| 1331 tw(tr(TR_END, TrJournal, "} %d\n", 0)); | |
| 1332 return 0; | |
| 1333 } | |
| 1334 | |
| 1335 // If we are working on a write-once file system, we do not need a | |
| 1336 // journal. | |
| 1337 if (fs.blocks_free_min == 0) { | |
| 1338 tw(tr(TR_FUNC, TrJournal, "Journal file creation aborted because fs.blocks_free_min = 0 (write-once system)\n")); | |
| 1339 tw(tr(TR_END, TrJournal, "} %d\n", 0)); | |
| 1340 return 0; | |
| 1341 } | |
| 1342 | |
| 1343 journal_begin(oldi); | |
| 1344 | |
| 1345 i = object_create(FFS_JOURNAL_NAME, 0, fs.journal_size, -fs.root); | |
| 1346 if (i < 0) { | |
| 1347 tw(tr(TR_END, TrJournal, "} %d\n", i)); | |
| 1348 return i; | |
| 1349 } | |
| 1350 fs.journal.flags = BIT_SET(fs.journal.flags, OF_READONLY); | |
| 1351 | |
| 1352 // commit the creation or relocation | |
| 1353 if (oldi != 0) | |
| 1354 journal_end(0); | |
| 1355 else { | |
| 1356 journal_commit(OT_FILE); | |
| 1357 fs.journal_pos = JOURNAL_POS_INITIAL; | |
| 1358 } | |
| 1359 | |
| 1360 tw(tr(TR_END, TrJournal, "} %d\n", i)); | |
| 1361 | |
| 1362 return i; | |
| 1363 } | |
| 1364 | |
| 1365 /****************************************************************************** | |
| 1366 * FFS Begin and End | |
| 1367 ******************************************************************************/ | |
| 1368 | |
| 1369 // The following two functions should surround the code of every API | |
| 1370 // function in ffs.c (except preformat and format). The functions | |
| 1371 // ensures that the operation about to be executed can be made without | |
| 1372 // race-conditions or other problems. | |
| 1373 #if (TARGET == 0) | |
| 1374 int debug_suspend = 0; | |
| 1375 #endif | |
| 1376 | |
| 1377 | |
| 1378 // Check if ffs has been initialized. Suspend an erase operation. | |
| 1379 effs_t ffs_begin(void) | |
| 1380 { | |
| 1381 #if (TARGET == 0) | |
| 1382 if (debug_suspend > 0) { | |
| 1383 tw(tr(TR_FUNC, TrAll, "FATAL: Previous erase_suspend was not resumed\n")); | |
| 1384 return EFFS_CORRUPTED; | |
| 1385 } | |
| 1386 // tw(tr(TR_FUNC, TrHelper, "Set debug_suspend\n")); | |
| 1387 debug_suspend = 1; | |
| 1388 #endif | |
| 1389 | |
| 1390 if (fs.initerror != EFFS_OK) | |
| 1391 return fs.initerror; | |
| 1392 | |
| 1393 // Suspend an erase in progress (only applicable if we are using a | |
| 1394 // multi-bank device driver) | |
| 1395 if (dev.state == DEV_ERASE) { | |
| 1396 ffsdrv.erase_suspend(); | |
| 1397 } | |
| 1398 else if (dev.state == DEV_WRITE) { | |
| 1399 ffsdrv.write_end(); | |
| 1400 } | |
| 1401 | |
| 1402 return EFFS_OK; | |
| 1403 } | |
| 1404 | |
| 1405 // Resume an erase operation that was in progress. | |
| 1406 int ffs_end(int error) | |
| 1407 { | |
| 1408 #if (TARGET == 1) | |
| 1409 // Resume an erase in progress (only applicable if we are using a | |
| 1410 // multi-bank device driver) | |
| 1411 if (dev.state == DEV_ERASE_SUSPEND) { | |
| 1412 ffsdrv.erase_resume(); | |
| 1413 } | |
| 1414 #else | |
| 1415 debug_suspend = 0; | |
| 1416 #endif | |
| 1417 | |
| 1418 return error; | |
| 1419 } | |
| 1420 | |
| 1421 /****************************************************************************** | |
| 1422 * FFS Statistics functions | |
| 1423 ******************************************************************************/ | |
| 1424 | |
| 1425 // Not implemented: | |
| 1426 int statistics_file_create(void) | |
| 1427 { | |
| 1428 return 0; | |
| 1429 } | |
| 1430 | |
| 1431 // Not implemented: | |
| 1432 // Rewrite the statistics file if it exists. Otherwise return error | |
| 1433 // code. The function is called after each data and inodes reclaim (after | |
| 1434 // writing the file that provoked the reclaim). | |
| 1435 int statistics_write(void) | |
| 1436 { | |
| 1437 return 0; | |
| 1438 } | |
| 1439 | |
| 1440 // Read the statistics file if it exists. Otherwise reset all statistics to | |
| 1441 // zero and set the magic. This function is called from ffs_init(). | |
| 1442 void statistics_init(void) | |
| 1443 { | |
| 1444 memset(&stats, 0, sizeof(struct ffs_stats_s)); | |
| 1445 } | |
| 1446 | |
| 1447 void statistics_update_drec(int valid, int lost, int candidate) | |
| 1448 { | |
| 1449 unsigned int old; | |
| 1450 | |
| 1451 switch (candidate) { | |
| 1452 case MOST_LOST: stats.drec.most_lost++; break; | |
| 1453 case MOST_UNUSED: stats.drec.most_unused++; break; | |
| 1454 case YOUNGEST: stats.drec.youngest++; break; | |
| 1455 } | |
| 1456 | |
| 1457 // Increment Most Significant Word if overflow is detected | |
| 1458 old = stats.drec.valid[0]; | |
| 1459 stats.drec.valid[0] += valid; | |
| 1460 if (old > stats.drec.valid[0]) | |
| 1461 stats.drec.valid[1]++; | |
| 1462 | |
| 1463 old = stats.drec.lost[0]; | |
| 1464 stats.drec.lost[0] += lost; | |
| 1465 if (old > stats.drec.lost[0]) | |
| 1466 stats.drec.lost[1]++; | |
| 1467 } | |
| 1468 | |
| 1469 void statistics_update_irec(int valid, int lost) | |
| 1470 { | |
| 1471 stats.irec.num++; | |
| 1472 stats.irec.valid += valid; | |
| 1473 stats.irec.lost += lost; | |
| 1474 } | |
| 1475 |
