Skip to content

Commit a8ac1df

Browse files
authored
Merge pull request #134 from RHamalainen/master
Extend support for clusters (Python only)
2 parents a1e6374 + 299a751 commit a8ac1df

File tree

1 file changed

+193
-4
lines changed

1 file changed

+193
-4
lines changed

svdtools/patch.py

Lines changed: 193 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111
import re
1212
from collections import OrderedDict
1313
from fnmatch import fnmatchcase
14+
from pathlib import Path
15+
from typing import Any, Generator, Union
1416

1517
import lxml.etree as ET
1618
import yaml
1719
from braceexpand import braceexpand
20+
from lxml.etree import _Element as Element
21+
from lxml.etree import _ElementTree as ElementTree
1822

1923
DEVICE_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+
377391
class SvdPatchError(ValueError):
378392
pass
379393

@@ -398,6 +412,10 @@ class UnknownTagError(SvdPatchError):
398412
pass
399413

400414

415+
class MissingClusterError(SvdPatchError):
416+
pass
417+
418+
401419
class 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

11661344
def 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

Comments
 (0)