1111import re
1212from collections import OrderedDict
1313from fnmatch import fnmatchcase
14+ from pathlib import Path
15+ from typing import Any , Generator , Union
1416
1517import lxml .etree as ET
1618import yaml
1719from braceexpand import braceexpand
20+ from lxml .etree import _Element as Element
21+ from lxml .etree import _ElementTree as ElementTree
1822
1923DEVICE_CHILDREN = [
2024 "vendor" ,
@@ -374,6 +378,16 @@ def sort_recursive(tag):
374378 sort_recursive (child )
375379
376380
381+ def get_element_name (element : Union [Element , ElementTree ], description : str ) -> str :
382+ assert isinstance (element , (Element , ElementTree ))
383+ assert isinstance (description , str )
384+ nametag = element .find ("name" )
385+ assert isinstance (nametag , Element ), f"{ description .capitalize ()} must have name."
386+ name = nametag .text
387+ assert isinstance (name , str ), f"{ description .capitalize ()} name must be string."
388+ return name
389+
390+
377391class SvdPatchError (ValueError ):
378392 pass
379393
@@ -398,6 +412,10 @@ class UnknownTagError(SvdPatchError):
398412 pass
399413
400414
415+ class MissingClusterError (SvdPatchError ):
416+ pass
417+
418+
401419class Device :
402420 """Class collecting methods for processing device contents"""
403421
@@ -701,6 +719,9 @@ def process_peripheral(self, pspec, peripheral, update_fields=True):
701719 elif rname == "_interrupts" :
702720 for iname in radd :
703721 p .add_interrupt (iname , radd [iname ])
722+ elif rname == "_clusters" :
723+ for cname in radd :
724+ p .add_cluster (cname , radd [cname ])
704725 else :
705726 p .add_register (rname , radd )
706727 # Handle derive
@@ -728,6 +749,17 @@ def process_peripheral(self, pspec, peripheral, update_fields=True):
728749 for cname in peripheral .get ("_cluster" , {}):
729750 cmod = peripheral ["_cluster" ][cname ]
730751 p .collect_in_cluster (cname , cmod )
752+ # Handle registers inside clusters
753+ clusters = peripheral .get ("_clusters" )
754+ if isinstance (clusters , list ):
755+ raise Exception (f"Cluster specification must be a dictionary." )
756+ elif isinstance (clusters , dict ):
757+ for cspec in clusters :
758+ assert isinstance (cspec , str )
759+ if not cspec .startswith ("_" ):
760+ cluster = peripheral ["_clusters" ][cspec ]
761+ p .process_cluster (cspec , cluster , update_fields )
762+
731763 if pcount == 0 :
732764 raise MissingPeripheralError (f"Could not find { pspec } " )
733765
@@ -737,12 +769,19 @@ class Peripheral:
737769
738770 def __init__ (self , ptag ):
739771 self .ptag = ptag
772+ self .name = get_element_name (self .ptag , "peripheral" )
740773
741774 def iter_registers (self , rspec ):
742775 """
743776 Iterates over all registers that match rspec and live inside ptag.
777+
778+ Ignore `register`s owned by `cluster`s.
744779 """
745780 for rtag in self .ptag .iter ("register" ):
781+ parent = rtag .getparent ()
782+ assert isinstance (parent , Element ), "Register must have parent."
783+ if parent .tag == "cluster" :
784+ continue
746785 name = rtag .find ("name" ).text
747786 if matchname (name , rspec ):
748787 yield rtag
@@ -752,8 +791,10 @@ def iter_registers_with_matches(self, rspec):
752791
753792 Each element is a tuple of the matching register and the rspec substring
754793 that it matched.
794+
795+ Ignore `register`s owned by `cluster`s.
755796 """
756- for rtag in self .ptag . iter ( "register " ):
797+ for rtag in self .iter_registers ( "* " ):
757798 name = rtag .find ("name" ).text
758799 if matchname (name , rspec ):
759800 yield (rtag , matchsubspec (name , rspec ))
@@ -821,7 +862,7 @@ def add_register(self, rname, radd):
821862 parent = self .ptag .find ("registers" )
822863 if parent is None :
823864 parent = ET .SubElement (self .rtag , "registers" )
824- for rtag in parent . iter ( "register " ):
865+ for rtag in self . iter_registers ( "* " ):
825866 if rtag .find ("name" ).text == rname :
826867 raise SvdPatchError (
827868 "peripheral {} already has a register {}" .format (
@@ -888,7 +929,7 @@ def copy_register(self, rname, rderive):
888929 )
889930 srcname = rderive ["_from" ]
890931 source = None
891- for rtag in parent . iter ( "register " ):
932+ for rtag in self . iter_registers ( "* " ):
892933 if rtag .find ("name" ).text == rname :
893934 raise SvdPatchError (
894935 "peripheral {} already has a register {}" .format (
@@ -923,6 +964,27 @@ def delete_register(self, rspec):
923964 for rtag in list (self .iter_registers (rspec )):
924965 self .ptag .find ("registers" ).remove (rtag )
925966
967+ def add_cluster (self , cname : str , cadd : OrderedDict [str , Any ]) -> None :
968+ """Add `cname` given by `cadd` to `ptag`."""
969+ assert isinstance (cname , str )
970+ assert isinstance (cadd , OrderedDict )
971+ parent = self .ptag .find ("registers" )
972+ if parent is None :
973+ parent = ET .SubElement (self .ptag , "registers" )
974+ for ctag in parent .iter ("cluster" ):
975+ if get_element_name (ctag , "cluster" ) == cname :
976+ raise SvdPatchError (
977+ f"peripheral { self .name } already has cluster { cname } "
978+ )
979+ cnew = ET .SubElement (parent , "cluster" )
980+ ET .SubElement (cnew , "name" ).text = cname
981+ for (key , value ) in cadd .items ():
982+ # print(key, value)
983+ if key in {"addressOffset" }:
984+ ET .SubElement (cnew , key ).text = str (value )
985+ else :
986+ Cluster (cnew ).add_register (key , value )
987+
926988 def modify_cluster (self , cspec , cmod ):
927989 """Modify cspec inside ptag according to cmod."""
928990 for ctag in self .iter_clusters (cspec ):
@@ -939,7 +1001,11 @@ def strip(self, substr, strip_end=False):
9391001 beginning of the name by default.
9401002 """
9411003 regex = create_regex_from_pattern (substr , strip_end )
942- for rtag in self .ptag .iter ("register" ):
1004+ for rtag in self .iter_registers ("*" ):
1005+ parent = rtag .getparent ()
1006+ assert isinstance (parent , Element ), "Register must have parent."
1007+ if parent .tag == "cluster" :
1008+ continue
9431009 nametag = rtag .find ("name" )
9441010 nametag .text = regex .sub ("" , nametag .text )
9451011
@@ -1162,6 +1228,118 @@ def process_register(self, rspec, register, update_fields=True):
11621228 if rcount == 0 :
11631229 raise MissingRegisterError (f"Could not find { pname } :{ rspec } " )
11641230
1231+ def process_cluster_tag (
1232+ self , ctag : Element , cluster : OrderedDict [str , Any ], update_fields : bool
1233+ ) -> None :
1234+ assert isinstance (ctag , Element )
1235+ assert isinstance (cluster , OrderedDict )
1236+ assert isinstance (update_fields , bool )
1237+ c = Cluster (ctag )
1238+ # Handle deletions
1239+ deletions = cluster .get ("_delete" , [])
1240+ if isinstance (deletions , list ):
1241+ for rspec in deletions :
1242+ c .delete_register (rspec )
1243+ elif isinstance (deletions , dict ):
1244+ for rspec in deletions :
1245+ c .delete_register (rspec )
1246+ # Handle strips
1247+ for prefix in cluster .get ("_strip" , []):
1248+ c .strip (prefix )
1249+ for suffix in cluster .get ("_strip_end" , []):
1250+ c .strip (suffix , strip_end = True )
1251+ # Handle modifications
1252+ for rspec in cluster .get ("_modify" , {}):
1253+ rmod = cluster ["_modify" ][rspec ]
1254+ c .modify_register (rspec , rmod )
1255+ # Handle additions
1256+ for rname in cluster .get ("_add" , {}):
1257+ radd = cluster ["_add" ][rname ]
1258+ c .add_register (rname , radd )
1259+
1260+ def process_cluster (
1261+ self , cspec : str , cluster : OrderedDict [str , Any ], update_fields : bool = True
1262+ ) -> None :
1263+ assert isinstance (cspec , str )
1264+ assert isinstance (cluster , OrderedDict )
1265+ assert isinstance (update_fields , bool )
1266+ # Find all clusters that match the spec
1267+ ccount = 0
1268+ for ctag in self .iter_clusters (cspec ):
1269+ self .process_cluster_tag (ctag , cluster , update_fields )
1270+ ccount += 1
1271+ if ccount == 0 :
1272+ raise MissingClusterError (f"Could not find { self .name } :{ cspec } " )
1273+
1274+
1275+ class Cluster :
1276+ """Class collecting methods for processing `cluster` contents."""
1277+
1278+ def __init__ (self , ctag : Element ) -> None :
1279+ assert isinstance (ctag , Element )
1280+ self .ctag = ctag
1281+ self .name = get_element_name (self .ctag , "cluster" )
1282+
1283+ def iter_registers (self , rspec : str ) -> Generator [Element , None , None ]:
1284+ """Iterate over all `cluster`s `register`s that match `rspec`."""
1285+ assert isinstance (rspec , str )
1286+ for rtag in self .ctag .iter ("register" ):
1287+ name = get_element_name (rtag , "register" )
1288+ if matchname (name , rspec ):
1289+ yield rtag
1290+
1291+ def add_register (self , rname : str , radd : OrderedDict [str , Any ]) -> None :
1292+ assert isinstance (rname , str )
1293+ assert isinstance (radd , OrderedDict )
1294+ parent = self .ctag
1295+ for rtag in parent .iter ("register" ):
1296+ if get_element_name (rtag , "register" ) == rname :
1297+ raise SvdPatchError (
1298+ f"cluster { self .name } already has a register { rname } "
1299+ )
1300+ rnew = ET .SubElement (parent , "register" )
1301+ ET .SubElement (rnew , "name" ).text = rname
1302+ for (key , value ) in radd .items ():
1303+ if key == "fields" :
1304+ ET .SubElement (rnew , "fields" )
1305+ for fname in value :
1306+ Register (rnew ).add_field (fname , value [fname ])
1307+ else :
1308+ ET .SubElement (rnew , key ).text = str (value )
1309+ rnew .tail = "\n "
1310+
1311+ def modify_register (self , rspec : str , rmod : OrderedDict [str , Any ]) -> None :
1312+ assert isinstance (rspec , str )
1313+ assert isinstance (rmod , OrderedDict )
1314+ for rtag in self .iter_registers (rspec ):
1315+ for (key , value ) in rmod .items ():
1316+ tag = rtag .find (key )
1317+ if value == "" and tag is not None :
1318+ rtag .remove (tag )
1319+ elif value != "" :
1320+ if tag is None :
1321+ tag = ET .SubElement (rtag , key )
1322+ tag .text = str (value )
1323+
1324+ def delete_register (self , rspec : str ) -> None :
1325+ assert isinstance (rspec , str )
1326+ for rtag in list (self .iter_registers (rspec )):
1327+ self .ctag .remove (rtag )
1328+
1329+ def strip (self , substr : str , strip_end : bool = False ) -> None :
1330+ assert isinstance (substr , str )
1331+ assert isinstance (strip_end , bool )
1332+ regex = create_regex_from_pattern (substr , strip_end )
1333+ for rtag in self .ctag .iter ("register" ):
1334+ nametag = rtag .find ("name" )
1335+ assert isinstance (nametag , Element ), "Register must have name."
1336+ assert isinstance (nametag .text , str )
1337+ nametag .text = regex .sub ("" , nametag .text )
1338+ dnametag = rtag .find ("displayName" )
1339+ if dnametag is not None :
1340+ assert isinstance (dnametag .text , str )
1341+ dnametag .text = regex .sub ("" , dnametag .text )
1342+
11651343
11661344def sorted_fields (fields ):
11671345 return sorted (fields , key = lambda ftag : get_field_offset_width (ftag )[0 ])
@@ -1640,3 +1818,14 @@ def main(yaml_file):
16401818
16411819 # SVD should now be updated, write it out
16421820 svd .write (svdpath_out )
1821+
1822+
1823+ if __name__ == "__main__" :
1824+ from argparse import ArgumentParser
1825+
1826+ parser = ArgumentParser ()
1827+ parser .add_argument ("patch-file" )
1828+ arguments = vars (parser .parse_args ())
1829+ path = arguments ["patch-file" ]
1830+ path = Path (path ).resolve ()
1831+ main (path )
0 commit comments