001package jmri.jmrix.can.cbus.swing.bootloader;
002
003import static jmri.jmrix.can.cbus.CbusConstants.MANU_MERG;
004import static jmri.jmrix.can.cbus.node.CbusNodeConstants.*;
005
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Extend hex file class for a CBUS PIC with parameter block
011 *
012 * Assumes hex record addresses are "nicely" aligned, i.e., on 8, 16, 32, ...
013 * -byte boundaries and that addresses increase monotonically. With Microchip
014 * tools you should select the options to format the hex file for download and
015 * program with default config words.
016 *
017 * @author Andrew Crosland Copyright (C) 2022
018 */
019public class CbusPicHexFile extends HexFile{
020
021    public static final int PARAM_OLD_START = 0x810;
022    public static final int PARAM_OLD_END = 0x817;
023    public static final int PARAM_OLD_LEN = 7;
024    public static final int PARAM_NEW_START = 0x820;
025    public static final int PARAM_NEW_END = 0x840;
026    public static final int PARAM_NEW_LEN = 32;
027
028    private CbusParameters newParams = new CbusParameters();
029    private CbusParameters oldParams = new CbusParameters();
030
031    private boolean checkValid = false;
032    // Running 16-bit sum of the new style parameter block bytes 1 - 30,
033    // accumulated over all of the hex records that make up the block
034    private int paramChecksum = 0;
035
036    /**
037     * Create a new HexFile object and initialize data to unprogrammed state.
038     *
039     * @param fileName file name to use for the hex file
040     */
041    public CbusPicHexFile(String fileName) {
042        super(fileName);
043    }
044
045
046    /**
047     * Check one hex record from the file
048     *
049     * Overridden to capture CBUS PIC parameter block.
050     *
051     * Assumes hex record addresses are "nicely" aligned, i.e., on 8, 16, 32, ...
052     * -byte boundaries and that addresses increase monotonically. With Microchip
053     * tools you should select the options to format the hex file for download
054     * and program with default config words.
055     *
056     * @param r hex record
057     */
058    @Override
059    protected void checkRecord(HexRecord r) {
060        int rStart = 0;
061        int rEnd = 0;
062        int pStart = 0;
063
064        if (r.type == HexRecord.END) {
065            // Parameter block may be the last thing in the file
066            if (checkValid) {
067                checkParams();
068            }
069            return;
070        }
071
072        if (r.type == HexRecord.TYPE_DATA) {
073            // Look for "old" 7-byte paraneter block at 0x810, assuming aligned hex address
074            if ((r.address == PARAM_OLD_START) && (r.len >= PARAM_OLD_LEN)) {
075                // Parameter 1 is the first byte of the block, index 0 is the count
076                pStart = 1;
077                for (int i = 0; i < PARAM_OLD_LEN; i++) {
078                    oldParams.setParam(pStart++, r.getData(i) & 0xFF);
079                }
080                checkValid = true;
081            } else {
082                // Look for "new" 32-byte parameter block starting at 0x820
083                if ((r.address < PARAM_NEW_START) && (r.address + r.len > PARAM_NEW_START)) {
084                    // Record overlaps at start. Should only happen if record is greater than 32 bytes
085                    // and encompasses the whole parameter block.
086                    rStart = PARAM_NEW_START - r.address;
087                    pStart = 1;
088                    if ((r.len - rStart) > PARAM_NEW_LEN) {
089                        rEnd = rStart + PARAM_NEW_LEN;
090                    } else {
091                        rEnd = r.len;
092                    }
093                    checkValid = true;
094                } else if ((r.address >= PARAM_NEW_START) && (r.address < PARAM_NEW_END)) {
095                    // Record starts within the parameter block. Should only happen for
096                    // 32, 16 or 8-byte records and we want all the data in the record.
097                    rStart = 0;
098                    pStart = r.address - PARAM_NEW_START + 1;
099                    if ((pStart - 1 + r.len) > PARAM_NEW_LEN) {
100                        rEnd = PARAM_NEW_LEN - (pStart - 1);
101                    } else {
102                        rEnd = r.len;
103                    }
104                    checkValid = true;
105                }
106
107                if (pStart == 1) {
108                    // Start of a new parameter block
109                    paramChecksum = 0;
110                }
111
112                if (checkValid && (pStart > 0)) {
113                    // Copy data to parameters and add to checksum
114                    for (int i = rStart; i < rEnd; i++) {
115                        int v = r.getData(i) & 0xFF;
116                        newParams.setParam(pStart, v);
117                        if (pStart < PARAM_CHECK_IDX) {
118                            // Checksum covers parameters 1 - 30
119                            paramChecksum += v;
120                        }
121                        pStart++;
122                    }
123                }
124            }
125
126            if ((r.address >= PARAM_NEW_END) && (checkValid == true)) {
127                // First record after parameter block so check if parameters are valid
128                checkParams();
129            }
130        }
131    }
132
133
134    /**
135     * Check the parameters captured from the hex file, once the whole
136     * parameter block has been read.
137     * <p>
138     * The last two bytes of the new style parameter block hold a 16-bit
139     * (little endian) checksum which is the sum of the parameter bytes 1 - 30.
140     * If that does not match, fall back to an old style block at 0x810, which
141     * has no checksum, and is assumed to be valid only if it appears to be a
142     * MERG module.
143     */
144    private void checkParams() {
145        checkValid = false;
146        // Copy new parameter count to parameter
147        newParams.setParam(NUM_PARAM_IDX, newParams.getParam(PARAM_COUNT_IDX));
148        if (newParamChecksumValid()) {
149            log.debug("Valid new style parameter block found");
150            newParams.setValid(true);
151        } else if (oldParams.getParam(MANU_ID_IDX) == MANU_MERG) {
152            // Assume old style parameter block @ 0x810 and assume only MERG made these
153            log.debug("Assuming old style parameter block");
154            oldParams.setParam(NUM_PARAM_IDX, PARAM_OLD_LEN);
155            oldParams.setValid(true);
156        } else {
157            log.debug("No valid parameter block found");
158        }
159    }
160
161
162    /**
163     * Check the checksum of the new style parameter block.
164     * <p>
165     * The 16-bit sum of parameter bytes 1 - 30 must equal the little endian
166     * 16-bit value stored in parameters 31 - 32. An all zero block, which
167     * would trivially pass, is not accepted.
168     *
169     * @return true if the checksum is valid
170     */
171    private boolean newParamChecksumValid() {
172        int lo = newParams.getParam(PARAM_CHECK_IDX);
173        int hi = newParams.getParam(PARAM_CHECK_IDX + 1);
174        if ((lo < 0) || (hi < 0)) {
175            // Checksum bytes were not read
176            return false;
177        }
178        int stored = lo + (hi * 256);
179        log.debug("Parameter checksum calculated {} stored {}", paramChecksum & 0xFFFF, stored);
180        return (stored != 0) && ((paramChecksum & 0xFFFF) == stored);
181    }
182
183
184    /**
185     * Get the file parameters
186     *
187     * Create an invalid parameter set of necessary
188     *
189     * @return CBUS parameters from the file
190     */
191    @Override
192    public CbusParameters getParams() {
193        if (newParams.areValid()) {
194            return newParams;
195        } else if (oldParams.areValid()) {
196            return oldParams;
197        } else
198            return new CbusParameters();
199    }
200
201    private static final Logger log = LoggerFactory.getLogger(CbusPicHexFile.class);
202
203}