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}