Skip to content

Commit 019b4ac

Browse files
committed
devlib/module: Add irq module for stats and affinity manipulation
FEATURE Add module to collect irq configuration, stats, and affinities from target. Enables setting of affinity.
1 parent a83fe52 commit 019b4ac

File tree

1 file changed

+240
-0
lines changed

1 file changed

+240
-0
lines changed

devlib/module/irq.py

+240
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# Copyright 2024 ARM Limited
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
import devlib.utils.asyn as asyn
17+
from devlib.module import Module
18+
from devlib.utils.misc import ranges_to_list
19+
20+
class Irq(object):
21+
def __init__(self, target, intid, data_dict, sysfs_root, procfs_root):
22+
self.target = target
23+
self.intid = intid
24+
self.sysfs_path = self.target.path.join(sysfs_root, str(intid))
25+
self.procfs_path = self.target.path.join(procfs_root, str(intid))
26+
27+
self.irq_info = self._fix_data_dict(data_dict.copy())
28+
29+
def _fix_data_dict(self, data_dict):
30+
clean_dict = data_dict.copy()
31+
32+
self._fix_sysfs_data(clean_dict)
33+
self._fix_procfs_data(clean_dict)
34+
35+
return clean_dict
36+
37+
def _fix_sysfs_data(self, clean_dict):
38+
clean_dict['wakeup'] = 0 if clean_dict['wakeup'] == 'disabled' else 1
39+
40+
if 'hwirq' not in clean_dict:
41+
clean_dict['hwirq'] = -1
42+
else:
43+
clean_dict['hwirq'] = int(clean_dict['hwirq'])
44+
45+
if 'per_cpu_count' in clean_dict:
46+
del clean_dict['per_cpu_count']
47+
48+
if 'name' not in clean_dict:
49+
clean_dict['name'] = ''
50+
51+
if 'actions' not in clean_dict:
52+
clean_dict['actions'] = ''
53+
else:
54+
alist = clean_dict['actions'].split(',')
55+
if alist[0] == '(null)':
56+
alist = []
57+
clean_dict['actions'] = alist
58+
59+
def _fix_procfs_data(self, clean_dict):
60+
61+
if 'spurious' not in clean_dict:
62+
clean_dict['spurious'] = ''
63+
else:
64+
temp = clean_dict['spurious'].split('\n')
65+
clean_dict['spurious'] = dict([[i.split(' ')[0], i.split(' ')[1]] for i in temp])
66+
67+
for alist in ['smp_affinity_list', 'effective_affinity_list']:
68+
if alist in clean_dict:
69+
if clean_dict[alist] == '':
70+
clean_dict[alist] = []
71+
continue
72+
clean_dict[alist] = ranges_to_list(clean_dict[alist])
73+
74+
@property
75+
def actions(self):
76+
return self.irq_info['actions']
77+
78+
@property
79+
def chip_name(self):
80+
return self.irq_info['chip_name']
81+
82+
@property
83+
def hwirq(self):
84+
return self.irq_info['hwirq']
85+
86+
@property
87+
def name(self):
88+
return None if self.irq_info['name'] == '' else self.irq_info['name']
89+
90+
@property
91+
def type(self):
92+
return self.irq_info['type']
93+
94+
@property
95+
def wakeup(self):
96+
return self.irq_info['wakeup']
97+
98+
@property
99+
def smp_affinity(self):
100+
if 'smp_affinity' in self.irq_info.keys():
101+
return self.irq_info['smp_affinity']
102+
return -1
103+
104+
@smp_affinity.setter
105+
def smp_affinity(self, affinity, verify=True):
106+
aff = str(affinity)
107+
aff_path = self.target.path.join(self.procfs_path, 'smp_affinity')
108+
self.target.write_value(aff_path, aff, verify=verify)
109+
110+
self.update_affinities()
111+
112+
@property
113+
def effective_affinity(self):
114+
if 'effective_affinity' in self.irq_info.keys():
115+
return self.irq_info['effective_affinity']
116+
return -1
117+
118+
def to_dict(self):
119+
return self.irq_info.copy()
120+
121+
@asyn.asyncf
122+
async def update_affinities(self):
123+
"""Read affinity masks from target."""
124+
proc_data = await self.target.read_tree_values.asyn(self.procfs_path, depth=2, check_exit_code=False)
125+
self._fix_procfs_data(proc_data)
126+
127+
for aff in ['smp_affinity', 'effective_affinity', 'smp_affinity_list', 'effective_affinity_list']:
128+
self.irq_info[aff] = proc_data[aff]
129+
130+
class IrqModule(Module):
131+
name = 'irq'
132+
irq_sysfs_root = '/sys/kernel/irq/'
133+
irq_procfs_root = '/proc/irq/'
134+
135+
@staticmethod
136+
def probe(target):
137+
if target.file_exists(IrqModule.irq_sysfs_root):
138+
if target.file_exists(IrqModule.irq_procfs_root):
139+
return True
140+
141+
def __init__(self, target):
142+
self.logger = logging.getLogger(self.name)
143+
self.logger.debug(f'Initialized {self.name} module')
144+
145+
self.target = target
146+
self.irqs = {}
147+
148+
temp_dict = self._scrape_data(self.target, self.irq_sysfs_root, self.irq_procfs_root)
149+
for irq, data in temp_dict.items():
150+
intid = int(irq)
151+
self.irqs[intid] = Irq(self.target, intid, data, self.irq_sysfs_root, self.irq_procfs_root)
152+
153+
@asyn.asyncf
154+
@staticmethod
155+
async def _scrape_data(cls, target, sysfs_path=None, procfs_path=None):
156+
if sysfs_path and procfs_path:
157+
sysfs_dict = await target.read_tree_values.asyn(sysfs_path, depth=2, check_exit_code=False)
158+
procfs_dict = await target.read_tree_values.asyn(procfs_path, depth=2, check_exit_code=False)
159+
160+
for irq, data in sysfs_dict.items():
161+
if irq in procfs_dict.keys():
162+
sysfs_dict[irq] = {**data, **procfs_dict[irq]}
163+
return sysfs_dict
164+
165+
if sysfs_path:
166+
sysfs_dict = await target.read_tree_values.asyn(sysfs_path, depth=2, check_exit_code=False)
167+
return sysfs_dict
168+
if procfs_path:
169+
procfs_dict = await target.read_tree_values.asyn(procfs_path, depth=1, check_exit_code=False)
170+
return procfs_dict
171+
172+
return None
173+
174+
175+
def get_all_irqs(self):
176+
"""Returns list of all interrupt IDs (list of integers)."""
177+
return list(self.irqs.keys())
178+
179+
def get_all_wakeup_irqs(self):
180+
"""Returns list of all wakeup-enabled interrupt IDs (list of integers)."""
181+
return [irq.intid for intid, irq in self.irqs.items() if irq.wakeup == 1]
182+
183+
@asyn.asyncf
184+
async def get_raw_stats(self):
185+
"""Return raw interrupt stats from procfs on target."""
186+
raw_stats = await self.target.read_value.asyn('/proc/interrupts')
187+
return raw_stats
188+
189+
@asyn.asyncf
190+
async def get_stats_dict(self):
191+
"""Returns dict of dicts of irq and IPI stats."""
192+
raw_stats = await self.get_raw_stats.asyn()
193+
194+
nr_cpus = self.target.number_of_cpus
195+
196+
d_irq = {
197+
'intid' : [],
198+
'chip_name' : [],
199+
'hwirq' : [],
200+
'type' : [],
201+
'actions' : [],
202+
}
203+
204+
d_ipi = {
205+
'id' : [],
206+
'purpose' : [],
207+
}
208+
209+
for cpu in range(0, nr_cpus):
210+
d_irq[f'cpu{cpu}'] = []
211+
d_ipi[f'cpu{cpu}'] = []
212+
213+
for line in self.target.irq.get_raw_stats().splitlines()[1:-1]:
214+
intid, data = line.split(':', 1)
215+
data = data.split()
216+
217+
if 'IPI' in intid:
218+
d_ipi['id'].append(int(intid[3:]))
219+
220+
for cpu in range(0, nr_cpus):
221+
d_ipi[f'cpu{cpu}'].append(int(data[cpu]))
222+
223+
d_ipi['purpose'].append(' '.join(data[nr_cpus:]))
224+
else:
225+
d_irq['intid'].append(int(intid))
226+
d_irq['chip_name'].append(data[nr_cpus])
227+
228+
for cpu in range(0, nr_cpus):
229+
d_irq[f'cpu{cpu}'].append(int(data[cpu]))
230+
231+
if 'Level' in data[nr_cpus+1] or 'Edge' in data[nr_cpus+1]:
232+
d_irq['hwirq'].append(None)
233+
d_irq['type'].append(data[nr_cpus+1])
234+
d_irq['actions'].append(data[nr_cpus+2:])
235+
else:
236+
d_irq['hwirq'].append(int(data[nr_cpus+1]))
237+
d_irq['type'].append(data[nr_cpus+2])
238+
d_irq['actions'].append(data[nr_cpus+3:])
239+
240+
return {'irq' : d_irq, 'ipi' : d_ipi}

0 commit comments

Comments
 (0)