www.pudn.com > efs.rar > fs_pm_log.c
/**********************************************************************
* fs_pm_log.c
*
* File System Log.
* Copyright (C) 2002, 2003, 2004, 2005, 2006 Qualcomm, Inc.
*/
/*===========================================================================
EDIT HISTORY FOR MODULE
This section contains comments describing changes made to the module.
Notice that changes are listed in reverse chronological order.
$Header: //depot/asic/MSMSHARED/services/efs/MSM_EFS.01.02/fs_pm_log.c#6 $ $DateTime: 2006/09/20 14:10:00 $ $Author: davidb $
when who what, where, why
-------- --- ------------------------------------------------------
2006-09-20 dlb Lint cleanup.
2006-09-11 dlb Use flash driver wrappers.
2006-08-23 dlb Disable log write verification by default.
2006-08-23 dlb Zero entire remainder of page.
2006-08-17 dlb New CRC format with in-place upgrades.
2005-12-14 dlb Export log checking.
2005-11-03 dlb Track last written log page.
2004-10-15 dlb Update copyright header.
2003-06-17 jkl Clean up code.
2003-05-22 dlb Add some debugging in unit test.
2003-05-07 dlb Add log grouping.
2003-02-13 dlb Replay logs for Nand operations.
2002-08-08 drh Created by dlb. Added history header. #ifdef out
printf debug statements.
===========================================================================*/
#include
#ifdef FS_UNIT_TEST
#error code not present
#endif
#include "customer.h"
#include "fs_sys_types.h"
#include "fs_err.h"
#include "comdef.h"
#ifdef FEATURE_IG_EFS_EXT_SERVER
#include "amssassert.h"
#else
#include "assert.h"
#endif
#include "fs_pm_log.h"
#include "fs_device.h"
#include "fs_pm_super.h"
#include "fs_upgrade.h"
#include "crc.h"
/* These macros offer some additional hints to the simulation environment
* that we are not rewriting paired bits in this code. */
#ifdef FS_UNIT_TEST
#error code not present
#else
#define IGNORE_PAIR_VIOLATIONS(dev) \
do { } while (0)
#define CHECK_PAIR_VIOLATIONS(dev) \
do { } while (0)
#endif
#define LOG_MAGIC 0x9F11
#define LOG_FIRST_GROUP_OFFSET 8
#define LOG_PAD 4
/* The log page is a byte coded block of data. The first 4 bytes of the
* page are reserved for a state marker. This is used for different
* purposes in different types of flash devices.
*
* Following this is the byte coded data. The top two bits of each code
* indicate how many 32 bit operands follow the byte code. The special
* code LOG_CODE_GROUP_END is followed by a CRC-16 of the previous data
* (not counting the first 4 bytes). Encountering an 0xFF in the data
* stream probably indicates that the log was not successfully written out.
*
* The above LOG_CODE_GROUP_END is used for legacy log pages (which can
* still be read). Log pages will always be written with
* LOG_CODE_EXTENDED_END which is followed by a CRC-16 of the data
* (unlike the LOG_CODE_GROUP_END, this includes the first 4 bytes, skips 4
* bytes of state, and then the rest of the data). The remaining bytes
* following this will be written in a separate write, and must be zero for
* the log page to be considered valid. This makes detection of partially
* written logs more reliable.
*/
#define LOG_CODE_EXTENDED_END 0xFD
#define LOG_END_MARKER 0x00
/* Increment a Queue variable handling the wrapping. */
#define Q_INCR(x) \
do { \
if ((++x) >= FS_LOG_QUEUE_SIZE) \
((x) = 0); \
} while (0)
void
fs_log_init (fs_log_t log, fs_device_t dev)
{
log->dev = dev;
log->q_count = 0;
log->q_free = 0;
log->q_flush = 0;
log->q_alloc = 0;
log->group_offset = LOG_FIRST_GROUP_OFFSET;
log->last_written_log = INVALID_PAGE_ID;
#ifdef FS_UNIT_TEST
#error code not present
#endif
}
int
fs_log_page_count (fs_log_t log)
{
int alloc = log->q_alloc;
int flush = log->q_flush;
if (alloc >= flush)
return alloc - flush;
else
return FS_LOG_QUEUE_SIZE + alloc - flush;
}
int
fs_log_written_count (fs_log_t log)
{
int free = log->q_free;
int flush = log->q_flush;
if (log->q_count == 0)
return 0;
if (flush >= free)
return flush - free;
else
return FS_LOG_QUEUE_SIZE + flush - free;
}
page_id
fs_log_peek_page (fs_log_t log)
{
if (log->q_flush == log->q_alloc)
FS_ERR_FATAL ("Out of log pages to write", 0, 0, 0);
return log->queue[log->q_flush];
}
page_id
fs_log_peek_written (fs_log_t log)
{
if (log->q_count == 0 || log->q_free == log->q_flush)
FS_ERR_FATAL ("No written page!", 0, 0, 0);
return log->queue[log->q_free];
}
page_id
fs_log_get_written (fs_log_t log)
{
page_id result;
if (log->q_count == 0 || log->q_free == log->q_flush)
return INVALID_PAGE_ID;
else {
result = log->queue[log->q_free];
Q_INCR (log->q_free);
log->q_count--;
return result;
}
}
void
fs_log_add_page (fs_log_t log, page_id page)
{
if (log->q_count >= FS_LOG_QUEUE_SIZE)
FS_ERR_FATAL ("Log queue overflow", 0, 0, 0);
log->queue[log->q_alloc] = page;
Q_INCR (log->q_alloc);
log->q_count++;
}
void
fs_log_add_written_page (fs_log_t log, page_id page)
{
if (log->q_flush != log->q_alloc)
FS_ERR_FATAL ("fs_log_add_written_page called after fs_log_add_page",
0, 0, 0);
fs_log_add_page (log, page);
log->q_flush = log->q_alloc;
}
void
fs_log_set_sequence (fs_log_t log, uint32 seqno)
{
if (seqno == 0xFFFFFFFF)
seqno = 0;
log->seqno = seqno;
}
void
fs_log_add (fs_log_t log,
fs_log_code_t code,
uint32 *args)
{
int i, j, k;
int count = FS_LOG_CODE_ARGC (code);
uint32 tmp;
/* Check for overflow, and flush if necessary. The LOG_PAD accounts for
* the bytes needed by the LOG_CODE_GROUP_END and the CRC-16. */
if (log->group_offset + 1 + (4*count) + LOG_PAD > FS_LOG_PAGE_SIZE) {
#ifdef FS_UNIT_TEST
#error code not present
#endif
fs_log_flush (log);
}
#ifdef FS_UNIT_TEST
#error code not present
#endif
/* Add the single entry. */
i = log->group_offset;
log->buffer[i++] = code;
for (j = 0; j < count; j++) {
/* Encode 32 bit value. */
tmp = args[j];
for (k = 0; k < 4; k++) {
log->buffer[i++] = tmp & 0xFF;
tmp >>= 8;
}
}
log->group_offset = i;
}
void
fs_log_group (fs_log_t log, fs_log_code_t a, fs_log_code_t b)
{
if (log->group_offset + 1 + 1
+ (4 * FS_LOG_CODE_ARGC (a))
+ (4 * FS_LOG_CODE_ARGC (b))
+ LOG_PAD
> FS_LOG_PAGE_SIZE)
{
#ifdef FS_UNIT_TEST
#error code not present
#endif
fs_log_flush (log);
}
}
/* Flush the current log page to the device. When this code was originally
* developed, NOR flash devices were written in 16-bit chunks. Newer
* devices use larger write buffers (such as 32-bytes). The CRC check
* reliably detects write failures on the 16-bit writes, but not the larger
* write buffers. The compensate for this, write the log in two pieces,
* with the last chunks containing the END marker coming last. */
void
fs_log_flush (fs_log_t log)
{
uint16 crc;
int i;
int result;
int limit;
unsigned ending;
#ifdef FS_UNIT_TEST
#error code not present
#endif
/* If the group offset is already at the beginning, just return. It is a
* redundant flush, which doesn't hurt anything. */
if (log->group_offset == LOG_FIRST_GROUP_OFFSET)
return;
log->buffer[log->group_offset++] = LOG_CODE_EXTENDED_END;
/* Advance the sequence number. To avoid both all 1's and all 0's as the
* sequence number, we skip these two values. */
log->seqno++;
if (log->seqno == 0xFFFFFFFF)
log->seqno = 1;
/* Set the first 32 bits of the field to the sequence number. */
((uint32 *) log->buffer)[0] = log->seqno;
/* Compute a CRC over the chunk of data here. */
limit = log->group_offset;
crc = CRC_16_L_SEED;
for (i = 0; i < 4; i++)
crc = CRC_16_L_STEP (crc, log->buffer[i]);
for (i = LOG_FIRST_GROUP_OFFSET; i < limit; i++)
crc = CRC_16_L_STEP (crc, log->buffer[i]);
crc = ~crc;
/* Store the CRC so that it can be checked. */
log->buffer[log->group_offset++] = crc & 0xFF;
log->buffer[log->group_offset++] = (crc >> 8) & 0xFF;
/* Set remainder of buffer to the end marker. */
memset (log->buffer + log->group_offset,
LOG_END_MARKER,
FS_LOG_PAGE_SIZE - log->group_offset);
log->group_offset++;
/* The next group of 32 bits is a mask that can have various parts
* cleared to indicate "phases" this log page goes through. */
((uint32 *) log->buffer)[1] = 0xFFFFFFFF;
if (log->q_flush == log->q_alloc)
FS_ERR_FATAL ("Out of log pages to write", 0, 0, 0);
/* If we have partial write in this device, use it to write out the early
* part of the log before the CRC, and write the CRC separately. */
if (log->dev->partial_write != NULL) {
IGNORE_PAIR_VIOLATIONS (log->dev);
/* Write out all but the last piece of the data. The last word is
* written separately, to detect unfinished writes. */
ending = FS_LOG_PAGE_SIZE - 4;
result = fs_flash_partial_write (log->dev,
log->queue[log->q_flush],
log->buffer, 0, ending);
if (result != FS_DEVICE_DONE)
FS_ERR_FATAL ("Error writing log", 0, 0, 0);
/* Write the remaining 4 bytes, possibly containing the CRC and the
* ending marker. */
result = fs_flash_partial_write (log->dev,
log->queue[log->q_flush],
log->buffer + ending, ending, 4);
CHECK_PAIR_VIOLATIONS (log->dev);
} else {
result = fs_flash_write_page (log->dev,
log->queue[log->q_flush],
log->buffer,
FS_FOP_LOG);
}
/* XXX, error handling. */
if (result != FS_DEVICE_DONE)
FS_ERR_FATAL ("Error writing log.", 0, 0, 0);
#ifdef FS_LOG_VERIFY_WRITES
/* This can be defined to verify that log writes happen correctly. This
* doesn't check against bad powerdowns, since obviously this code can't
* run after the power fails. */
{
static uint8 verify_buffer[FS_LOG_PAGE_SIZE];
result = fs_flash_read_page (log->dev,
log->queue[log->q_flush],
verify_buffer,
FS_FOP_LOG);
ASSERT (result == FS_DEVICE_DONE);
ASSERT (memcmp (log->buffer, verify_buffer, log->group_offset) == 0);
}
#endif
log->last_written_log = log->queue[log->q_flush];
Q_INCR (log->q_flush);
/* Prepare to write the next log. */
log->group_offset = LOG_FIRST_GROUP_OFFSET;
#ifdef FS_UNIT_TEST
#error code not present
#endif
}
/* Determine if a region of data matches the LOG_END_MARKER. Used to check
* that the end marker has been properly written to the end of the log. */
static unsigned
fs_log_is_blanked (const uint8 *data, unsigned length)
{
while (length > 0) {
if (*data != LOG_END_MARKER)
return 0;
length--;
data++;
}
return 1;
}
/* XXX: Share this buffer, since it is only happening during upgrade. */
static uint8 fs_log_upgrade_buffer[FS_LOG_PAGE_SIZE];
/* Check the validity of the log. This is a little bit complicated,
* because of the combinations we have due to upgrades, NAND and NOR. We
* have three types of CRCs that are possible.
*
* 1. legacy, not followed by anything in particular.
* 2. legacy, with a LOG_END_MARKER after it.
* 3. New, which always has the LOG_END_MARKER.
*
* For NAND, we always just accept any of these, but for NOR, we need to
* detect case #1, and correct it, only if upgrading. Once the upgrade is
* finished, only #2 and #3 are to be considered valid.
*
* The reasoning here is that NAND already contains an ECC, which will keep
* us from even seeing the log pages that are corrupt. Also, NAND can't be
* corrected with partial writes.
*/
int
fs_log_is_valid (const void *data, fs_device_t dev, page_id upgrade_page)
{
const uint8 *buffer = (const uint8 *) data;
int done = 0;
int offset = LOG_FIRST_GROUP_OFFSET;
int code;
int count;
uint16 crc;
int i;
while (!done) {
if (offset >= FS_LOG_PAGE_SIZE)
return 0;
code = buffer[offset++];
/* Check for a legacy end code. While upgrading, we allow the log
* pages to have a non-zero value after the CRC, which we then force to
* be zero as part of the upgrade. Once the upgrade is finished,
* require the zero. */
if (code == LOG_CODE_GROUP_END) {
/* Check that this isn't off of the end. */
if (offset + 2 > FS_LOG_PAGE_SIZE)
return 0;
/* Verify the CRC. */
crc = CRC_16_L_SEED;
for (i = LOG_FIRST_GROUP_OFFSET; i < offset + 2; i++)
crc = CRC_16_L_STEP (crc, buffer[i]);
crc = ~crc;
if (crc != CRC_16_L_OK) {
/* CRC is invalid. */
return 0;
}
/* Check the log end marker. Only applies to NOR flash. */
if (dev->partial_write != NULL &&
!fs_log_is_blanked (buffer + offset + 2,
FS_LOG_PAGE_SIZE - (offset + 2)))
{
if (fs_upgrade_check (FS_UPGRADE_LOG_ZERO_AFTER_CRC)) {
if (upgrade_page == INVALID_PAGE_ID) {
FS_ERR_FATAL ("Detected upgrade need, but not in right place",
0, 0, 0);
} else {
int result;
unsigned update_base;
/* Write out the LOG_END_MARKER for the entire rest of the
* page. */
/* Round down to the nearest location. */
update_base = (offset + 2) & ~3;
memcpy (fs_log_upgrade_buffer, buffer, offset + 2);
memset (fs_log_upgrade_buffer + offset + 2,
LOG_END_MARKER,
FS_LOG_PAGE_SIZE - (offset + 2));
IGNORE_PAIR_VIOLATIONS (dev);
result = fs_flash_partial_write (dev, upgrade_page,
fs_log_upgrade_buffer + update_base,
update_base,
FS_LOG_PAGE_SIZE - update_base);
CHECK_PAIR_VIOLATIONS (dev);
if (result != FS_DEVICE_DONE)
FS_ERR_FATAL ("Unable to upgrade log page for new version",
0, 0, 0);
}
} else {
/* Not upgrading, so this is not valid. */
return 0;
}
}
done = 1;
} else if (code == LOG_CODE_EXTENDED_END) {
/* Verify the newer CRC, as well as the end marker. */
/* First, make sure the code fits in the rest of the page. */
if (offset + 3 > FS_LOG_PAGE_SIZE)
return 0;
if (!fs_log_is_blanked (buffer + offset + 2,
FS_LOG_PAGE_SIZE - (offset + 2)))
{
/* End marker was not written out successfully. */
return 0;
}
crc = CRC_16_L_SEED;
for (i = 0; i < 4; i++)
crc = CRC_16_L_STEP (crc, buffer[i]);
for (i = LOG_FIRST_GROUP_OFFSET; i < offset + 2; i++)
crc = CRC_16_L_STEP (crc, buffer[i]);
crc = ~crc;
if (crc != CRC_16_L_OK) {
/* The CRC is not correct. */
return 0;
}
done = 1;
} else if (code == 0xFF)
return 0;
else {
count = FS_LOG_CODE_ARGC (code);
offset += 4 * count;
}
}
/* If we made it here, all is well. */
return 1;
}
static int
check_log (fs_log_t log)
{
if (fs_log_is_valid (log->buffer, log->dev, INVALID_PAGE_ID))
return FS_LOG_ITER_GOOD;
else
return FS_LOG_ITER_CORRUPT;
}
int
fs_log_iter_set_page (fs_log_t log, page_id p, uint32 *header)
{
int result;
result = fs_flash_read_page (log->dev, p, log->buffer,
FS_FOP_LOG);
if (result != FS_DEVICE_DONE) {
if (fs_flash_is_erased (log->dev, p))
return FS_LOG_ITER_ERASED;
else
return FS_LOG_ITER_CORRUPT;
}
if (*((uint32 *) log->buffer) == 0xFFFFFFFF)
return FS_LOG_ITER_ERASED;
result = check_log (log);
if (result != FS_LOG_ITER_GOOD)
return result;
log->iter_offset = LOG_FIRST_GROUP_OFFSET;
log->iter_page = p;
if (header != NULL) {
header[0] = ((uint32 *) log->buffer)[0];
header[1] = ((uint32 *) log->buffer)[1];
}
return FS_LOG_ITER_GOOD;
}
page_id
fs_log_iter_peek_page (fs_log_t log)
{
return log->iter_page;
}
void
fs_log_iter_step (fs_log_t log,
fs_log_code_t *code,
uint32 *args)
{
int lcode, i, j, count;
uint32 tmp;
int io;
if (log->iter_page == INVALID_PAGE_ID) {
FS_ERR_FATAL ("Usage error: iter step before setup", 0, 0, 0);
}
io = log->iter_offset;
lcode = log->buffer[io++];
if (lcode == LOG_CODE_GROUP_END || lcode == LOG_CODE_EXTENDED_END) {
/* Return the legacy end code, even for new logs. */
*code = LOG_CODE_GROUP_END;
return;
}
count = FS_LOG_CODE_ARGC (lcode);
for (i = 0; i < count; i++) {
tmp = 0;
for (j = 0; j < 4; j++) {
tmp >>= 8;
tmp |= (log->buffer[io++] << 24);
}
args[i] = tmp;
}
log->iter_offset = io;
*code = lcode;
return;
}
/* Mark the current position, usually called after a transaction start in
* the log. Resetting the log pointer with reset will continue at this
* point. */
void
fs_log_iter_mark (fs_log_t log)
{
log->mark_page = log->iter_page;
log->mark_offset = log->iter_offset;
}
/* Reset the iter pointer to the place it was at with mark. */
void
fs_log_iter_reset (fs_log_t log)
{
if (log->iter_page != log->mark_page) {
fs_log_iter_set_page (log, log->mark_page, NULL);
}
log->iter_offset = log->mark_offset;
}