-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
c60737c
commit e89bb70
Showing
1 changed file
with
243 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
# Copyright 2024 ARM Limited | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import logging | ||
import devlib.utils.asyn as asyn | ||
from devlib.module import Module | ||
from devlib.utils.misc import ranges_to_list | ||
|
||
class Irq(object): | ||
def __init__(self, target, intid, data_dict, sysfs_root, procfs_root): | ||
self.target = target | ||
self.intid = intid | ||
self.sysfs_path = self.target.path.join(sysfs_root, str(intid)) | ||
self.procfs_path = self.target.path.join(procfs_root, str(intid)) | ||
|
||
self.irq_info = self._fix_data_dict(data_dict.copy()) | ||
|
||
def _fix_data_dict(self, data_dict): | ||
clean_dict = data_dict.copy() | ||
|
||
self._fix_sysfs_data(clean_dict) | ||
self._fix_procfs_data(clean_dict) | ||
|
||
return clean_dict | ||
|
||
def _fix_sysfs_data(self, clean_dict): | ||
clean_dict['wakeup'] = 0 if clean_dict['wakeup'] == 'disabled' else 1 | ||
|
||
if 'hwirq' not in clean_dict: | ||
clean_dict['hwirq'] = -1 | ||
else: | ||
clean_dict['hwirq'] = int(clean_dict['hwirq']) | ||
|
||
if 'per_cpu_count' in clean_dict: | ||
clean_dict['per_cpu_count'] = clean_dict['per_cpu_count'].split(',') | ||
|
||
if 'name' not in clean_dict: | ||
clean_dict['name'] = '' | ||
|
||
if 'actions' not in clean_dict: | ||
clean_dict['actions'] = '' | ||
else: | ||
alist = clean_dict['actions'].split(',') | ||
if alist[0] == '(null)': | ||
alist = [] | ||
clean_dict['actions'] = alist | ||
|
||
def _fix_procfs_data(self, clean_dict): | ||
|
||
if 'spurious' not in clean_dict: | ||
clean_dict['spurious'] = '' | ||
else: | ||
temp = clean_dict['spurious'].split('\n') | ||
clean_dict['spurious'] = dict([[i.split(' ')[0], i.split(' ')[1]] for i in temp]) | ||
|
||
for alist in ['smp_affinity_list', 'effective_affinity_list']: | ||
if alist in clean_dict: | ||
if clean_dict[alist] == '': | ||
clean_dict[alist] = [] | ||
continue | ||
clean_dict[alist] = ranges_to_list(clean_dict[alist]) | ||
|
||
@property | ||
def actions(self): | ||
return self.irq_info['actions'] | ||
|
||
@property | ||
def chip_name(self): | ||
return self.irq_info['chip_name'] | ||
|
||
@property | ||
def hwirq(self): | ||
return self.irq_info['hwirq'] | ||
|
||
@property | ||
def name(self): | ||
return None if self.irq_info['name'] == '' else self.irq_info['name'] | ||
|
||
@property | ||
def per_cpu_count(self): | ||
return self.irq_info['per_cpu_count'] | ||
|
||
@per_cpu_count.setter | ||
def per_cpu_count(self, value): | ||
self.irq_info['per_cpu_count'] = value | ||
|
||
@property | ||
def type(self): | ||
return self.irq_info['type'] | ||
|
||
@property | ||
def wakeup(self): | ||
return self.irq_info['wakeup'] | ||
|
||
@property | ||
def smp_affinity(self): | ||
if 'smp_affinity' in self.irq_info.keys(): | ||
return self.irq_info['smp_affinity'] | ||
return -1 | ||
|
||
@smp_affinity.setter | ||
def smp_affinity(self, affinity, verify=True): | ||
aff = str(affinity) | ||
aff_path = self.target.path.join(self.procfs_path, 'smp_affinity') | ||
self.target.write_value(aff_path, aff, verify=verify) | ||
|
||
self.update_affinities() | ||
|
||
@property | ||
def effective_affinity(self): | ||
if 'effective_affinity' in self.irq_info.keys(): | ||
return self.irq_info['effective_affinity'] | ||
return -1 | ||
|
||
def to_dict(self): | ||
return self.irq_info.copy() | ||
|
||
@asyn.asyncf | ||
async def update_counts(self): | ||
""" | ||
Updates irq's per-CPU counts. | ||
""" | ||
counts = await self.target.read_value.asyn(self.target.path.join(self.sysfs_path, 'per_cpu_count')) | ||
self.per_cpu_count = counts.split(',') | ||
|
||
def update_counts_bulk(self, data_dict): | ||
""" | ||
Update per-CPU counts based on provided data. | ||
:params data_dict: Dictionary with raw procfs data for the interrupt. | ||
:type data_dict: dict | ||
""" | ||
self.irq_info['per_cpu_count'] = self._fix_data_dict(data_dict)['per_cpu_count'] | ||
|
||
@asyn.asyncf | ||
async def update_affinities(self): | ||
"""Read affinity masks from target.""" | ||
proc_data = await self.target.read_tree_values.asyn(self.procfs_path, depth=2, check_exit_code=False) | ||
self._fix_procfs_data(proc_data) | ||
|
||
for aff in ['smp_affinity', 'effective_affinity', 'smp_affinity_list', 'effective_affinity_list']: | ||
self.irq_info[aff] = proc_data[aff] | ||
|
||
class IrqModule(Module): | ||
name = 'irq' | ||
irq_sysfs_root = '/sys/kernel/irq/' | ||
irq_procfs_root = '/proc/irq/' | ||
|
||
@staticmethod | ||
def probe(target): | ||
if target.file_exists(IrqModule.irq_sysfs_root): | ||
if target.file_exists(IrqModule.irq_procfs_root): | ||
return True | ||
|
||
def __init__(self, target): | ||
self.logger = logging.getLogger(self.name) | ||
self.logger.debug(f'Initialized {self.name} module') | ||
|
||
self.target = target | ||
self.irqs = {} | ||
|
||
temp_dict = self._scrape_data(self.target, self.irq_sysfs_root, self.irq_procfs_root) | ||
for irq, data in temp_dict.items(): | ||
intid = int(irq) | ||
self.irqs[intid] = Irq(self.target, intid, data, self.irq_sysfs_root, self.irq_procfs_root) | ||
|
||
@asyn.asyncf | ||
@staticmethod | ||
async def _scrape_data(cls, target, sysfs_path=None, procfs_path=None): | ||
if sysfs_path and procfs_path: | ||
sysfs_dict = await target.read_tree_values.asyn(sysfs_path, depth=2, check_exit_code=False) | ||
procfs_dict = await target.read_tree_values.asyn(procfs_path, depth=2, check_exit_code=False) | ||
|
||
for irq, data in sysfs_dict.items(): | ||
if irq in procfs_dict.keys(): | ||
sysfs_dict[irq] = {**data, **procfs_dict[irq]} | ||
return sysfs_dict | ||
|
||
if sysfs_path: | ||
sysfs_dict = await target.read_tree_values.asyn(sysfs_path, depth=2, check_exit_code=False) | ||
return sysfs_dict | ||
if procfs_path: | ||
procfs_dict = await target.read_tree_values.asyn(procfs_path, depth=1, check_exit_code=False) | ||
return procfs_dict | ||
|
||
return None | ||
|
||
|
||
def get_all_irqs(self): | ||
"""Returns list of all interrupt IDs (list of integers).""" | ||
return list(self.irqs.keys()) | ||
|
||
def get_all_wakeup_irqs(self): | ||
"""Returns list of all wakeup-enabled interrupt IDs (list of integers).""" | ||
return [irq.intid for intid, irq in self.irqs.items() if irq.wakeup == 1] | ||
|
||
def get_per_cpu_counts(self, irq_list=None): | ||
""" | ||
Returns dictionary of per-CPU interrupt counts. | ||
:params irq_list: List of interrupt IDs to include in dict. | ||
:type irq_list: list(int) | ||
:returns: A dict of list(int) indexed by interrupt ID. | ||
""" | ||
if not irq_list: | ||
irq_list = self.get_all_irqs() | ||
return {intid:self.irqs[intid].per_cpu_count for intid in irq_list} | ||
|
||
@asyn.asyncf | ||
async def update_counts(self, irq_list=None): | ||
""" | ||
Updates per-CPU counts from target. | ||
:params irq_list: List of interrupt IDs to update. | ||
:type irq_list: list(int) | ||
""" | ||
if not irq_list: | ||
irq_list = self.get_all_irqs() | ||
|
||
if len(irq_list) < 3: | ||
async def update_irq_stats(irq): | ||
return await self.irqs[irq].update_counts.asyn() | ||
|
||
await self.target.async_manager.map_concurrently(update_irq_stats, irq_list) | ||
return | ||
|
||
# read_tree_values() on the target is much faster | ||
data_dict = self._scrape_data(self.target, self.irq_sysfs_root) | ||
for irq, data in data_dict.items(): | ||
intid = int(irq) | ||
self.irqs[intid].update_counts_bulk(data) |