-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcmn_devmem.py
1623 lines (1462 loc) · 65.9 KB
/
cmn_devmem.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/python3
"""
CMN (Coherent Mesh Network) driver
Copyright (C) Arm Ltd. 2024. All rights reserved.
SPDX-License-Identifier: Apache 2.0
This is a userspace device driver. It is not expected to disrupt CMN
interconnect operation, but the PMU configuration features might come
into conflict with the Linux driver (drivers/perf/arm-cmn.c).
"""
from __future__ import print_function
import os, sys, struct, time
import traceback
import iommap as mmap
import cmn_devmem_find as cmn_find
import cmn_base
from cmn_enum import *
from cmn_diagram import CMNDiagram
def BITS(x,p,n):
return (x >> p) & ((1<<n)-1)
def BIT(x,p):
return (x >> p) & 1
def hexstr(x):
s = ""
# be portable for Python2/3
for ix in range(len(x)):
s += ("%02x" % ord(x[ix:ix+1]))
return s
assert hexstr(b"\x12\x34") == "1234"
# Diagnostic options for debugging CMN programming issues
DIAG_DEFAULT = 0x00
DIAG_READS = 0x01 # Trace all device reads
DIAG_WRITES = 0x02 # Trace all device writes
class DevMem:
def __init__(self):
self.page_size = os.sysconf("SC_PAGE_SIZE")
self.fd = None
self.fd = open("/dev/mem", "r+b")
self.fno = self.fd.fileno()
def __del__(self):
if self.fd is not None:
self.fd.close()
def map(self, physaddr, size, write=False):
assert (size % self.page_size) == 0
if write:
prot = (mmap.PROT_READ|mmap.PROT_WRITE)
else:
prot = mmap.PROT_READ
m = mmap.mmap(self.fno, size, mmap.MAP_SHARED, prot, offset=physaddr)
return m
def map_read64(m, off):
assert (off % 8) == 0, "invalid offset: 0x%x" % off
raw = m[off:off+8]
x = struct.unpack("<Q", raw)[0]
return x
# Register offsets
# 'any' generally means offset is valid for any node type, or any node type except XP.
CMN_any_NODE_INFO = 0x0000
CMN_any_CHILD_INFO = 0x0080
CMN_any_UNIT_INFO = 0x0900
CMN_any_PMU_EVENT_SEL = 0x2000 # for some nodes it's 0x2008
CMN_CFG_PERIPH_01 = 0x0008
CMN_CFG_PERIPH_23 = 0x0010
CMN_any_SECURE_ACCESS = 0x0980
def node_has_logical_id(nt):
"""
Check if this node type has a "logical id" field.
Logical ids are numbered sequentially from 0 for a given node type.
For DTCs, the logical id is the DTC domain number.
"""
return nt != CMN_NODE_RNSAM
class NotTestable:
def __init__(self, msg):
self.msg = msg
def __bool__(self):
assert False, self.msg
class CMNNode:
"""
A single CMN node, at some offset in the overall peripheral space.
Spans a 16K range of configuration registers.
"""
def __init__(self, cmn, node_offset, map=None, write=False, parent=None, is_external=False):
self.C = cmn
self.diag_trace = DIAG_DEFAULT # defer to owning CMN object
self.parent = parent
self.is_external = is_external
assert node_offset not in self.C.offset_node, "node already discovered: %s" % self.C.offset_node[node_offset]
self.C.offset_node[node_offset] = self
self.write = write
self.node_base_addr = cmn.periphbase + node_offset
if self.C.verbose >= 2:
self.C.log("created node at 0x%x" % self.node_base_addr, level=2)
if map is not None:
self.m = map
else:
self.m = self.C.D.map(self.node_base_addr, self.C.node_size(), write=write)
himem = self.node_base_addr + self.C.node_size()
if himem > self.C.himem:
self.C.himem = himem
self.node_info = 0x0000 # in case we throw in the next line
self.node_info = self.read64(CMN_any_NODE_INFO)
self.child_info = self.read64(CMN_any_CHILD_INFO)
#print("%s%s" % ((self.level()*" "), self))
def do_trace_reads(self):
return (self.diag_trace | self.C.diag_trace) & DIAG_READS
def do_trace_writes(self):
return (self.diag_trace | self.C.diag_trace) & DIAG_WRITES
def ensure_writeable(self):
if not self.write:
self.m = self.C.D.map(self.node_base_addr, self.C.node_size(), write=True)
self.write = True
def read64(self, off):
if self.do_trace_reads():
print()
print("at %s" % self.C.source_line())
self.C.log("%s: read 0x%x (0x%x)" % (str(self), off, self.node_base_addr+off), end="")
data = map_read64(self.m, off)
if self.do_trace_reads():
self.C.log(" => 0x%x" % data, prefix=None)
return data
def test64(self, off, x):
return (self.read64(off) & x) == x
def check_reg_is_writeable(self, off):
"""
Check that the node is in a suitable programming state to allow writing.
Expect a subclass to override this.
"""
pass
def write64(self, off, data, check=None):
self.ensure_writeable()
self.check_reg_is_writeable(off)
if check is None:
check = self.C.check_writes
if self.do_trace_writes():
print("at %s" % self.C.source_line())
self.C.log("%s: write 0x%x := 0x%x" % (str(self), off, data))
self.m[off:off+8] = struct.pack("Q", data)
if check:
ndata = self.read64(off)
assert ndata == data, "%s: at 0x%04x wrote 0x%x, read back 0x%x" % (self, off, data, ndata)
def set64(self, off, mask, check=None):
old = self.read64(off)
self.write64(off, old | mask, check=check)
return (old & mask)
def clear64(self, off, mask, check=None):
old = self.read64(off)
self.write64(off, old & ~mask, check=check)
return (old & mask)
def setclear64(self, off, mask, flag, check=None):
"""
Set or clear a mask (likely a single bit) under control of a flag.
"""
if flag:
old = self.set64(off, mask, check=check)
else:
old = self.clear64(off, mask, check=check)
return old
def level(self):
lv = 0
node = self
while node.parent is not None:
node = node.parent
lv += 1
return lv
def discover_children(self):
"""
From the configuration node, iterate the XPs;
from an XP, iterate the child (device) nodes.
"""
self.children = []
self.n_children = BITS(self.child_info, 0, 16)
child_off = BITS(self.child_info, 16, 16)
assert self.n_children < 200, "CMN discovery found too many node children"
cobits = 30 if self.C.part_ge_650() else 28
for i in range(0, self.n_children):
child = self.read64(child_off + (i*8))
child_offset = BITS(child, 0, cobits)
child_node = self.C.create_node(child_offset, parent=self)
if child_node is None:
# Probably a child of a RN-F port
continue
child_node.is_external = BIT(child, 31)
self.children.append(child_node)
def type(self):
return BITS(self.node_info, 0, 16)
def type_str(self):
return cmn_node_type_str(self.type())
def node_id(self):
return BITS(self.node_info, 16, 16)
def logical_id(self):
"""
The logical ID is programmed by the mesh configurator.
It should be unique for nodes of a particular type.
For a DTC, this is actually a 2-bit field, dtc_domain.
"""
if not node_has_logical_id(self.type()):
return None
return BITS(self.node_info, 32, 16)
def is_XP(self):
return self.type() == CMN_NODE_XP
def XP(self):
"""
For any node, return its crosspoint. For XPs, this is the node itself.
"""
return self if self.is_XP() else self.parent
def is_home_node(self):
return self.type() in [CMN_NODE_HNF, CMN_NODE_HNS]
def XY(self):
id = self.node_id()
cb = self.C.coord_bits
assert cb is not None, "can't get coordinates until mesh size is known"
Y = BITS(id,3,cb)
X = BITS(id,3+cb,cb)
return (X,Y)
def coords(self):
"""
Return device coordinates as a tuple (X, Y, P, D).
For an XP, P and D will be zero.
Otherwise, P is the port number, and D is the device number.
D is generally zero, but for CAL-attached devices it may be 0 or 1.
"""
id = self.node_id()
(X, Y) = self.XY()
# If the CMN has at least one XP with more than 2 device ports,
# all device ids use 2 bits for the port and one for the device.
# Otherwise it's 1 bit for the port and 2 for the device.
if self.parent is not None and self.XP().n_device_bits() == 1:
D = BIT(id, 0)
P = BITS(id, 1, 2)
else:
D = BITS(id, 0, 2)
P = BIT(id, 2)
return (X, Y, P, D)
def device_port(self):
assert not self.is_XP()
(X, Y, P, D) = self.coords()
return P
def device_number(self):
assert not self.is_XP()
(X, Y, P, D) = self.coords()
return D
def show(self):
# Node-specific subclass can override
pass
def extra_str(self):
# Node-specific subclass can override
return ""
def __lt__(self, node):
return self.node_base_addr < node.node_base_addr
def __str__(self):
lid = self.logical_id()
s = "%s" % cmn_node_type_str(self.type())
if lid is not None:
s += "#%u" % lid
if self.is_external:
s += "(ext)"
s += ":0x%x" % self.node_id()
if self.C.verbose:
s = "@0x%x:%s" % (self.node_base_addr, s)
if self.C.coord_bits is not None:
(X, Y, P, D) = self.coords()
if self.is_XP():
s += ":(%u,%u)" % (X, Y)
else:
s += ":(%u,%u,%u,%u)" % (X, Y, P, D)
if self.C.verbose >= 2:
s += ":info=0x%x" % self.node_info
return s
# Debug/Trace Monitor registers in XP.
CMN_DTM_CONTROL = 0x2100
CMN_DTM_CONTROL_DTM_ENABLE = 0x01
CMN_DTM_CONTROL_TRACE_TAG_ENABLE = 0x02 # set TraceTag on a match
CMN_DTM_CONTROL_SAMPLE_PROFILE_ENABLE = 0x04 # use PMSIRR/PMSICR countdown
CMN_DTM_CONTROL_TRACE_NO_ATB = 0x08 # trace to FIFO in XP
CMN650_DTM_UNIT_INFO = 0x910 # CMN-650
CMN700_DTM_UNIT_INFO = 0x960 # CMN-700
CMN_DTM_FIFO_ENTRY_READY = 0x2118 # write 1 to clear
CMN_DTM_FIFO_ENTRY0_0 = 0x2120
def CMN_DTM_FIFO_ENTRY(fn,dn):
return CMN_DTM_FIFO_ENTRY0_0 + (fn * 24) + (dn * 8)
CMN_DTM_WP0_CONFIG = 0x21A0
CMN_DTM_WP0_VAL = 0x21A8
CMN_DTM_WP0_MASK = 0x21B0 # 1 bit means ignore
# CMN_DTM_WP1_CONFIG = 0x21B8
CMN_DTM_PMU_PMSICR = 0x2200 # sampling interval counter
CMN_DTM_PMU_PMSIRR = 0x2208 # sampling interval reload (bits 7:0 must be zero)
CMN_DTM_PMU_CONFIG = 0x2210
CMN_DTM_PMU_CONFIG_PMU_EN = 0x01 # DTM PMU enable - other fields are valid only when this is set
CMN_DTM_PMU_CONFIG_PMEVCNT01_COMBINED = 0x02 # combine PMU counters 0 and 1
CMN_DTM_PMU_CONFIG_PMEVCNT23_COMBINED = 0x04 # combine PMU counters 2 and 3
CMN_DTM_PMU_CONFIG_PMEVENTALL_COMBINED = 0x08 # combine PMU counters 0,1,2 and 3
CMN_DTM_PMU_CONFIG_CNTR_RST = 0x100 # clear live counters upon assertion of snapshot
CMN_DTM_PMU_PMEVCNT = 0x2220 # DTM event counters 0 to 3: 16 bits each
CMN_DTM_PMU_PMEVCNTSR = 0x2240 # DTM event counter shadow
# Port connectivity information.
# For CMN-600/650 this is max 2 ports with east/north immediately following.
# For CMN-700 it is up to 6 ports, with east/north following those.
CMN_XP_DEVICE_PORT_CONNECT_INFO_P0 = 0x08
CMN_XP_DEVICE_PORT_CONNECT_INFO_P1 = 0x10
def CMN_XP_DEVICE_PORT_CONNECT_INFO_P(p):
return 0x08 + 8*p
CMN_XP_DEVICE_PORT_CAL_CONNECTED_BIT = 7
N_WATCHPOINTS = 4
N_FIFO_ENTRIES = 4
class CMNNodeXP(CMNNode):
"""
Crosspoint node. This has special behavior as follows:
- manages child nodes on its ports P0 and P1, possibly several on both.
- contains a Debug/Trace Monitor (DTM)
"""
def __init__(self, *args, **kwargs):
CMNNode.__init__(self, *args, **kwargs)
# We maintain a cached copy of the DTM enable bit so we can fault writes
# to DTM configuration registers when enabled.
self._dtm_is_enabled = None
self._dtm_is_enabled = self.dtm_is_enabled()
def port_nodes(self, rP):
"""
Yield all a port's device nodes, ordered by device number.
(There may be multiple device nodes for a given device number.)
"""
for rD in range(0, 4):
for n in self.port_device_nodes(rP, rD):
yield n
def port_device_nodes(self, rP, rD):
"""
Yield all a port's device nodes, for a given device number.
Note that a RN-F port will only have a RN-SAM device node,
and a SN-F port will not have any device nodes at all.
"""
for n in self.children:
(X, Y, P, D) = n.coords()
if P == rP and D == rD:
yield n
def n_device_ports(self):
"""
The number of device ports on this XP. In general it is not guaranteed that
devices exist on every port. For CMN-6xx, each XP is assumed to have 2 ports.
For CMN-700, the number of device ports is discoverable from the XP's info register
(some XPs might have 3 or 4), but even so, some might be unused.
It is possible for there to be a device on P1 but not on P0.
"""
if self.C.part_ge_700():
return BITS(self.node_info, 48, 4)
else:
return 2
def port_info(self, rP, n=0):
"""
Get the port info (por_mxp_p<n>_info).
This includes the number of devices connected to the port.
"""
if not self.C.part_ge_700():
assert n == 0
off = 0x900 + (rP * 8)
else:
assert n <= 1
off = 0x900 + (rP * 16) + (n * 8)
return self.read64(off)
def port_device_type(self, rP):
"""
Return a "connected device type" value indicating the type of device(s)
attached to this port. This is not the same as the node type.
"""
pinfo = self.read64(CMN_XP_DEVICE_PORT_CONNECT_INFO_P(rP))
dt = BITS(pinfo, 0, 5)
if dt == 0:
return None # indicates there is no device on this port
return dt
def port_device_type_str(self, rP):
"""
The string for the port's "connected device type".
"""
dt = self.port_device_type(rP)
if dt is not None:
return cmn_port_device_type_str(dt)
else:
return "?"
def port_has_cal(self, rP):
"""
Return True if a port has a CAL allowing multiple devices to be connected.
In earlier CMNs a CAL would connect two identical devices.
In later CMNs, CALs can connect up to four devices, or two different RNs.
"""
pinfo = self.read64(CMN_XP_DEVICE_PORT_CONNECT_INFO_P(rP))
return BIT(pinfo, CMN_XP_DEVICE_PORT_CAL_CONNECTED_BIT)
def n_device_bits(self):
"""
In the device node id, the split between port id and device id
(whether it is 2:1 or 1:2) depends on the number of ports on the
individual XP - contrary to the implication of the CMN TRM.
"""
return 1 if self.n_device_ports() > 2 else 2
def port_base_id(self, rP):
return self.node_id() + (rP << self.n_device_bits())
def dtc_domain(self):
"""
Return the DTC domain number of this XP, if known.
TBD: Recent CMN allows an XP to have multiple DTMs, with a corrresponding
dtm_unit_info register for each one - implying an XP's DTMs could be in
different domains. We have not observed this.
"""
if self.C.product_config.product_id == cmn_base.PART_CMN600:
if len(self.C.debug_nodes) == 1:
return 0 # this mesh has only one DTC
else:
# In a CMN-600 with multiple DTCs, we can't discover the assignment.
return None
elif self.C.product_config.product_id == cmn_base.PART_CMN650:
return BITS(self.read64(CMN650_DTM_UNIT_INFO), 0, 2)
else:
return BITS(self.read64(CMN700_DTM_UNIT_INFO), 0, 2)
def check_reg_is_writeable(self, off):
"""
Some DTM configuration registers are only writeable when the DTM is disabled.
"""
if off >= 0x2100 and off <= 0x22ff and off not in [CMN_DTM_CONTROL, CMN_DTM_FIFO_ENTRY_READY] and self._dtm_is_enabled:
assert False, "try to write DTM programming register when DTM is enabled"
def dtm_enable(self):
"""
Enable debug watchpoint and PMU function; prior to writing this bit,
all other DT configuration registers must be programmed; once this bit
is set, other DT configuration registers must not be modified.
"""
self.set64(CMN_DTM_CONTROL, 0x1)
self._dtm_is_enabled = True
def dtm_disable(self):
self.clear64(CMN_DTM_CONTROL, 0x1)
self._dtm_is_enabled = False
def dtm_is_enabled(self):
"""
Check whether por_dtm_control.dtm_enable is set, indicating that DTM is enabled.
"Once this bit is set, other DT configuration registers must not be modified."
"""
e = self.test64(CMN_DTM_CONTROL, CMN_DTM_CONTROL_DTM_ENABLE)
if self._dtm_is_enabled is not None:
assert e == self._dtm_is_enabled, "%s: cached DTM emable state out of sync" % self
return e
def dtm_clear_fifo(self):
"""
Ensure the FIFO is empty, after reading its contents.
Appears to be possible to do this even when the DTM is enabled.
But it appears that data will still go into the FIFO if trace_no_atb is set.
"""
#print("%s: DTM control = 0x%x" % (self, self.read64(CMN_DTM_CONTROL)))
self.write64(CMN_DTM_FIFO_ENTRY_READY, 0xf, check=False)
#self.write64(CMN_DTM_FIFO_ENTRY_READY, 0x0, check=True)
fe = self.read64(CMN_DTM_FIFO_ENTRY_READY)
if fe != 0:
ctl = self.read64(CMN_DTM_CONTROL)
self.C.log("%s: FIFO not empty after clearing: 0x%x (control=0x%x)" % (self, fe, ctl))
def dtm_fifo_ready(self):
return self.read64(CMN_DTM_FIFO_ENTRY_READY)
def dtm_fifo_entry(self, e):
"""
Get a FIFO entry, returning it as (byte string, cycle count)
"""
assert 0 <= e and e <= N_FIFO_ENTRIES
ws = []
for w in range(0, 3):
ws.append(self.read64(CMN_DTM_FIFO_ENTRY(e, w)))
#print("FIFO: 0x%016x 0x%016x 0x%016x" % (ws[0], ws[1], ws[2]))
# The cycle count is at a fixed bit offset in register #2, but the
# offset varies by part number, reflecting the FIFO data size
if self.C.product_config.product_id == cmn_base.PART_CMN600:
dwidth = 144
elif self.C.product_config.product_id == cmn_base.PART_CMN650:
dwidth = 160
else:
dwidth = 176
doff = dwidth - 128
cc = BITS(ws[2], doff, 16)
b = struct.pack("QQQ", ws[0], ws[1], ws[2])[:(dwidth//8)]
return (b, cc)
def dtm_wp_details(self, wp):
"""
Return a tuple indicating the current configuration of a watchpoint,
so that it can be decoded.
(nodeid, dev:0..3, wp#, channel:0..3, format:0..7, cc)
Counterpart of dtm_set_watchpoint()
"""
assert 0 <= wp and wp <= N_WATCHPOINTS
cfg = self.read64(CMN_DTM_WP0_CONFIG+(wp*24))
VC = BITS(cfg, 1, 2)
dev = BIT(cfg, 0)
if self.n_device_ports() > 2:
dev |= (BIT(cfg, 17) << 1)
type = BITS(cfg, self.C.DTM_WP_PKT_TYPE_SHIFT, 3)
cc = BIT(cfg, self.C.DTM_WP_PKT_TYPE_SHIFT+3)
return (self.node_id(), dev, wp, VC, type, cc)
def dtm_atb_packet_header(self, wp, lossy=0):
"""
Construct a trace packet header, as if for a packet output on ATB.
"""
(nid, dev, wp, VC, type, cc) = self.dtm_wp_details()
if self.C.product_config.product_id == cmn_base.PART_CMN600:
h = (VC << 30) | (dev << 29) | (wp << 27) | (type << 24) | (nid << 8) | 0x40 | (cc << 4) | lossy
else:
h = (VC << 28) | (wp << 24) | ((nid >> 3) << 11) | (dev << 8) | 0x40 | (cc << 4) | (type << 1) | lossy
return h
def pmu_enable(self):
self.set64(CMN_DTM_PMU_CONFIG, CMN_DTM_PMU_CONFIG_PMU_EN)
def pmu_disable(self):
self.clear64(CMN_DTM_PMU_CONFIG, CMN_DTM_PMU_CONFIG_PMU_EN)
def pmu_is_enabled(self):
return self.test64(CMN_DTM_PMU_CONFIG, CMN_DTM_PMU_CONFIG_PMU_EN)
def pmu_event_input_selector_str(self, eis):
if eis <= 0x03:
return "Watchpoint %u" % eis
elif eis <= 0x07:
xpen = eis - 4
xpe = BITS(self.read64(CMN_any_PMU_EVENT_SEL),(xpen*8),8)
chn = ["REQ","RSP","SNP","DAT","?4","?5","?6","?7"][BITS(xpe,5,3)]
ifc = ["E","W","N","S","P0","P1","?6","?7"][BITS(xpe,2,3)]
evt = ["none", "txvalid", "txstall", "partial"][BITS(xpe,0,2)]
xpes = "xp-%s-%s-%s" % (ifc, chn, evt)
return "XP PMU Event %u (event=0x%02x: %s)" % (xpen, xpe, xpes)
elif eis >= 0x10:
port = (eis >> 4) - 1
device = BITS(eis,2,2)
return "Port %u Device %u PMU Event %u" % (port, device, (eis&3))
else:
return "?(eis=0x%x)" % eis
def dtm_set_control(self, control=0, atb=False, tag=False, enable=False):
"""
Configure the DTM, which controls all watchpoints an PMU.
Note that this function isn't read/modify/write.
"""
if not atb:
control |= CMN_DTM_CONTROL_TRACE_NO_ATB
if tag:
control |= CMN_DTM_CONTROL_TRACE_TAG_ENABLE
if enable:
control |= CMN_DTM_CONTROL_DTM_ENABLE
self.write64(CMN_DTM_CONTROL, control)
self._dtm_is_enabled = enable
def dtm_set_watchpoint(self, wp, val=0, mask=0xffffffffffffffff, config=0, gen=True, group=None, format=None, chn=None, dev=None, cc=False, exclusive=False, combine=False):
"""
Configure a watchpoint on the XP. The DTM should be disabled.
The mask is the bits we don't care about. I.e. 0 is exact match, 0xffffffffffffffff is don't care.
"""
if gen:
config |= self.C.DTM_WP_PKT_GEN
if combine:
config |= self.C.DTM_WP_COMBINE
if format is not None:
config |= (format << self.C.DTM_WP_PKT_TYPE_SHIFT)
if chn is not None:
assert chn in [0, 1, 2, 3]
config |= (chn << 1)
if dev is not None:
dev0 = dev & 1
config |= (dev0 << 0)
if dev >= 2:
# dev_sel is actually the port number, not the device number
assert dev < self.n_device_ports(), "%s: invalid dev_sel=%u" % (self, dev)
dev1 = dev >> 1
config |= (dev1 << 17)
if group is not None:
config |= (group << 4) # for CMN-650 and CMN-700 this is a 2-bit field[5:4]
if cc:
# note: must also be enabled in the DTM
config |= self.C.DTM_WP_CC_EN
if exclusive:
config |= self.C.DTM_WP_EXCLUSIVE
self.write64(CMN_DTM_WP0_VAL+(wp*24), val)
self.write64(CMN_DTM_WP0_MASK+(wp*24), mask)
self.write64(CMN_DTM_WP0_CONFIG+(wp*24), config)
def show_dtm(self, show_pmu=True):
dtm_control = self.read64(CMN_DTM_CONTROL)
fifo = self.read64(CMN_DTM_FIFO_ENTRY_READY)
dom = self.dtc_domain()
print(" %s:" % self, end="")
if dom is not None:
print(" (DTC%u)" % dom, end="")
print(" DTM control: 0x%08x" % (dtm_control), end="")
if dtm_control & CMN_DTM_CONTROL_DTM_ENABLE:
print(" (enabled)", end="")
if dtm_control & CMN_DTM_CONTROL_TRACE_TAG_ENABLE:
print(" (trace tag)", end="")
if dtm_control & CMN_DTM_CONTROL_SAMPLE_PROFILE_ENABLE:
print(" (sample profile)", end="")
if dtm_control & CMN_DTM_CONTROL_TRACE_NO_ATB:
print(" (to FIFO)", end="")
else:
print(" (to ATB)", end="")
print(", FIFO: 0x%x" % (fifo))
if not (dtm_control & CMN_DTM_CONTROL_TRACE_NO_ATB):
fifo = 0xf # if tracing to ATB, show all FIFO contents
for wp in range(0, N_WATCHPOINTS):
wctl = self.read64(CMN_DTM_WP0_CONFIG+(wp*24))
wval = self.read64(CMN_DTM_WP0_VAL+(wp*24))
wmsk = self.read64(CMN_DTM_WP0_MASK+(wp*24))
if wctl or wval or wmsk:
chn = BITS(wctl, 1, 3)
port = BIT(wctl, 0) # wp_dev_sel
if self.C.product_config.product_id == cmn_base.PART_CMN600:
grp = BITS(wctl, 4, 1)
else:
grp = BITS(wctl, 4, 2)
pkt_type = BITS(wctl, self.C.DTM_WP_PKT_TYPE_SHIFT, 3) # 9 for CMN-600, 11 otherwise
print(" WP #%u: ctrl=0x%016x comp=0x%016x mask=0x%016x" % (wp, wctl, wval, wmsk), end="")
print(" P%u %s type=%u" % (port, ["REQ","RSP","SNP","DAT"][chn], pkt_type), end="") # sic: values 4..7 are reserved
if grp:
print(" grp=%u" % grp, end="")
if wctl & self.C.DTM_WP_COMBINE:
print(" combine", end="")
if wctl & self.C.DTM_WP_EXCLUSIVE:
print(" exclusive", end="")
if wctl & self.C.DTM_WP_PKT_GEN:
print(" pkt_gen", end="")
if wctl & self.C.DTM_WP_CC_EN:
print(" cc", end="")
print()
if fifo != 0:
# Show FIFO contents. Note that in read mode (TRACECTL[3]==1), each FIFO entry is
# allocated to the corresponding WP - i.e. each WP has a 1-entry FIFO.
for e in range(0,N_FIFO_ENTRIES):
if fifo & (1<<e):
(data, cc) = self.dtm_fifo_entry(e)
print(" FIFO #%u: %s cc=0x%04x" % (e, hexstr(data), cc))
if show_pmu:
pmu_config = self.read64(CMN_DTM_PMU_CONFIG)
if pmu_config:
pmu_pmevcnt = self.read64(CMN_DTM_PMU_PMEVCNT)
print(" PMU config: 0x%016x" % (pmu_config))
print(" PMU counts: 0x%016x" % (pmu_pmevcnt))
show_dtm_pmu_config(self)
# Debug/Trace Controller registers (e.g. CMN-600 TRM Table 3-4)
CMN_DTC_CTL = 0xA00
CMN_DTC_CTL_DT_EN = 0x01 # Enable debug, trace and PMU features
CMN_DTC_CTL_DBGTRIGGER_EN = 0x02 # DBGWATCHTRIG enable
CMN_DTC_CTL_ATBTRIGGER_EN = 0x04 # ATB trigger enable
CMN_DTC_CTL_DT_WAIT_FOR_TRIGGER = 0x08 # Wait for cross trigger before trace enable
CMN_DTC_CTL_CG_DISABLE = 0x400 # Disable DT architectural clock gates
CMN_DTC_TRACECTRL = 0xA30
CMN_DTC_TRACECTRL_CC_ENABLE = 0x100 # Cycle count enable
CMN_DTC_TRACEID = 0xA48
CMN_DTC_PMEVCNT = 0x2000 # AB at 0x2000, CD at 0x2010, EF at 0x2020, GH at 0x2030
CMN_DTC_PMCCNTR = 0x2040 # cycle counter (40-bit)
CMN_DTC_PMEVCNTSR = 0x2050 # AB at 0x2050, CD at 0x2060, EF at 0x2070, GH at 0x2080 (shadow regs)
CMN_DTC_PMCCNTRSR = 0x2090 # cycle counter (shadow register)
CMN_DTC_PMCR = 0x2100 # PMU control register
CMN_DTC_PMCR_PMU_EN = 0x01
CMN_DTC_PMCR_OVFL_INTR_EN = 0x40
CMN_DTC_PMOVSR = 0x2118 # PMU overflow status (read-only)
CMN_DTC_PMOVSR_CLR = 0x2120 # PMU overflow clear (write-only)
CMN_DTC_PMSSR = 0x2128 # PMU snapshot status (read-only)
CMN_DTC_PMSSR_SS_STATUS = 0x01ff # Snapshot status (7:0 events; 8 cycles)
CMN_DTC_PMSSR_SS_CFG_ACTIVE = 0x8000 # PMU snapshot activated from configuration write
CMN_DTC_PMSSR_SS_PIN_ACTIVE = 0x10000 # PMU snapshot activated from PMUSNAPSHOTREQ
CMN_DTC_PMSRR = 0x2130 # PMU snapshot request (write-only)
CMN_DTC_PMSRR_SS_REQ = 0x01 # Write-only - request a snapshot
CMN_DTC_CLAIM = 0x2DA0 # set (lower 32 bits) or clear (upper 32 bits) claim tags
CMN_DTC_AUTHSTATUS_DEVARCH = 0x2DB8
class CMNNodeDT(CMNNode):
"""
DTC (debug/trace controller) node. There is one per DTC domain.
The one located in the HN-D is designated as DTC0.
"""
def atb_traceid(self):
return BITS(self.read64(CMN_DTC_TRACEID),0,7)
def set_atb_traceid(self, x):
if self.C.verbose:
self.C.log("ATB ID 0x%02x: %s" % (x, self))
self.write64(CMN_DTC_TRACEID, x)
def dtc_domain(self):
"""
The domain number for this DTC. We'd expect it to be the
same as the domain number for the DTC's XP's DTM.
"""
return BITS(self.node_info, 32, 2)
def dtc_enable(self, cc=None, pmu=None, clock_disable_gating=None):
"""
Enable the DTC. Optionally also enable other DTC features,
e.g.
- cycle-counting for trace
- PMU
- always-on clock (i.e. disable clock-gating)
"""
self.C.log("DTC enable: %s" % self)
self.set64(CMN_DTC_CTL, CMN_DTC_CTL_DT_EN)
if cc:
self.set64(CMN_DTC_TRACECTRL, CMN_DTC_TRACECTRL_CC_ENABLE)
if pmu:
self.pmu_enable()
if clock_disable_gating is not None:
self.clock_disable_gating(clock_disable_gating)
def dtc_disable(self):
self.C.log("DTC disable: %s" % self)
self.clear64(CMN_DTC_CTL, CMN_DTC_CTL_DT_EN)
def dtc_is_enabled(self):
return self.test64(CMN_DTC_CTL, CMN_DTC_CTL_DT_EN)
def pmu_enable(self):
self.set64(CMN_DTC_PMCR, CMN_DTC_PMCR_PMU_EN)
def pmu_disable(self):
self.clear64(CMN_DTC_PMCR, CMN_DTC_PMCR_PMU_EN)
def pmu_is_enabled(self):
return self.test64(CMN_DTC_PMCR, CMN_DTC_PMCR_PMU_EN)
def pmu_clear(self):
for i in range(0,8,2):
self.write64(CMN_DTC_PMEVCNT + 8*i, 0)
self.write64(CMN_DTC_PMEVCNTSR + 8*i, 0)
def pmu_cc(self):
return self.read64(CMN_DTC_PMCCNTR)
def pmu_snapshot(self):
"""
Cause the DTC to send a PMU snapshot instruction to the DTMs.
Return the status flags, or None if the snapshot did not complete.
"""
status = None
if self.C.verbose:
self.C.log("PMU snapshot from %s" % (self))
c0 = self.pmu_cc()
s0 = self.read64(CMN_DTC_PMSSR)
self.write64(CMN_DTC_PMSRR, CMN_DTC_PMSRR_SS_REQ, check=False)
s1 = self.read64(CMN_DTC_PMSSR)
# "The DTC updates por_dt_pmssr.ss_status after receiving PMU snapshot
# packets. Software can poll this register field to check if the snapshot
# process is complete." We also check that the snapshot is not active.
for i in range(0, 10):
ssr = self.read64(CMN_DTC_PMSSR)
if not (ssr & CMN_DTC_PMSSR_SS_CFG_ACTIVE):
status = BITS(ssr, 0, 9) # Return the status (0..7: counters, 8: cycle counter)
break
assert status is not None, "%s: snapshot did not complete after %u reads" % (self, i)
c1 = self.pmu_cc()
if self.C.verbose:
self.C.log("PMU snapshot complete: 0x%x (cyc=0x%x) => 0x%x (cyc=0x%x) => 0x%x, %u reads" % (s0, c0, s1, c1, ssr, i))
return status
def clock_disable_gating(self, disable_gating=True):
"""
We need to set the "disable clock-gating" bit... this allows
the clock to run all the time.
Return the previous setting.
"""
return self.setclear64(CMN_DTC_CTL, CMN_DTC_CTL_CG_DISABLE, disable_gating)
def extra_str(self):
return "DTC%u" % self.dtc_domain()
def show(self, pfx=""):
"""
DTC-specific printing
"""
print("%s%s: " % (pfx, self))
auth = self.read64(CMN_DTC_AUTHSTATUS_DEVARCH)
print("%s DTC auth: 0x%x" % (pfx, auth))
ctl = self.read64(CMN_DTC_CTL)
print("%s DTC control: 0x%x" % (pfx, ctl), end="")
if ctl & CMN_DTC_CTL_DT_EN:
print(" (enabled)", end="")
else:
print(" (disabled)", end="")
if ctl & CMN_DTC_CTL_DBGTRIGGER_EN:
print(" (dbgtrigger)", end="")
if ctl & CMN_DTC_CTL_ATBTRIGGER_EN:
print(" (ATB trigger)", end="")
if ctl & CMN_DTC_CTL_DT_WAIT_FOR_TRIGGER:
print(" (wait for triggers: %u)" % BITS(ctl,4,6), end="")
if ctl & CMN_DTC_CTL_CG_DISABLE:
print(" (clock-gating disabled)", end="")
else:
print(" (clock-gating enabled)", end="")
print()
tracectrl = self.read64(CMN_DTC_TRACECTRL)
print("%s ATB trace control: 0x%x" % (pfx, tracectrl), end="")
if BITS(tracectrl,5,3):
print(" (TS:%uK cycles)" % (1 << BITS(tracectrl,5,3)), end="")
if tracectrl & CMN_DTC_TRACECTRL_CC_ENABLE:
print(" (cc_enable)", end="")
print()
print("%s ATB trace id: 0x%02x" % (pfx, self.atb_traceid()))
# Show global PMU configuration and counter values
# Read the counter values twice, to indicate if they are (rapidly) changing
pmcr = self.read64(CMN_DTC_PMCR)
print("%s DTC PMU control: 0x%x" % (pfx, pmcr), end="")
if pmcr & CMN_DTC_PMCR_PMU_EN:
print(" (enabled)", end="")
print()
for rbase in [CMN_DTC_PMEVCNT, CMN_DTC_PMEVCNTSR]:
ecs = [self.read64(rbase + 8*i) for i in range(0,8,2)]
if rbase == CMN_DTC_PMEVCNT:
pass
else:
ssr = self.read64(CMN_DTC_PMSSR)
print("%s DTC PMU snapshot status: 0x%08x" % (pfx, ssr))
for i in range(0, 8, 2):
cv = self.read64(rbase + 8*i)
c0a = (ecs[i>>1] & 0xffffffff)
c1a = (ecs[i>>1] >> 32)
c0b = (cv & 0xffffffff)
c1b = (cv >> 32)
c0x = (c0a != c0b)
c1x = (c1a != c1b)
if (i % 4) == 0:
print("%s " % (pfx), end="")
print(" #%u: 0x%08x%s #%u: 0x%08x%s" % (i, c0b, " *"[c0x], i+1, c1b, " *"[c1x]), end="")
if (i % 4) == 2:
print()
class CMN:
"""
A complete CMN mesh interconnect.
There is usually one per die.
The interconnect has a base address of the entire 256Mb (0x10000000) region,
and an address within that for the root region.
Component regions are 16Mb for CMN-600/650 and 64Mb for CMN-700.
"""
#DTM_WP_PKT_GEN = 0x0100 # capture a packet (TBD: 0x400 on CMN-700)
#DTM_WP_CC_EN = 0x1000 # enable cycle count (TBD: 0x4000 on CMN-700)
def __init__(self, cmn_loc, check_writes=False, verbose=0, restore_dtc_status=False, secure_accessible=None):
self.check_writes = check_writes
self.verbose = verbose
self.diag_trace = DIAG_WRITES if (verbose >= 2) else DIAG_DEFAULT
self._restore_dtc_status = restore_dtc_status
self.secure_accessible = secure_accessible # if None, will be found from CFG
self.periphbase = cmn_loc.periphbase
rootnode_offset = cmn_loc.rootnode_offset
assert rootnode_offset >= 0 and rootnode_offset < 0x4000000
self.himem = self.periphbase # will be updated as we discover nodes
if verbose:
print("CMN: PERIPHBASE=0x%x, CONFIG=0x%x" % (self.periphbase, self.periphbase+rootnode_offset))
# CMNProductConfig object will be created when we read CMN_CFG_PERIPH_01
# from the root node
self.product_config = None
self.D = DevMem()
self.offset_node = {} # nodes indexed by register space offset
# How do we find the dimensions?
# We could look at the maximum X,Y across all XPs. But to decode X,Y
# from node_info we need the dimensions.
# So, we defer getting XP coordinates, until we count the XPs, then heuristically
# say that more than 16 XPs means 3 bits and more than 256 XPs means 4 bits.
self.coord_bits = None
self.dimX = None
self.dimY = None
self.coord_XP = {} # XPs indexed by (X,Y)
self.logical_id_XP = {} # XPs indexed by logical ID
self.logical_id = {} # Non-XP nodes indexed by (type, logical_id)
self.debug_nodes = [] # DTC(s). A large mesh might have more than one.
self.extra_ports = NotTestable("shouldn't calculate device ids before all XPs seen")
# Discovery phase.
self.creating = True
# we can't map nodes until we know the node size, but we don't
# know that until we've mapped the root config node...
# create a temporary 16K mapping to get out of that.
temp_m = self.D.map(self.periphbase+rootnode_offset, 0x4000)
id01 = map_read64(temp_m, CMN_CFG_PERIPH_01)
product_id = (BITS(id01, 32, 4) << 8) | BITS(id01, 0, 8)
# We can't get chi_version() until we've read unit_info, and
self.product_config = cmn_base.CMNConfig(product_id=product_id)
del temp_m
self.rootnode = self.create_node(rootnode_offset)
self.unit_info = self.rootnode.read64(CMN_any_UNIT_INFO)
# The release is e.g. r0p0, r1p2
self.product_config.revision = BITS(self.rootnode.read64(CMN_CFG_PERIPH_23), 4, 4)
self.product_config.mpam_enabled = self.part_ge_650() and BIT(self.unit_info, 49)
self.product_config.chi_version = self.chi_version()
#
# Now traverse the CMN space to discover all the nodes.
#
self.rootnode.discover_children()
self.creating = False
#
# We've discovered the XPs but we generally don't yet know their X,Y coordinates,
# since we don't know coord_bits.
n_XPs = len(list(self.XPs()))
# For some XPs, the coordinates are independent of coord_bits. These include (0,0) and (0,1).
# Using the conventional logical_id assignment, (0,1) will have logical_id == dimX.
any_extra_ports_seen = False
for xp in self.XPs():
assert xp.logical_id() < n_XPs
if xp.n_device_ports() > 2:
any_extra_ports_seen = True
self.extra_ports = any_extra_ports_seen
for xp in self.XPs():
if BITS(xp.node_info,19,8) == 0x01: # XP at (0,1), regardless of coord_bits
self.dimX = xp.logical_id()
break
if self.dimX is None:
self.dimX = n_XPs
assert (n_XPs % self.dimX) == 0, "%u XPs but X dimension is %u" % (n_XPs, self.dimX)
self.dimY = n_XPs // self.dimX
self.coord_bits = cmn_base.id_coord_bits(self.dimX, self.dimY)
md = max(self.dimX, self.dimY)
if md >= 9:
self.coord_bits = 4
elif md >= 5:
self.coord_bits = 3
else:
self.coord_bits = 2