11
11
import re
12
12
from collections import OrderedDict
13
13
from fnmatch import fnmatchcase
14
+ from pathlib import Path
15
+ from typing import Any , Generator , Union
14
16
15
17
import lxml .etree as ET
16
18
import yaml
17
19
from braceexpand import braceexpand
20
+ from lxml .etree import _Element as Element
21
+ from lxml .etree import _ElementTree as ElementTree
18
22
19
23
DEVICE_CHILDREN = [
20
24
"vendor" ,
@@ -374,6 +378,16 @@ def sort_recursive(tag):
374
378
sort_recursive (child )
375
379
376
380
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
+
377
391
class SvdPatchError (ValueError ):
378
392
pass
379
393
@@ -398,6 +412,10 @@ class UnknownTagError(SvdPatchError):
398
412
pass
399
413
400
414
415
+ class MissingClusterError (SvdPatchError ):
416
+ pass
417
+
418
+
401
419
class Device :
402
420
"""Class collecting methods for processing device contents"""
403
421
@@ -701,6 +719,9 @@ def process_peripheral(self, pspec, peripheral, update_fields=True):
701
719
elif rname == "_interrupts" :
702
720
for iname in radd :
703
721
p .add_interrupt (iname , radd [iname ])
722
+ elif rname == "_clusters" :
723
+ for cname in radd :
724
+ p .add_cluster (cname , radd [cname ])
704
725
else :
705
726
p .add_register (rname , radd )
706
727
# Handle derive
@@ -728,6 +749,17 @@ def process_peripheral(self, pspec, peripheral, update_fields=True):
728
749
for cname in peripheral .get ("_cluster" , {}):
729
750
cmod = peripheral ["_cluster" ][cname ]
730
751
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
+
731
763
if pcount == 0 :
732
764
raise MissingPeripheralError (f"Could not find { pspec } " )
733
765
@@ -737,12 +769,19 @@ class Peripheral:
737
769
738
770
def __init__ (self , ptag ):
739
771
self .ptag = ptag
772
+ self .name = get_element_name (self .ptag , "peripheral" )
740
773
741
774
def iter_registers (self , rspec ):
742
775
"""
743
776
Iterates over all registers that match rspec and live inside ptag.
777
+
778
+ Ignore `register`s owned by `cluster`s.
744
779
"""
745
780
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
746
785
name = rtag .find ("name" ).text
747
786
if matchname (name , rspec ):
748
787
yield rtag
@@ -752,8 +791,10 @@ def iter_registers_with_matches(self, rspec):
752
791
753
792
Each element is a tuple of the matching register and the rspec substring
754
793
that it matched.
794
+
795
+ Ignore `register`s owned by `cluster`s.
755
796
"""
756
- for rtag in self .ptag . iter ( "register " ):
797
+ for rtag in self .iter_registers ( "* " ):
757
798
name = rtag .find ("name" ).text
758
799
if matchname (name , rspec ):
759
800
yield (rtag , matchsubspec (name , rspec ))
@@ -821,7 +862,7 @@ def add_register(self, rname, radd):
821
862
parent = self .ptag .find ("registers" )
822
863
if parent is None :
823
864
parent = ET .SubElement (self .rtag , "registers" )
824
- for rtag in parent . iter ( "register " ):
865
+ for rtag in self . iter_registers ( "* " ):
825
866
if rtag .find ("name" ).text == rname :
826
867
raise SvdPatchError (
827
868
"peripheral {} already has a register {}" .format (
@@ -888,7 +929,7 @@ def copy_register(self, rname, rderive):
888
929
)
889
930
srcname = rderive ["_from" ]
890
931
source = None
891
- for rtag in parent . iter ( "register " ):
932
+ for rtag in self . iter_registers ( "* " ):
892
933
if rtag .find ("name" ).text == rname :
893
934
raise SvdPatchError (
894
935
"peripheral {} already has a register {}" .format (
@@ -923,6 +964,27 @@ def delete_register(self, rspec):
923
964
for rtag in list (self .iter_registers (rspec )):
924
965
self .ptag .find ("registers" ).remove (rtag )
925
966
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
+
926
988
def modify_cluster (self , cspec , cmod ):
927
989
"""Modify cspec inside ptag according to cmod."""
928
990
for ctag in self .iter_clusters (cspec ):
@@ -939,7 +1001,11 @@ def strip(self, substr, strip_end=False):
939
1001
beginning of the name by default.
940
1002
"""
941
1003
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
943
1009
nametag = rtag .find ("name" )
944
1010
nametag .text = regex .sub ("" , nametag .text )
945
1011
@@ -1162,6 +1228,118 @@ def process_register(self, rspec, register, update_fields=True):
1162
1228
if rcount == 0 :
1163
1229
raise MissingRegisterError (f"Could not find { pname } :{ rspec } " )
1164
1230
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
+
1165
1343
1166
1344
def sorted_fields (fields ):
1167
1345
return sorted (fields , key = lambda ftag : get_field_offset_width (ftag )[0 ])
@@ -1640,3 +1818,14 @@ def main(yaml_file):
1640
1818
1641
1819
# SVD should now be updated, write it out
1642
1820
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