#!/usr/bin/env python """ This file is part of PyCortexMDebug PyCortexMDebug is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. PyCortexMDebug is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with PyCortexMDebug. If not, see . """ from collections import OrderedDict from . import x2d import traceback import warnings import pickle import sys import os import re class SmartDict: """ Dictionary for search by case-insensitive lookup and/or prefix lookup """ def __init__(self): self.od = OrderedDict() self.casemap = {} def __getitem__(self, key): if key in self.od: return self.od[key] if key.lower() in self.casemap: return self.od[self.casemap[key.lower()]] return self.od[self.prefix_match(key)] def is_ambiguous(self, key): return ( key not in self.od and key not in self.casemap and len(list(self.prefix_match_iter(key))) > 1 ) def prefix_match_iter(self, key): name, number = re.match(r"^(.*?)([0-9]*)$", key.lower()).groups() for entry, od_key in self.casemap.items(): if entry.startswith(name) and entry.endswith(number): yield od_key def prefix_match(self, key): for od_key in self.prefix_match_iter(key): return od_key return None def __setitem__(self, key, value): if key in self.od: warnings.warn("Duplicate entry %s", key) elif key.lower() in self.casemap: warnings.warn( "Entry %s differs from duplicate %s only in cAsE", key, self.casemap[key.lower()], ) self.casemap[key.lower()] = key self.od[key] = value def __delitem__(self, key): if ( self.casemap[key.lower()] == key ): # Check that we did not overwrite this entry del self.casemap[key.lower()] del self.od[key] def __contains__(self, key): return key in self.od or key.lower() in self.casemap or self.prefix_match(key) def __iter__(self): return iter(self.od) def __len__(self): return len(self.od) def items(self): return self.od.items() def keys(self): return self.od.keys() def values(self): return self.od.values() def __str__(self): return str(self.od) class SVDNonFatalError(Exception): """Exception class for non-fatal errors So far, these have related to quirks in some vendor SVD files which are reasonable to ignore """ def __init__(self, m): self.m = m self.exc_info = sys.exc_info() def __str__(self): s = "Non-fatal: {}".format(self.m) s += "\n" + str("".join(traceback.format_exc())).strip() return s class SVDFile: """ A parsed SVD file """ def __init__(self, fname): """ Args: fname: Filename for the SVD file """ self.peripherals = SmartDict() self.base_address = 0 xml_file_name = os.path.expanduser(fname) pickle_file_name = xml_file_name + ".pickle" root = None if os.path.exists(pickle_file_name): print("Loading pickled SVD") root = pickle.load(open(pickle_file_name, "rb")) else: print("Loading XML SVD and pickling it") root = x2d.parse(open(xml_file_name, "rb")) pickle.dump(root, open(pickle_file_name, "wb"), pickle.HIGHEST_PROTOCOL) print("Processing SVD tree") # XML elements for p in root["device"]["peripherals"]["peripheral"]: try: self.peripherals[p["name"]] = SVDPeripheral(p, self) except SVDNonFatalError as e: # print(e) pass print("SVD Ready") def add_register(parent, node): """ Add a register node to a peripheral Args: parent: Parent SVDPeripheral object node: XML file node fot of the register """ if hasattr(node, "dim"): dim = int(str(node.dim), 0) # dimension is not used, number of split indexes should be same incr = int(str(node.dimIncrement), 0) default_dim_index = ",".join((str(i) for i in range(dim))) dim_index = str(getattr(node, "dimIndex", default_dim_index)) indices = dim_index.split(",") offset = 0 for i in indices: name = str(node.name) % i reg = SVDPeripheralRegister(node, parent) reg.name = name reg.offset += offset parent.registers[name] = reg offset += incr else: try: reg = SVDPeripheralRegister(node, parent) name = str(node.name) if name not in parent.registers: parent.registers[name] = reg else: if hasattr(node, "alternateGroup"): print("Register %s has an alternate group", name) except SVDNonFatalError as e: print(e) def add_cluster(parent, node): """ Add a register cluster to a peripheral """ if hasattr(node, "dim"): dim = int(str(node.dim), 0) # dimension is not used, number of split indices should be same incr = int(str(node.dimIncrement), 0) default_dim_index = ",".join((str(i) for i in range(dim))) dim_index = str(getattr(node, "dimIndex", default_dim_index)) indices = dim_index.split(",") offset = 0 for i in indices: name = str(node.name) % i cluster = SVDRegisterCluster(node, parent) cluster.name = name cluster.address_offset += offset cluster.base_address += offset parent.clusters[name] = cluster offset += incr else: try: parent.clusters[str(node.name)] = SVDRegisterCluster(node, parent) except SVDNonFatalError as e: print(e) class SVDRegisterCluster: """ Register cluster """ def __init__(self, svd_elem, parent): """ Args: svd_elem: XML element for the register cluster parent: Parent SVDPeripheral object """ self.parent_base_address = parent.base_address self.parent_name = parent.name self.address_offset = int(str(svd_elem.addressOffset), 0) self.base_address = self.address_offset + self.parent_base_address # This doesn't inherit registers from anything children = svd_elem.getchildren() self.description = str(svd_elem.description) self.name = str(svd_elem.name) self.registers = SmartDict() self.clusters = SmartDict() for r in children: if r.tag == "register": add_register(self, r) def refactor_parent(self, parent): self.parent_base_address = parent.base_address self.parent_name = parent.name self.base_address = self.parent_base_address + self.address_offset values = self.registers.values() for r in values: r.refactor_parent(self) def __str__(self): return str(self.name) class SVDPeripheral: """ This is a peripheral as defined in the SVD file """ def __init__(self, svd_elem, parent): """ Args: svd_elem: XML element for the peripheral parent: Parent SVDFile object """ self.parent_base_address = parent.base_address # Look for a base address, as it is required if "baseAddress" not in svd_elem: raise SVDNonFatalError("Periph without base address") self.base_address = int(str(svd_elem.baseAddress), 0) if "@derivedFrom" in svd_elem: derived_from = svd_elem["@derivedFrom"] try: self.name = str(svd_elem.name) except AttributeError: self.name = parent.peripherals[derived_from].name try: self.description = str(svd_elem.description) except AttributeError: self.description = parent.peripherals[derived_from].description # pickle is faster than deepcopy by up to 50% on svd files with a # lot of derivedFrom definitions def copier(a): return pickle.loads(pickle.dumps(a)) self.registers = copier(parent.peripherals[derived_from].registers) self.clusters = copier(parent.peripherals[derived_from].clusters) self.refactor_parent(parent) else: # This doesn't inherit registers from anything self.description = str(svd_elem.description) self.name = str(svd_elem.name) self.registers = SmartDict() self.clusters = SmartDict() if hasattr(svd_elem, "registers"): if "register" in svd_elem.registers: for r in svd_elem.registers.register: if isinstance(r, x2d.ObjectDict): add_register(self, r) if "cluster" in svd_elem.registers: for c in svd_elem.registers.cluster: if isinstance(c, x2d.ObjectDict): add_cluster(self, c) def refactor_parent(self, parent): self.parent_base_address = parent.base_address values = self.registers.values() for r in values: r.refactor_parent(self) for c in self.clusters.values(): c.refactor_parent(self) def __str__(self): return str(self.name) class SVDPeripheralRegister: """ A register within a peripheral """ def __init__(self, svd_elem, parent): self.parent_base_address = parent.base_address self.name = str(svd_elem.name) self.description = str(svd_elem.description) self.offset = int(str(svd_elem.addressOffset), 0) if hasattr(svd_elem, "access"): self.access = str(svd_elem.access) else: self.access = "read-write" if hasattr(svd_elem, "size"): self.size = int(str(svd_elem.size), 0) else: self.size = 0x20 self.fields = SmartDict() if "fields" in svd_elem: # Filter fields to only consider those of tag "field" for f in svd_elem.fields.field: if isinstance(f, x2d.ObjectDict): self.fields[str(f.name)] = SVDPeripheralRegisterField(f, self) def refactor_parent(self, parent): self.parent_base_address = parent.base_address def address(self): return self.parent_base_address + self.offset def readable(self): return self.access in ["read-only", "read-write", "read-writeOnce"] def writable(self): return self.access in [ "write-only", "read-write", "writeOnce", "read-writeOnce", ] def __str__(self): return str(self.name) class SVDPeripheralRegisterField: """ Field within a register """ def __init__(self, svd_elem, parent): self.name = str(svd_elem.name) self.description = str(getattr(svd_elem, "description", "")) # Try to extract a bit range (offset and width) from the available fields if hasattr(svd_elem, "bitOffset") and hasattr(svd_elem, "bitWidth"): self.offset = int(str(svd_elem.bitOffset)) self.width = int(str(svd_elem.bitWidth)) elif hasattr(svd_elem, "bitRange"): bitrange = list(map(int, str(svd_elem.bitRange).strip()[1:-1].split(":"))) self.offset = bitrange[1] self.width = 1 + bitrange[0] - bitrange[1] else: assert hasattr(svd_elem, "lsb") and hasattr( svd_elem, "msb" ), "Range not found for field {} in register {}".format(self.name, parent) lsb = int(str(svd_elem.lsb)) msb = int(str(svd_elem.msb)) self.offset = lsb self.width = 1 + msb - lsb self.access = str(getattr(svd_elem, "access", parent.access)) self.enum = {} if hasattr(svd_elem, "enumeratedValues"): values = [ v for v in svd_elem.enumeratedValues.getchildren() if v.tag == "enumeratedValue" ] for v in values: # Skip the "name" tag and any entries that don't have a value if v.tag == "name" or not hasattr(v, "value"): continue # Some Kinetis parts have values with # instead of 0x... value = str(v.value).replace("#", "0x") description = str(v.description) if hasattr(v, "description") else "" try: index = int(value, 0) self.enum[int(value, 0)] = (str(v.name), description) except ValueError: # If the value couldn't be converted as a single integer, skip it pass def readable(self): return self.access in ["read-only", "read-write", "read-writeOnce"] def writable(self): return self.access in [ "write-only", "read-write", "writeOnce", "read-writeOnce", ] def __str__(self): return str(self.name) def _main(): """ Basic test to parse a file and do some things """ for f in sys.argv[1:]: print("Testing file: {}".format(f)) svd = SVDFile(f) print(svd.peripherals) key = list(svd.peripherals)[0] print("Registers in peripheral '{}':".format(key)) print(svd.peripherals[key].registers) print("Done testing file: {}".format(f)) if __name__ == "__main__": _main()