Recently I implemented a self-reprogramming feature for an AVR XMEGA-E5 cpu. Naturally one wants to have some validation logic to make sure that what was burned to the flash is right. This is a common want, so Atmel implemented in hardware a chunk of logic to compute the CRC checksum of a block of flash memory. This is nice. What is less nice is their documentation, which is lacking to put it nicely. To use their hardware CRC, I need to make sure it matches a CRC that I compute in software. This was needlessly difficult, and hopefully this will serve as a guide for somebody in the future.

The AVR supports a CRC16 checksum (CCITT) and a CRC32 checksum (IEEE 802.3). I use the bigger 32-bit checksum here.

The XMEGA E MANUAL describes in section "29.11.2.6" how to compute the CRC of a block of flash memory. It says

The CRC checksum will be available in the NVM DATA register

This is a lie. On my partiular CPU, there are 24 bits worth of NVM.DATA, so this cannot be true for a 32-bit checksum. The checksum really ends up in the CRC.CHECKSUM registers.

In Section 24.3 the manual says

when CRC-32 polynomial is used, the final checksum read is bit reversed and complemented

I'm not 100% sure what's going on here; I think that "bit reversed and complemented" simply means "binary NOT", but I'm not certain. In any case, there also seems to be a difference in behavior if we're checksumming flash memory (CRC_SOURCE_FLASH_gc) or if we're looking at CRC.DATAIN input (CRC_SOURCE_IO_gc).

By default, the CRC module resets from zero. However to match other CRC32 implementations, it is apparently necessary to reset from 1 instead (CRC_RESET_RESET1_gc). Oh, and this reset must happen before the CRC module is turned on.

Section 24.7.2 says

When running CRC-32 and appending the checksum at the end
of the packet (as little endian), the final checksum should be 0x2144df1c, and
not zero. However, if the checksum is complemented before it is appended (as
little endian) to the data, the final result in the checksum register will be
zero

This is a lie also. The constant 0x2144df1c is indeed there. However if you append a complemented (meaning binary NOT instead of a 2-complement) CRC then you get 0xFFFFFFFF and not 0.

So in the end, if I have a flash of size SIZE_APP_FLASH_BYTES, then I compute the CRC32 of the first SIZE_APP_FLASH_BYTES-4 bytes, and append a binary NOT of this CRC to the end. I can then validate by making sure that this CRC matches 0xFFFFFFFF. On the AVR:

bool CRC_matches(void)
{
    CRC.CTRL |= CRC_RESET_RESET1_gc;
    CRC.CTRL  = CRC_CRC32_bm | CRC_SOURCE_FLASH_gc;
    nvm_issue_flash_range_crc(0, SIZE_APP_FLASH_BYTES-1);
    nvm_wait_until_ready();
    while( CRC.STATUS & CRC_BUSY_bm ) ;

    return
      CRC.CHECKSUM0 == '\xFF' &&
      CRC.CHECKSUM1 == '\xFF' &&
      CRC.CHECKSUM2 == '\xFF' &&
      CRC.CHECKSUM3 == '\xFF';
}

When preparing this image, I have perl pad, compute the CRC, binary-NOT and append:

use Digest::CRC 'crc32';

sub prepareImage
{
    my ($rawimage) = @_;

    my $Npad = $SIZE_APP_FLASH_BYTES-4 - length($rawimage);
    my $ff   = pack('C', 0xFF);
    my $pad  = $ff x $Npad;

    my $crc = crc32($rawimage . $pad);
    return $rawimage . $pad . pack('V', 0xFFFFFFFF & ~$crc);
}