397 lines
18 KiB
Python
397 lines
18 KiB
Python
|
#!/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('<?xml version="1.0" encoding="utf-8" ?>')
|
||
|
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 <ctypes_library> <output_dir> <configuration_file> <corename> <imagename> [<rules_file>]")
|
||
|
|
||
|
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)
|