1# Copyright (C) 2021-2022 Intel Corporation.
2#
3# SPDX-License-Identifier: BSD-3-Clause
4#
5
6import logging
7import lxml.etree
8from extractors.helpers import add_child, get_node
9
10from cpuparser import parse_cpuid
11import cpuparser.msr as msr
12from acpiparser import parse_rtct
13import acpiparser.rtct
14
15known_cbms = {
16    # From 11th Gen Intel(R) Core(TM) Processors Real-Time Tuning Guide, document number 640980-1.4
17    "11th Gen Intel(R) Core(TM) i3-1115GRE": 12,
18    "11th Gen Intel(R) Core(TM) i5-1145GRE": 8,
19    "11th Gen Intel(R) Core(TM) i7-1185GRE": 12,
20}
21
22def infer_l3_cat(cpu_id, processor_model_node, cache_node):
23    # First of all, existence of L3 CAT is indicated by the presence of IA32_L3_MASK_0 at C90H
24    try:
25        ia32_l3_mask_0 = msr.MSR_IA32_L3_MASK_n(0).rdmsr(cpu_id)
26    except IOError:
27        return
28
29    # If L3 CAT does exist, try inferring its parameters:
30    #
31    #   - For capacity mask length, detect in an trial-and-error way starting from:
32    #     a. the capacity mask length documented in any public real-time tuning guide, if any.
33    #     b. or, the number of ways of the L3 cache.
34    #
35    #   - For the number of CLOS IDs available, detect by searching the last programmable IA32_L3_MASK_n register within
36    #     the C90H - D0FH range which is the architecturally defined MSR space for those registers.
37    #
38    #   - For CDP, try setting the enable bit in IA32_L3_QOS_CFG. CDP is available if and only if that MSR is present
39    #     and its bit 0 can be set.
40
41    # Initial guess of the capacity mask length
42    capacity_mask_length = int(cache_node.find("ways").text)
43    processor_model = processor_model_node.get("description")
44    for k, v in known_cbms.items():
45        if processor_model.startswith(k):
46            capacity_mask_length = v
47            break
48
49    # Verify our guess. If the verification fails, decrease by 1 and guess again.
50    while capacity_mask_length > 0:
51        ia32_l3_mask_0.bit_mask = (1 << capacity_mask_length) - 1
52        try:
53            ia32_l3_mask_0.wrmsr()
54            break
55        except IOError:
56            capacity_mask_length = capacity_mask_length - 1
57            continue
58    else:
59        logging.debug("All writes to IA32_L3_MASK_0 failed. Cannot guess the capacity mask length of L3 CAT.")
60        return
61
62    # Binary search of the number of CLOS available
63    known_good = 1
64    known_bad = 129
65    while known_good + 1 < known_bad:
66        mid = (known_good + known_bad) // 2
67        try:
68            msr.MSR_IA32_L3_MASK_n(mid - 1).rdmsr(cpu_id)
69            known_good = mid
70        except IOError:
71            known_bad = mid
72    clos_number = known_good
73
74    # Detect availability of CDP by trying to write the enable bit.
75    try:
76        l3_qos_cfg = msr.MSR_IA32_L3_QOS_CFG.rdmsr(cpu_id)
77        l3_qos_cfg.cdp_enable = 1
78        l3_qos_cfg.wrmsr()
79        has_cdp = True
80    except IOError:
81        has_cdp = False
82
83    cap = add_child(cache_node, "capability", None, id="CAT")
84    add_child(cap, "capacity_mask_length", str(capacity_mask_length))
85    add_child(cap, "clos_number", str(clos_number))
86    if has_cdp:
87        add_child(cap, "capability", None, id="CDP")
88
89def extract_topology(args, root_node, caches_node):
90    threads = root_node.xpath("//processors//*[cpu_id]")
91    for thread in threads:
92        subleaf = 0
93        while True:
94            cpu_id = int(get_node(thread, "cpu_id/text()"))
95            leaf_4 = parse_cpuid(4, subleaf, cpu_id)
96            cache_type = leaf_4.cache_type
97            if cache_type == 0:
98                break
99
100            cache_level = leaf_4.cache_level
101            shift_width = leaf_4.max_logical_processors_sharing_cache.bit_length() - 1
102            cache_id = hex(int(get_node(thread, "apic_id/text()"), base=16) >> shift_width)
103
104            n = get_node(caches_node, f"cache[@id='{cache_id}' and @type='{cache_type}' and @level='{cache_level}']")
105            if n is None:
106                n = add_child(caches_node, "cache", None, level=str(cache_level), id=cache_id, type=str(cache_type))
107                add_child(n, "cache_size", str(leaf_4.cache_size))
108                add_child(n, "line_size", str(leaf_4.line_size))
109                add_child(n, "ways", str(leaf_4.ways))
110                add_child(n, "sets", str(leaf_4.sets))
111                add_child(n, "partitions", str(leaf_4.partitions))
112                add_child(n, "self_initializing", str(leaf_4.self_initializing))
113                add_child(n, "fully_associative", str(leaf_4.fully_associative))
114                add_child(n, "write_back_invalidate", str(leaf_4.write_back_invalidate))
115                add_child(n, "cache_inclusiveness", str(leaf_4.cache_inclusiveness))
116                add_child(n, "complex_cache_indexing", str(leaf_4.complex_cache_indexing))
117                add_child(n, "processors")
118
119                # Check support of Cache Allocation Technology
120                leaf_10 = parse_cpuid(0x10, 0, cpu_id)
121                if cache_level == 2:
122                    leaf_10 = parse_cpuid(0x10, 2, cpu_id) if leaf_10.l2_cache_allocation == 1 else None
123                elif cache_level == 3:
124                    leaf_10 = parse_cpuid(0x10, 1, cpu_id) if leaf_10.l3_cache_allocation == 1 else None
125                else:
126                    leaf_10 = None
127                if leaf_10 is not None:
128                    cap = add_child(n, "capability", None, id="CAT")
129                    add_child(cap, "capacity_mask_length", str(leaf_10.capacity_mask_length))
130                    add_child(cap, "clos_number", str(leaf_10.clos_number))
131                    if leaf_10.code_and_data_prioritization == 1:
132                        add_child(n, "capability", None, id="CDP")
133
134                    # Inform the user if L3 CAT capability is specified manually.
135                    if args.add_llc_cat:
136                        logging.warning(r"The last level cache (cache ID: {cache_id}) already reports CAT capability. The explicit settings from the command line options are ignored.")
137                elif cache_level == 3:
138                    if args.add_llc_cat:
139                        # Inject L3 CAT capability specified by the user
140                        cap = add_child(n, "capability", None, id="CAT")
141                        add_child(cap, "capacity_mask_length", str(args.add_llc_cat.capacity_mask_length))
142                        add_child(cap, "clos_number", str(args.add_llc_cat.clos_number))
143                        if args.add_llc_cat.has_CDP:
144                            add_child(cap, "capability", None, id="CDP")
145                    else:
146                        # Try inferring L3 CAT according to the methods described in section 7.2.3, 11th Gen Intel(R)
147                        # Core(TM) Processors Real-Time Tuning Guide (document number: 640980-1.4).
148                        family_id = thread.find("family_id").text
149                        model_id = thread.find("model_id").text
150                        core_type = thread.find("core_type").text
151                        native_model_id = thread.find("native_model_id").text
152                        processor_model_node = get_node(root_node, f"//processors/model[family_id='{family_id}' and model_id='{model_id}' and core_type='{core_type}' and native_model_id='{native_model_id}']")
153                        infer_l3_cat(cpu_id, processor_model_node, n)
154
155            add_child(get_node(n, "processors"), "processor", get_node(thread, "apic_id/text()"))
156
157            subleaf += 1
158
159    def getkey(n):
160        level = int(n.get("level"))
161        id = int(n.get("id"), base=16)
162        type = int(n.get("type"))
163        return (level, id, type)
164    caches_node[:] = sorted(caches_node, key=getkey)
165
166def extract(args, board_etree):
167    root_node = board_etree.getroot()
168    caches_node = get_node(board_etree, "//caches")
169    extract_topology(args, root_node, caches_node)
170