#!/usr/bin/env python3 # coding=utf-8 #=============================================================================== # @brief XML tools # Copyright (c) HiSilicon (Shanghai) Technologies Co., Ltd. #=============================================================================== import ctypes import sys import inspect import os import re from xml.etree import ElementTree from xml.dom import minidom sys.path.append(os.path.join("build", "script", "hdbxml_custom", "parse")) import parse_msgdefs def isStackmessage(id, core): if (core in ['protocol_core']) and ((id >> 26) != 0x10): return True return False def swap16(x): return (((x << 8) & 0xFF00) | ((x >> 8) & 0x00FF)) def swap32(x): return (((x << 24) & 0xFF000000) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | ((x >> 24) & 0x000000FF)) def getIdFromParts(core, msg_class, msg_module, msg_id): return (((core << 30) & 0xC0000000) | ((msg_class << 29) & 0x20000000) | ((msg_module << 16) & 0x00FF0000) | (swap16(msg_id) & 0x0000FFFF)) def getIdForMessage(id, core): if isStackmessage(id, core): # A stack message needs to be dissasembled and put back together msg_module = (((id) >> 24) & 0xFF) msg_log = (((id) >> 21) & 0x1) msg_verbosity = (((id) >> 22) & 0x3) msg_id = (((id) & 0x3ff) | (msg_log << 13) | (msg_verbosity << 14)) messageIDOut = getIdFromParts(1, 1, msg_module, msg_id) else: # All other messages need their low two bytes swapped messageIDOut = ((id) & 0xFFFF0000) | (((id) & 0x0000FF00) >> 8) | (((id) & 0x000000FF) << 8) messageIDOut = swap32(messageIDOut) return messageIDOut def getFirmwareVersion(): versionfile = os.path.join("build_scons", "VERSION_STRING") if os.path.exists(versionfile): with open(versionfile) as f: firmware_version = f.read().strip() else: # This can happen when the messagexml generation is used outside the build. # The DSP build uses this via tools/scripts/messagesxml.bat firmware_version = "UNKNOWN" return firmware_version # just used to include comments from rules files. class CommentedTreeBuilder(ElementTree.TreeBuilder): def __init__(self, *args, **kwargs): super(CommentedTreeBuilder, self).__init__(*args, **kwargs) def comment(self, data): self.start(ElementTree.Comment, {}) self.data(data) self.end(ElementTree.Comment) class messageTree(object): def __init__(self, distribution, firmware_version, coreName, imageName): self.parser = ElementTree.XMLParser(target=CommentedTreeBuilder()) ElementTree.register_namespace("","http://tempuri.org/xmlDefinition.xsd" ) debugInformationAttributes = { \ 'Distribution': distribution, \ 'FirmwareVersion':firmware_version, \ 'MulticoreLoggingSupported':"true", \ 'SocXMLVersion':"2", \ 'SequenceNumbersSupported':"true", \ 'xmlns':"http://tempuri.org/xmlDefinition.xsd", \ } if imageName in ['SEBoot', 'SERecovery', 'updater']: isBootImage = 'true' else: isBootImage = 'false' coreAttributes = { \ 'CoreName': str(coreName), \ 'ImageName': str(imageName), \ 'BootImage': isBootImage, \ } self.top = ElementTree.Element('DebugInformation', debugInformationAttributes) self.tree = ElementTree.ElementTree(element=self.top, file=None) loghdr = ElementTree.SubElement(self.top, 'CommonLogHeader', {'LogIndexMask': '0x0000001F'}) coreEntry = ElementTree.SubElement(self.top, 'Core', coreAttributes) self.messages = ElementTree.SubElement(coreEntry, 'Messages') self.structs_dict = ElementTree.SubElement(coreEntry, 'StructureDictionary') self.enums_dict = ElementTree.SubElement(coreEntry, 'EnumsDictionary') def addMessageHeader(self, messageEnumName, messageId, core, elemName): messageAttributes = { \ 'Name': messageEnumName, \ 'MessageID': "0x"+str(hex(messageId))[2:].zfill(8), \ 'Type': 'structure', \ } if isStackmessage(messageId, core): messageAttributes['MessageType'] = 'StackMessage' message = ElementTree.SubElement(self.messages, "Message",messageAttributes) return message def addStructure(self, nodeTypeName, fieldElementType, elementSize): structureAttributes = { \ 'Type': nodeTypeName, \ 'FieldType': fieldElementType, \ } fieldEntry = ElementTree.SubElement(self.structs_dict, 'Structure', structureAttributes) return fieldEntry def structureInDictionary(self, structName): found = False for i in self.structs_dict: nameEntry = i.attrib["Type"] if structName == nameEntry: found = True return found def addField(self, parent, fieldName, nodeTypeName, fieldElementType, elementSize, length, enumName): fieldAttributes = { \ 'FieldName': fieldName, \ 'Type': nodeTypeName, \ 'FieldType': fieldElementType, \ 'Size': str(elementSize), \ 'Length': str(length), \ } if enumName is not None: fieldAttributes['Enum'] = enumName fieldSubEntry = ElementTree.SubElement(parent, 'Field', fieldAttributes) return fieldSubEntry def addEnum(self, enum_type_name, enumList): found = False for i in self.enums_dict: nameEntry = i.find("Name").text if enum_type_name == nameEntry: found = True if not found: enumsEntry = ElementTree.SubElement(self.enums_dict, 'EnumsEntry') enumName = ElementTree.SubElement(enumsEntry, 'Name') enumName.text = enum_type_name #enumsArray = ElementTree.SubElement(enumsEntry, 'Enums') for name, val in enumList: self.addEnumEntry(enumsEntry, name, val) def addEnumEntry(self, parent, name, val): enumAttributes = { \ 'Name': name, \ 'Value': str(val), \ } enumValueEntry = ElementTree.SubElement(parent, 'Enum', enumAttributes) def dump(self): return ElementTree.dump(self.top) def prettify(self, elem, encoding = 'utf-8'): """Return a pretty-printed XML string for the Element. """ initial_string = ElementTree.tostring(elem, encoding=encoding, method='xml') reparsed_string = minidom.parseString(initial_string) return reparsed_string.toprettyxml(indent=" ") def write_xml_file (self, file, xml_root_element, xml_declaration=False, pretty_print=False, encoding='unicode', indent='\t'): if xml_declaration: file.write('') pretty_printed_xml = self.prettify(xml_root_element, encoding = 'utf-8') file.write(pretty_printed_xml) def outputMessageTree(self, regexp, rulesfilename, outfilename): if regexp: matcher = re.compile(regexp) # Filter out any messages we don't want to publish, then add the rules and write it out internal_message_list = [] for message in self.messages: name = message.attrib['Name'] internal = regexp and matcher.search(name) is not None if internal: internal_message_list.append(message) for message in internal_message_list: name = message.attrib['Name'] self.messages.remove(message) if rulesfilename != None: rules_data = ElementTree.parse(rulesfilename, self.parser) if rules_data is None: raise SystemExit('Error: failed to parse %s' % rulesfilename) rules_root = rules_data.getroot() if rules_root is None: raise SystemExit('Error: failed to find root section in %s' % rulesfilename) rulesEntry = self.top.find('Core') if rulesEntry is None: raise SystemExit('Error: failed to find "Core" section') rulesEntry.extend(rules_root) with open(outfilename, 'w') as outfile: self.write_xml_file(outfile, self.top, xml_declaration=False, pretty_print=True, encoding='unicode', indent='\t') # Taking some ctypes element, recursively analyse it, and build a tree of nodes representing it def buildTree(xmlTree, message_fields, elem): if hasattr(elem, "_fields_"): for field in elem._fields_: structAlreadyPopulated = False current_message_fields = message_fields fieldType = field[1] arrayItemNamePostfix='' # This is potentiallay a multi-dimensional array that must be descended into so do so until an actual type is encountered while True: # If this is an array there will be some kind of length length = fieldType._length_ if hasattr(fieldType, "_length_") else 1 # This size includes all the different elements (if this is an array) or just the size of the single field if not size = ctypes.sizeof(fieldType) # Deduce the size of a single element elementSize = size // length # Pointers have a "contents" attribute isPointer = hasattr(fieldType, "contents") # Simple single dimension arrays can be handled using the length isSomeKindOfArray = issubclass(fieldType, ctypes.Array) if isSomeKindOfArray: # This is some kind of array so get the element within the array - the fieldType from now onwards is within the array fieldType = fieldType._type_ # Base types have no fields in their "type" (note - arrays of base types are considered to be base types) isBaseType = not hasattr(fieldType, "_fields_") isUnion = isinstance(fieldType, ctypes.Union) isEnum = hasattr(fieldType, "members") if isEnum: for class_ in inspect.getmro(fieldType): if issubclass(class_, ctypes._SimpleCData) and class_ is not fieldType: fullTypeName = class_ break else: raise TypeError("Can't find a ctypes class to use for enum %s" % fieldType) else: fullTypeName = fieldType # Extract a useful type name from the verbose name in CTypes (junk, separator, fullTypeName) = str(fullTypeName).partition(".") (fullTypeName, separator, junk) = fullTypeName.partition("'") # Check to see if there are further array dimensions that need to be unwrapped and if not exit to actually if not issubclass(fieldType, ctypes.Array): break # This is a multi-dimensional array so use a fake struct here to hold the next dimensionality arrayItemNamePostfix='_item' nodeTypeName = ''.join(["struct_", fullTypeName]) fieldEntry = xmlTree.addField(current_message_fields, field[0], nodeTypeName, 'struct', elementSize, length, enum_type_name) # If the fake structure has already been populated then stop here if xmlTree.structureInDictionary(nodeTypeName): structAlreadyPopulated = True break # Add the fake struct definition and start populating it during the next iteration current_message_fields = xmlTree.addStructure(nodeTypeName, 'struct', elementSize) # Only need to do anything if a struct has not already been populated if not structAlreadyPopulated: # Now dealing with something that is not an array (friendlyTypeName, separator, junk) = fullTypeName.partition("_Array_") nodeTypeName = friendlyTypeName if 'struct' in nodeTypeName and not isPointer: fieldElementType = 'struct' elif 'union' in nodeTypeName: fieldElementType = 'union' else: fieldElementType = 'base' if isEnum: enum_type_name = inspect.getmro(fieldType)[0].__name__ xmlTree.addEnum(enum_type_name, sorted(fieldType.members.items())) else: enum_type_name = None # write the message entry in all cases. fieldEntry = xmlTree.addField(current_message_fields, field[0] + arrayItemNamePostfix, nodeTypeName, fieldElementType, elementSize, length, enum_type_name) # if it's a structure add to structure dict etc, if not it's a base type and go to the next one if fieldElementType == 'struct': if not xmlTree.structureInDictionary(nodeTypeName): fields_SubEntry = xmlTree.addStructure(nodeTypeName, fieldElementType, elementSize) buildTree(xmlTree, fields_SubEntry, fieldType) else: if not isBaseType and not isPointer: buildTree(xmlTree, fieldEntry, fieldType) def xml_gen_main(outputBasepath, source, cfgfilename, core, image, rulesfilename): messages = parse_msgdefs.parse_preprocessed_headers(source, core) # Load the configuration file with open(cfgfilename) as cfgfile: if cfgfile == None: raise SystemExit("Error: Could not find configuration file %s." % cfgfilename) # Load the core if core not in ['acore', 'protocol_core', 'security_core']: raise SystemExit("Error: Invalid core %s." % core) if rulesfilename is not None: with open(rulesfilename) as rulesfile: if rulesfile == None: raise SystemExit("Error: Could not find rules file %s." %rulesfilename) # List of filenames and regular expressions if not os.path.isdir(outputBasepath): # If given an output filename rather than base directory, # strip off the filename and keep the path outputBasepath = os.path.dirname(outputBasepath) outputs = [] # Parse the pairs of filenames and regular expressions for line in cfgfile.readlines(): if not line.startswith("#"): # Exclude comments if line.strip() != "": # Exclude empty lines # Format is: filename , regexp distribution, filename, regexp = line.split(",", 2) # Remove regexp whitespace regexp = regexp.strip() # Remove whitespace and add base path filename = filename.strip() filename = os.path.join(outputBasepath, filename) outputs.append((distribution, filename, regexp)) firmware_version = getFirmwareVersion() for distribution, outfilename, regexp in outputs: #with messageTree(distribution, firmware_version, core) as xmlTree: xmlTree = None xmlTree = messageTree(distribution, firmware_version, core, image) # And a list of message IDs already used # (These should ideally be stored in a tree for scalable search speed, but the number of messages is small so the wasted time isn't a problem) messageIdList = [] for messageEnumName, structname, messageId, struct in messages: # If not a stack xml then convert the IDs to the log IDs # # if distribution != "stack": # messageId = getIdForMessage(messageId, core) fieldsEntry = xmlTree.addMessageHeader(messageEnumName, messageId, core, structname) buildTree(xmlTree, fieldsEntry, struct) xmlTree.outputMessageTree(regexp, rulesfilename, outfilename) del xmlTree if len(sys.argv) < 6: raise SystemExit("Usage: python MessageXmlGen.py []") source = sys.argv[1] outputBasepath = sys.argv[2] cfgfilename = sys.argv[3] corename = sys.argv[4] imagename = sys.argv[5] # Optional Rules File if len(sys.argv) == 7: rulesfilename = sys.argv[6] else: rulesfilename = None # 删除.i文件中的#pragma预编译指令 with open(source, "rb+") as f: write_lines = [] for line in f.readlines(): if not re.match("#pragma", line.decode("utf-8", errors="replace").strip()): write_lines.append(line) f.seek(0, 0) f.truncate() for line in write_lines: f.write(line) xml_gen_main(outputBasepath, source, cfgfilename, corename, imagename, rulesfilename)