Example #1
0
        /// <summary>
        /// Erase a block in the flash memory.
        /// </summary>
        private async Task <bool> EraseMemoryRange(MemoryRange range, CancellationToken cancellationToken)
        {
            this.logger.AddUserMessage("Erasing.");

            await this.vehicle.SetDeviceTimeout(TimeoutScenario.EraseMemoryBlock);

            Query <byte> eraseRequest = this.vehicle.CreateQuery <byte>(
                () => this.protocol.CreateFlashEraseBlockRequest(range.Address),
                this.protocol.ParseFlashEraseBlock,
                cancellationToken);

            eraseRequest.MaxTimeouts = 3;
            Response <byte> eraseResponse = await eraseRequest.Execute();

            if (eraseResponse.Status != ResponseStatus.Success)
            {
                this.logger.AddUserMessage("Unable to erase flash memory: " + eraseResponse.Status.ToString());
                this.RequestDebugLogs(cancellationToken);
                return(false);
            }

            if (eraseResponse.Value != 0x00)
            {
                this.logger.AddUserMessage("Unable to erase flash memory. Code: " + eraseResponse.Value.ToString("X2"));
                this.RequestDebugLogs(cancellationToken);
                return(false);
            }

            return(true);
        }
Example #2
0
        private bool ShouldProcess(MemoryRange range, BlockType relevantBlocks)
        {
            if ((range.ActualCrc == range.DesiredCrc) && (this.writeType != WriteType.TestWrite))
            {
                return(false);
            }

            // Skip irrelevant blocks.
            if ((range.Type & relevantBlocks) == 0)
            {
                return(false);
            }

            return(true);
        }
Example #3
0
        /// <summary>
        /// Copy a single memory range to the PCM.
        /// </summary>
        private async Task <bool> WriteMemoryRange(
            MemoryRange range,
            byte[] image,
            bool justTestWrite,
            CancellationToken cancellationToken)
        {
            int devicePayloadSize = vehicle.DeviceMaxSendSize - 12; // Headers use 10 bytes, sum uses 2 bytes.

            for (int index = 0; index < range.Size; index += devicePayloadSize)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return(false);
                }

                await this.vehicle.SendToolPresentNotification();

                int startAddress    = (int)(range.Address + index);
                int thisPayloadSize = Math.Min(devicePayloadSize, (int)range.Size - index);

                Message payloadMessage = protocol.CreateBlockMessage(
                    image,
                    startAddress,
                    thisPayloadSize,
                    startAddress,
                    justTestWrite ? BlockCopyType.TestWrite : BlockCopyType.Copy);

                logger.AddUserMessage(
                    string.Format(
                        "Sending payload with offset 0x{0:X4}, start address 0x{1:X6}, length 0x{2:X4}.",
                        index,
                        startAddress,
                        thisPayloadSize));

                await this.vehicle.WritePayload(payloadMessage, cancellationToken);

                // Not checking the success or failure here.
                // The debug pane will show if anything goes wrong, and the CRC check at the end will alert the user.
                // TODO: log errors during test writes!
            }

            return(true);
        }
Example #4
0
        /// <summary>
        /// Copy a single memory range to the PCM.
        /// </summary>
        private async Task <Response <bool> > WriteMemoryRange(
            MemoryRange range,
            byte[] image,
            bool justTestWrite,
            DateTime startTime,
            UInt32 totalSize,
            UInt32 bytesRemaining,
            CancellationToken cancellationToken)
        {
            int retryCount        = 0;
            int devicePayloadSize = vehicle.DeviceMaxFlashWriteSendSize - 12; // Headers use 10 bytes, sum uses 2 bytes.

            for (int index = 0; index < range.Size; index += devicePayloadSize)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return(Response.Create(ResponseStatus.Cancelled, false, retryCount));
                }

                await this.vehicle.SendToolPresentNotification();

                int    startAddress    = (int)(range.Address + index);
                UInt32 thisPayloadSize = (UInt32)Math.Min(devicePayloadSize, (int)range.Size - index);

                logger.AddDebugMessage(
                    string.Format(
                        "Sending payload with offset 0x{0:X4}, start address 0x{1:X6}, length 0x{2:X4}.",
                        index,
                        startAddress,
                        thisPayloadSize));

                Message payloadMessage = protocol.CreateBlockMessage(
                    image,
                    startAddress,
                    (int)thisPayloadSize,
                    startAddress,
                    justTestWrite ? BlockCopyType.TestWrite : BlockCopyType.Copy);

                string timeRemaining;

                TimeSpan elapsed      = DateTime.Now - startTime;
                UInt32   totalWritten = totalSize - bytesRemaining;

                // Wait 10 seconds before showing estimates.
                if (elapsed.TotalSeconds < 10)
                {
                    timeRemaining = "Measuring write speed...";
                }
                else
                {
                    UInt32 bytesPerSecond = (UInt32)(totalWritten / elapsed.TotalSeconds);

                    // Don't divide by zero.
                    if (bytesPerSecond > 0)
                    {
                        UInt32 secondsRemaining = (UInt32)(bytesRemaining / bytesPerSecond);
                        timeRemaining = TimeSpan.FromSeconds(secondsRemaining).ToString("mm\\:ss");
                    }
                    else
                    {
                        timeRemaining = "??:??";
                    }
                }

                logger.AddUserMessage(
                    string.Format(
                        "0x{0:X6}\t{1}%\t{2}",
                        startAddress,
                        totalWritten * 100 / totalSize,
                        timeRemaining));

                await this.vehicle.SetDeviceTimeout(TimeoutScenario.WriteMemoryBlock);

                // WritePayload contains a retry loop, so if it fails, we don't need to retry at this layer.
                Response <bool> response = await this.vehicle.WritePayload(payloadMessage, cancellationToken);

                if (response.Status != ResponseStatus.Success)
                {
                    return(Response.Create(ResponseStatus.Error, false, response.RetryCount));
                }

                bytesRemaining -= thisPayloadSize;
                retryCount     += response.RetryCount;
            }

            return(Response.Create(ResponseStatus.Success, true, retryCount));
        }
Example #5
0
 private bool ShouldProcess(MemoryRange range, BlockType relevantBlocks)
 {
     return(true);
 }
Example #6
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="chipId"></param>
        /// <returns></returns>
        public static IList <MemoryRange> GetMemoryRanges(UInt32 chipId, ILogger logger)
        {
            IList <MemoryRange> result = null;

            switch (chipId)
            {
            // This is only here as a warning to anyone adding ranges for another chip.
            // Please read the comments carefully. See case 0x00894471 for the real deal.
            case 0xFFFF4471:
                var unused = new MemoryRange[]
                {
                    // These numbers and descriptions are straight from the intel 28F400B data sheet.
                    // Notice that if you convert the 16 bit word sizes to decimal, they're all
                    // half as big as the description here, in bytes, indicates.
                    new MemoryRange(0x30000, 0x10000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x20000, 0x10000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x10000, 0x10000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x04000, 0x0C000, BlockType.Calibration),     //  96kb main block
                    new MemoryRange(0x03000, 0x01000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x02000, 0x01000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x00000, 0x02000, BlockType.Boot),            //  16kb boot block
                };
                return(null);

            // Intel 28F800B
            case 0x0089889D:
                result = new MemoryRange[]
                {
                    // These addresses are for a bottom fill chip (B) in byte mode (not word)
                    new MemoryRange(0xE0000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0xC0000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0xA0000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x80000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x60000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x40000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x20000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x08000, 0x18000, BlockType.Calibration),     //  96kb main block
                    new MemoryRange(0x06000, 0x02000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x04000, 0x02000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x00000, 0x04000, BlockType.Boot),            //  16kb boot block
                };
                break;

            // Intel 28F400B
            case 0x00894471:
                result = new MemoryRange[]
                {
                    // These addresses are for a bottom fill chip (B) in byte mode (not word)
                    new MemoryRange(0x60000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x40000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x20000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x08000, 0x18000, BlockType.Calibration),     //  96kb main block
                    new MemoryRange(0x06000, 0x02000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x04000, 0x02000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x00000, 0x04000, BlockType.Boot),            //  16kb boot block
                };
                break;

            case 0x00012258:
                result = new MemoryRange[]
                {
                    new MemoryRange(0xF0000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0xE0000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0xD0000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0xC0000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0xB0000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0xA0000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x90000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x80000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x70000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x60000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x50000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x40000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x30000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x20000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x10000, 0x10000, BlockType.Calibration),     //  64kb calibration block
                    new MemoryRange(0x08000, 0x08000, BlockType.Calibration),     //  32kb calibration block
                    new MemoryRange(0x06000, 0x02000, BlockType.Parameter),       //  8kb parameter block
                    new MemoryRange(0x04000, 0x02000, BlockType.Parameter),       //  8kb parameter block
                    new MemoryRange(0x00000, 0x04000, BlockType.Boot),            //  16kb boot block
                };
                break;

            default:
                logger.AddUserMessage(
                    "Unsupported flash chip ID " + chipId.ToString("X8") + ". " +
                    Environment.NewLine +
                    "The flash memory in this PCM is not supported by this version of PCM Hammer." +
                    Environment.NewLine +
                    "Please look for a thread about this at pcmhacking.net, or create one if necessary." +
                    Environment.NewLine +
                    "We do aim to support for all flash chips over time.");
                return(null);
            }

            // Sanity check the memory ranges
            UInt32 lastStart = UInt32.MaxValue;

            for (int index = 0; index < result.Count; index++)
            {
                if (index == 0)
                {
                    UInt32 top = result[index].Address + result[index].Size;
                    if ((top != 512 * 1024) && (top != 1024 * 1024))
                    {
                        throw new InvalidOperationException("Upper end of memory range must be 512k or 1024k.");
                    }
                }

                if (index == result.Count - 1)
                {
                    if (result[index].Address != 0)
                    {
                        throw new InvalidOperationException("Memory ranges must start at zero.");
                    }
                }

                if (lastStart != UInt32.MaxValue)
                {
                    if (lastStart != result[index].Address + result[index].Size)
                    {
                        throw new InvalidDataException("Top of this range must match base of range above.");
                    }
                }

                lastStart = result[index].Address;
            }

            return(result);
        }
Example #7
0
        /// <summary>
        /// Factory method. Selects the memory configuration, size, and description.
        /// </summary>
        public static FlashChip Create(UInt32 chipId, ILogger logger)
        {
            IList <MemoryRange> memoryRanges = null;
            string description;
            UInt32 size;

            switch (chipId)
            {
            // This is only here as a warning to anyone adding ranges for another chip.
            // Please read the comments carefully. See case 0x00894471 for the real data.
            case 0xFFFF4471:
                var unused = new MemoryRange[]
                {
                    // These numbers and descriptions are straight from the intel 28F400B data sheet.
                    // Notice that if you convert the 16 bit word sizes to decimal, they're all
                    // half as big as the description here, in bytes, indicates.
                    new MemoryRange(0x30000, 0x10000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x20000, 0x10000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x10000, 0x10000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x04000, 0x0C000, BlockType.Calibration),     //  96kb main block
                    new MemoryRange(0x03000, 0x01000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x02000, 0x01000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x00000, 0x02000, BlockType.Boot),            //  16kb boot block
                };
                throw new InvalidOperationException("This flash chip ID was not supposed to exist in the wild.");

            // Intel 28F800B
            case 0x0089889D:
                size         = 1024 * 1024;
                description  = "Intel 28F800B, 1mb";
                memoryRanges = new MemoryRange[]
                {
                    // These addresses are for a bottom fill chip (B) in byte mode (not word)
                    new MemoryRange(0xE0000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0xC0000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0xA0000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x80000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x60000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x40000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x20000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x08000, 0x18000, BlockType.Calibration),     //  96kb main block
                    new MemoryRange(0x06000, 0x02000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x04000, 0x02000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x00000, 0x04000, BlockType.Boot),            //  16kb boot block
                };
                break;

            // Intel 28F400B
            case 0x00894471:
                size         = 512 * 1024;
                description  = "Intel 28F400B, 512kb";
                memoryRanges = new MemoryRange[]
                {
                    // These addresses are for a bottom fill chip (B) in byte mode (not word)
                    new MemoryRange(0x60000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x40000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x20000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x08000, 0x18000, BlockType.Calibration),     //  96kb main block
                    new MemoryRange(0x06000, 0x02000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x04000, 0x02000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x00000, 0x04000, BlockType.Boot),            //  16kb boot block
                };
                break;

            // AM29F800BB
            case 0x00012258:
                size         = 1024 * 1024;
                description  = "AMD AM29F800BB, 1mb";
                memoryRanges = new MemoryRange[]
                {
                    new MemoryRange(0xF0000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0xE0000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0xD0000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0xC0000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0xB0000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0xA0000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x90000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x80000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x70000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x60000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x50000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x40000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x30000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x20000, 0x10000, BlockType.OperatingSystem), //  64kb main block
                    new MemoryRange(0x10000, 0x10000, BlockType.Calibration),     //  64kb calibration block
                    new MemoryRange(0x08000, 0x08000, BlockType.Calibration),     //  32kb calibration block
                    new MemoryRange(0x06000, 0x02000, BlockType.Parameter),       //  8kb parameter block
                    new MemoryRange(0x04000, 0x02000, BlockType.Parameter),       //  8kb parameter block
                    new MemoryRange(0x00000, 0x04000, BlockType.Boot),            //  16kb boot block
                };
                break;

            // IPC
            case 0x4D696B65:
                size         = 16 * 1024;
                description  = "IPC Calibration";
                memoryRanges = new MemoryRange[]
                {
                    // These addresses are for a bottom fill chip (B) in byte mode (not word)
                    new MemoryRange(0x00000, 0x04000, BlockType.OperatingSystem),     // 128kb main block
                };
                break;

            // IPC1
            case 0x6D696B65:
                size         = 112 * 1024;
                description  = "IPC OS + Calibration";
                memoryRanges = new MemoryRange[]
                {
                    new MemoryRange(0x00000, 0x1C000, BlockType.OperatingSystem),
                };
                break;


            // Both of these have eight 8kb blocks at the low end, the rest are
            // 64kb. Not sure if they're actually used in any PCMs though.
            case 0x00898893:     // Intel 2F008B3
            case 0x008988C1:     // Intel 2F800C3
            default:
                string manufacturer;

                switch ((chipId >> 16))
                {
                case 0x0001:
                    manufacturer = "AMD";
                    break;

                case 0x0089:
                    manufacturer = "Intel";
                    break;

                default:
                    manufacturer = "Unknown";
                    break;
                }

                logger.AddUserMessage(
                    "Unsupported flash chip ID " + chipId.ToString("X8") + ". Manufacturer: " + manufacturer +
                    Environment.NewLine +
                    "The flash memory in this PCM is not supported by this version of PCM Hammer." +
                    Environment.NewLine +
                    "Please look for a thread about this at pcmhacking.net, or create one if necessary." +
                    Environment.NewLine +
                    "We do aim to add support for all flash chips eventually.");
                throw new ApplicationException();
            }

            // Sanity check the memory ranges;
            UInt32 lastStart    = UInt32.MaxValue;
            string chipIdString = chipId.ToString("X8");

            for (int index = 0; index < memoryRanges.Count; index++)
            {
                if (index == 0)
                {
                    UInt32 top = memoryRanges[index].Address + memoryRanges[index].Size;
                    if ((top != 16 * 1024) && (top != 112 * 1024) && (top != 512 * 1024) && (top != 1024 * 1024))
                    {
                        throw new InvalidOperationException(chipIdString + " - Upper end of memory range must be 512k or 1024k, is " + top.ToString("X8"));
                    }

                    if (size != top)
                    {
                        throw new InvalidOperationException(chipIdString + " - Size does not match upper memory block.");
                    }
                }

                if (index == memoryRanges.Count - 1)
                {
                    if (memoryRanges[index].Address != 0)
                    {
                        throw new InvalidOperationException(chipIdString + " - Memory ranges must start at zero.");
                    }
                }

                if (lastStart != UInt32.MaxValue)
                {
                    if (lastStart != memoryRanges[index].Address + memoryRanges[index].Size)
                    {
                        throw new InvalidDataException(chipIdString + " - Top of range " + index + " must match base of range above.");
                    }
                }

                lastStart = memoryRanges[index].Address;
            }

            return(new FlashChip(chipId, description, size, memoryRanges));
        }
Example #8
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="chipId"></param>
        /// <returns></returns>
        public IList <MemoryRange> GetMemoryRanges(UInt32 chipId)
        {
            IList <MemoryRange> result = null;

            switch (chipId)
            {
            // This is only here as a warning to anyone adding ranges for another chip.
            // Please read the comments carefully. See case 0x00894471 for the real deal.
            case 0xFFFF4471:
                var unused = new MemoryRange[]
                {
                    // These numbers and descriptions are straight from the data sheet.
                    // Notice that if you convert the hex sizes to decimal, they're all
                    // half as big as the description indicates. That's wrong. It doesn't
                    // work that way in the PCM, so this would only compare 256 kb.
                    new MemoryRange(0x30000, 0x10000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x20000, 0x10000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x10000, 0x10000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x04000, 0x0C000, BlockType.Calibration),     //  96kb main block
                    new MemoryRange(0x03000, 0x01000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x02000, 0x01000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x00000, 0x02000, BlockType.Boot),            //  16kb boot block
                };
                return(null);

            // Intel 28F400B
            case 0x00894471:
                result = new MemoryRange[]
                {
                    // All of these addresses and sizes are all 2x what's listed
                    // in the data sheet, because the data sheet table assumes that
                    // "bytes" are 16 bits wide. Which means they're not bytes. But
                    // the data sheet calls them bytes.
                    new MemoryRange(0x60000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x40000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x20000, 0x20000, BlockType.OperatingSystem), // 128kb main block
                    new MemoryRange(0x08000, 0x18000, BlockType.Calibration),     //  96kb main block
                    new MemoryRange(0x06000, 0x02000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x04000, 0x02000, BlockType.Parameter),       //   8kb parameter block
                    new MemoryRange(0x00000, 0x04000, BlockType.Boot),            //  16kb boot block
                };
                break;

            default:
                this.logger.AddUserMessage(
                    "Unsupported flash chip ID " + chipId.ToString("X8") + ". " +
                    Environment.NewLine +
                    "The flash memory in this PCM is not supported by this version of PCM Hammer." +
                    Environment.NewLine +
                    "Please look for a thread about this at pcmhacking.net, or create one if necessary." +
                    Environment.NewLine +
                    "We do aim to support for all flash chips over time.");
                return(null);
            }

            // Sanity check the memory ranges
            UInt32 lastStart = UInt32.MaxValue;

            for (int index = 0; index < result.Count; index++)
            {
                if (index == 0)
                {
                    UInt32 top = result[index].Address + result[index].Size;
                    if ((top != 512 * 1024) && (top != 1024 * 1024))
                    {
                        throw new InvalidOperationException("Upper end of memory range must be 512k or 1024k.");
                    }
                }

                if (index == result.Count - 1)
                {
                    if (result[index].Address != 0)
                    {
                        throw new InvalidOperationException("Memory ranges must start at zero.");
                    }
                }

                if (lastStart != UInt32.MaxValue)
                {
                    if (lastStart != result[index].Address + result[index].Size)
                    {
                        throw new InvalidDataException("Top of this range must match base of range above.");
                    }
                }

                lastStart = result[index].Address;
            }

            return(result);
        }