/*******************************************************************************
* Copyright (c) 2008, 2011 Thomas Holland (thomas@innot.de) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Thomas Holland - initial API and implementation
*******************************************************************************/
package de.innot.avreclipse.core.toolinfo.partdescriptionfiles;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import de.innot.avreclipse.AVRPlugin;
import de.innot.avreclipse.core.toolinfo.fuses.BitFieldDescription;
import de.innot.avreclipse.core.toolinfo.fuses.BitFieldValueDescription;
import de.innot.avreclipse.core.toolinfo.fuses.ByteDescription;
import de.innot.avreclipse.core.toolinfo.fuses.FuseType;
import de.innot.avreclipse.core.toolinfo.fuses.Fuses;
import de.innot.avreclipse.core.toolinfo.fuses.IMCUDescription;
import de.innot.avreclipse.core.toolinfo.fuses.MCUDescription;
import de.innot.avreclipse.core.util.AVRMCUidConverter;
/**
* Fuses info reader.
* <p>
* This Class will take a Atmel Device XML Document and read either the Fuse Byte or the Lockbit
* settings from it. What to read is determined by the subclass.
* </p>
* <p>
* The definitions of Fuses and Lockbits are very similar, therefore this class does most of the
* parsing and the subclasses just need to supply a few informations.
* </p>
* <p>
*
* </p>
*
* @author Thomas Holland
* @since 2.2
*/
public class FusesReader extends BaseReader {
private final static String FILE_POSTFIX = ".desc";
private final static String ELEM_REGISTER_GROUP = "register-group";
private final static String ELEM_REGISTER = "register";
private final static String ELEM_BITFIELD = "bitfield";
private final static String ELEM_VALUE_GROUP = "value-group";
private final static String ELEM_VALUE = "value";
private final static String ATTR_CAPTION = "caption";
private final static String ATTR_TEXT = "text";
private final static String ATTR_OFFSET = "offset";
private final static String ATTR_SIZE = "size";
private final static String ATTR_MASK = "mask";
private final static String ATTR_VALUES = "values";
private final static String ATTR_VALUE = "value";
/** List of all Fuses Descriptions */
private Map<String, MCUDescription> fFuseDescriptions;
// FIXME: debugging only
File fFile;
/*
* (non-Javadoc)
* @see de.innot.avreclipse.core.toolinfo.partdescriptionfiles.BaseReader#start()
*/
public void start() {
fFuseDescriptions = new HashMap<String, MCUDescription>();
}
/*
* (non-Javadoc)
* @see
* de.innot.avreclipse.core.toolinfo.partdescriptionfiles.BaseReader#parse(org.w3c.dom.Document)
*/
@Override
public void parse(Document document, File sourcefile) {
fFile = sourcefile;
// initialize the description object we will fill with data
MCUDescription desc = new MCUDescription(fMCUid);
// Read all <value-group> nodes and store them in a hashmap indexed by their name for easy
// access later on.
Map<String, Node> bitfieldValueNodes = new HashMap<String, Node>();
NodeList valueGroupList = document.getElementsByTagName(ELEM_VALUE_GROUP);
for (int i = 0; i < valueGroupList.getLength(); i++) {
Node node = valueGroupList.item(i);
Node nameAttr = node.getAttributes().getNamedItem(ATTR_NAME);
String name = nameAttr.getNodeValue();
name = valueGroupNameFixer(desc, name);
bitfieldValueNodes.put(name, node);
}
// Get all <register-group> nodes and filter those with an attribute of either
// name="*FUSE*", i.e. "FUSE", "FUSES" or "NVM_FUSES"
NodeList registerGroupList = document.getElementsByTagName(ELEM_REGISTER_GROUP);
for (int i = 0; i < registerGroupList.getLength(); i++) {
Node registerGroupNode = registerGroupList.item(i);
NamedNodeMap attributesMap = registerGroupNode.getAttributes();
Node nameAttributeNode = attributesMap.getNamedItem(ATTR_NAME);
String nameValue = nameAttributeNode.getNodeValue();
if (nameValue.contains("FUSE")) {
readRegisters(registerGroupNode, desc, FuseType.FUSE, bitfieldValueNodes);
}
if (nameValue.contains("LOCKBIT")) {
readRegisters(registerGroupNode, desc, FuseType.LOCKBITS, bitfieldValueNodes);
}
}
// and the build version and the status
setVersion(sourcefile, desc);
// Add the description object to the internal list of all
// descriptions.
fFuseDescriptions.put(fMCUid, desc);
}
/*
* (non-Javadoc)
* @see de.innot.avreclipse.core.toolinfo.partdescriptionfiles.IPDFreader#finish()
*/
public void finish() {
// The FuseDescription Objects are written as xml files to the plugin storage area.
// get the location where the descriptions will be written to.
IPath location = getStoragePath();
// Create the fusedesc folder if necessary
File folder = location.toFile();
if (!folder.isDirectory()) {
// Create the folder
if (!folder.mkdirs()) {
IStatus status = new Status(Status.ERROR, AVRPlugin.PLUGIN_ID,
"Can't create fusedesc folder [" + folder.toString() + "]", null);
AVRPlugin.getDefault().log(status);
// TODO Throw an Exception
return;
}
}
// Now serialize all FuseDescription Objects to the storage area
Set<String> allmcus = fFuseDescriptions.keySet();
for (String mcuid : allmcus) {
// Generate a filename: "mcuid.desc"
File file = location.append(mcuid + FILE_POSTFIX).toFile();
FileOutputStream fos = null;
MCUDescription fusesdesc = fFuseDescriptions.get(mcuid);
// Create a blank XML DOM document...
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder parser = factory.newDocumentBuilder();
Document document = parser.newDocument();
// Add a few comments to the document
Comment comment = document
.createComment("Fuse/Lockbit description file for the AVR Eclipse plugin");
document.appendChild(comment);
comment = document
.createComment("Author: automatically created by AVR Eclipse plugin");
document.appendChild(comment);
comment = document.createComment("Date: "
+ new SimpleDateFormat().format(new Date()));
document.appendChild(comment);
comment = document.createComment("Based on: Atmel Device File \""
+ AVRMCUidConverter.id2name(mcuid) + ".xml\"");
document.appendChild(comment);
// And now add the Actual Description to the document
fusesdesc.toXML(document);
// Use a Transformer for output
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
DOMSource source = new DOMSource(document);
fos = new FileOutputStream(file);
StreamResult result = new StreamResult(fos);
transformer.transform(source, result);
fos.close();
// if (false) {
// // Short fragment I used for debugging.
// // This dumps all BitFieldDescriptions into a large CSV file,
// // which can then be imported and analyzed with a Database.
// FileWriter fw = new FileWriter(new File("allfuses.csv"), true);
// for (FuseType type : FuseType.values()) {
// List<IByteDescription> list = fusesdesc.getByteDescriptions(type);
// for (IByteDescription desc : list) {
// List<BitFieldDescription> bfdlist = desc.getBitFieldDescriptions();
// for (BitFieldDescription bfd : bfdlist) {
// fw.write(mcuid + "; " + type.name() + "; " + bfd.getName() + "; \""
// + bfd.getDescription() + "\"; " + bfd.getMaxValue() + "\n");
// }
// }
// }
// fw.close();
// }
} catch (ParserConfigurationException pce) {
IStatus status = new Status(Status.ERROR, AVRPlugin.PLUGIN_ID,
"Could not create a XML object for " + mcuid, pce);
AVRPlugin.getDefault().log(status);
// TODO throw an Exception to notify the caller
// For now we just continue and try the next object.
} catch (TransformerException te) {
IStatus status = new Status(Status.ERROR, AVRPlugin.PLUGIN_ID,
"Could not transform the XML content for " + mcuid, te);
AVRPlugin.getDefault().log(status);
// TODO throw an Exception to notify the caller
// For now we just continue and try the next object.
} catch (FileNotFoundException fnfe) {
IStatus status = new Status(Status.ERROR, AVRPlugin.PLUGIN_ID,
"Could not create the MCUDescription file for " + mcuid, fnfe);
AVRPlugin.getDefault().log(status);
// TODO throw an Exception to notify the caller
// For now we just continue and try the next object.
} catch (IOException ioe) {
IStatus status = new Status(Status.ERROR, AVRPlugin.PLUGIN_ID,
"Could not write the MCUDescription file for " + mcuid, ioe);
AVRPlugin.getDefault().log(status);
// TODO throw an Exception to notify the caller
// For now we just continue and try the next object.
}
} // for loop
}
/**
* Get the storage destination folder for the fuse description files.
* <p>
* Override this method to supply a different location.
* </p>
*
* @see Fuses#getInstanceStorageLocation()
*
* @return <code>IPath</code> to the instance storage area.
*/
protected IPath getStoragePath() {
// The default is to get the folder from the {@link Fuses} class.
return Fuses.getDefault().getInstanceStorageLocation();
}
/**
* Read all fuse or lockbits bytes from the given <registry-group> node.
*
* @param registerGroupNode
* A <register-group> node for fuses
* @param desc
* A <code>MCUDescription</code> container which will be filled with the
* <code>ByteDescription</code> objects for each byte.
*/
private void readRegisters(Node registerGroupNode, MCUDescription desc, FuseType type,
Map<String, Node> valueGroupList) {
// Now get and parse all <register> nodes. Each <register> node stands for one fuses /
// lockbits object (either 8 bit or 32 bit).
Node registerNode = registerGroupNode.getFirstChild();
while (registerNode != null) {
if (ELEM_REGISTER.equalsIgnoreCase(registerNode.getNodeName())) {
// Get the caption, name, offset and size of this fuse register.
NamedNodeMap attributesMap = registerNode.getAttributes();
Node captionNode = attributesMap.getNamedItem(ATTR_CAPTION);
Node nameNode = attributesMap.getNamedItem(ATTR_NAME);
Node sizeNode = attributesMap.getNamedItem(ATTR_SIZE);
Node offsetNode = attributesMap.getNamedItem(ATTR_OFFSET);
// Sanity check
if (captionNode == null || nameNode == null || sizeNode == null
|| offsetNode == null) {
System.err.println("Missing attribute in the XML for MCU " + desc.getMCUId());
}
String caption = captionNode.getNodeValue();
String name = nameNode.getNodeValue();
int offset = hex2int(offsetNode.getNodeValue());
int size = Integer.parseInt(sizeNode.getNodeValue());
ByteDescription bytedesc = new ByteDescription(type, caption, name, offset, size,
-1);
// Now we can read the bitfields for each node
Node bitfieldnode = registerNode.getFirstChild();
while (bitfieldnode != null) {
if (ELEM_BITFIELD.equals(bitfieldnode.getNodeName())) {
readBitfieldNode(bytedesc, bitfieldnode, valueGroupList, offset);
}
bitfieldnode = bitfieldnode.getNextSibling();
}
desc.addByteDescription(type, bytedesc);
}
registerNode = registerNode.getNextSibling();
}
}
/**
* @param desc
* The fuses / lockbits object onto which to add this bitfield description.
* @param node
* A <bitfield> node
* @param valuesGroupNodeMap
* The map of all <value-group> element nodes.
* @param index
* the fuse byte index this bitfield is for.
* @return A <code>BitFieldDescription</code> object representing the given <bitfield>
* node.
*/
private void readBitfieldNode(ByteDescription desc, Node node,
Map<String, Node> valuesGroupNodeMap, int index) {
List<BitFieldValueDescription> valuesList = null;
// Get the Attributes of the <bitfield> node
NamedNodeMap attrs = node.getAttributes();
String name = attrs.getNamedItem(ATTR_NAME).getTextContent();
Long mask = Long.decode(attrs.getNamedItem(ATTR_MASK).getTextContent());
// The caption attribute is optional. If it is not set then take the name of the value as
// its description.
Node descriptionNode = attrs.getNamedItem(ATTR_CAPTION);
String description = descriptionNode != null ? descriptionNode.getNodeValue() : name;
// The values attribute is optional as well. It contains the name of a <values-group>
// element.
Node valuesNode = attrs.getNamedItem(ATTR_VALUES);
if (valuesNode != null) {
String valuesRefName = valuesNode.getNodeValue();
Node valueGroupNode = valuesGroupNodeMap.get(valuesRefName);
if (valueGroupNode != null) {
valuesList = readValueGroupNode(valueGroupNode);
} else {
System.out.println(fFile.getName()
+ " Found non-existing <value-group> reference: " + valuesRefName);
}
}
// Check if this bitfield is one that contains errors in the part description files and fix
// the errors.
name = bitfieldNameFixer(name, mask, description);
BitFieldDescription bitfield = new BitFieldDescription(index, name, description,
mask.intValue(), -1, valuesList);
desc.addBitFieldDescription(bitfield);
}
/**
* Read the given <value-group> node and add its content to the given global enumerators
* list.
* <p>
* All <value> children of the <value-group> node are collected in a list of
* BitFieldValueDescription objects and mapped to the name of the enumerator node, which is then
* added to the global list.
* </p>
*
* @param node
* A <enumerator> node
* @param enums
* The global map of all enumerator names and their
* <code>BitFieldValueDescription</code> lists
*/
private List<BitFieldValueDescription> readValueGroupNode(Node valueGroupNode) {
List<BitFieldValueDescription> valuesList = new ArrayList<BitFieldValueDescription>();
// Get all <value> children
Node valueNode = valueGroupNode.getFirstChild();
while (valueNode != null) {
if (ELEM_VALUE.equals(valueNode.getNodeName())) {
BitFieldValueDescription bfvd = readValueNode(valueNode);
valuesList.add(bfvd);
}
valueNode = valueNode.getNextSibling();
}
return valuesList;
}
/**
* Read the given <value> node and return a new BitFieldValueDescription with the
* descriptive text and the value of the value node.
*
* @param node
* A <value> node
* @return New <code>BitFieldValueDescription</code> object with the values from the "caption",
* "name" and "value" attributes.
*/
private BitFieldValueDescription readValueNode(Node node) {
NamedNodeMap attrs = node.getAttributes();
String name = attrs.getNamedItem(ATTR_NAME).getNodeValue();
int value = Integer.decode(attrs.getNamedItem(ATTR_VALUE).getNodeValue());
// The caption attribute is optional. If it is not set then take the name of the value as
// its description.
Node descriptionNode = attrs.getNamedItem(ATTR_CAPTION);
String description = descriptionNode != null ? descriptionNode.getNodeValue() : name;
// The same goes for the text attribute, which is used by the AVR32 files instead of the
// caption attribute.
Node textNode = attrs.getNamedItem(ATTR_TEXT);
description = textNode != null ? textNode.getNodeValue() : description;
return new BitFieldValueDescription(value, description, name);
}
/**
* Set the version of the {@link IMCUDescription} to the last modification date of the source
* file.
*
* @param sourcefile
* Device description file
* @param desc
* The MCUDescription
*/
private void setVersion(File sourcefile, MCUDescription desc) {
// get the file date and store it as the version number
long lastmodified = sourcefile.lastModified();
Date date = new Date(lastmodified);
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
String versionString = df.format(date);
int version = Integer.parseInt(versionString);
desc.setVersion(version);
}
/**
* With the given parameters check if they match a few obvious errors in the part description
* files and fix them.
* <p>
* As long as the description files generated by the plugin are only used by the plugin this
* should be save. If we ever need to interoperate with some external tools this modification of
* the fuses information needs to be checked to avoid compatibility issues.
* </p>
*
* @param name
* @param mask
* @param description
* @return The fixed name, or the original name if no fix was required.
*/
private String bitfieldNameFixer(String name, long mask, String description) {
if (name.equals("WTDON") && description.startsWith("Watch-Dog Timer")) {
// This is a typo in the atmega8.xml file
// Should be "WDTON", like in all other MCUs.
return "WDTON";
}
// The atmega103.xml file is really fucked up. All Fuses names are wrong in the part
// description file! We can fix two of them, but because at this point we do not know the
// MCU Id, we cannot fix the last bug: The "CKSEL" field is in reality a "SUT" as
// documented in the datasheet.
// AVR Studio 5.0 does not have a file for the ATmega103 anymore. But we leave this in
// anyway
if (name.equals("CKSEL3") && description.startsWith("Preserve EEPROM")) {
// According to the description this bitfield should be "EESAVE"
return "EESAVE";
}
if (name.equals("SUT1") && description.startsWith("Serial program")) {
// According to the description this bitfield should be "SPIEN"
return "SPIEN";
}
return name;
}
/**
* Try to fix some obvious errors in the Atmel device files.
* <p>
* This method is called with the name of the <value-group> element and returns the name
* that matches the values attribute of the <bitfield> element.
* </p>
*
* @param desc
* IMCUDescrtiption with the current MCU.
* @param name
* of the <value-group> element.
* @return name used by the value attribute.
*/
private String valueGroupNameFixer(MCUDescription desc, String name) {
String mcuid = desc.getMCUId();
// Fix for the AT32UC3C1512C.xml & AT32UC3C2512C.xml files
// The BODxx references all end with "_VALUES", while the actual <value-group> names do not
if (mcuid.equals("at32uc3c1512c") || mcuid.equals("at32uc3c2512c")) {
if (name.equals("BOD33EN")) {
return "BODEN33_VALUES";
}
if (name.equals("BODEN") || name.equals("BODHYST") || name.equals("BODLEVEL")) {
return name + "_VALUES";
}
}
// Fix for the AT32UC3C0512C.xml file.
// The reference is "BODEN33", while the actual <value-group> name is "BOD33EN".
if (mcuid.equals("at32uc3c0512c") && name.equals("BOD33EN")) {
return "BODEN33";
}
return name;
}
/**
* Convert a hex String to an integer.
* <p>
* The hex string may or may not start with '0x'.
* </p>
*
* @param hex
* @return
*/
private int hex2int(String hex) {
String value = hex.toLowerCase();
if (value.startsWith("0x")) {
value = value.substring(2);
}
return Integer.parseInt(value, 16);
}
}