Skip to content

Commit

Permalink
devlib/module: Add irq module for stats and affinity manipulation
Browse files Browse the repository at this point in the history
FEATURE

Add module to collect irq configuration, stats, and affinities
from target. Enables setting of affinity.
  • Loading branch information
msrasmussen committed Jan 29, 2024
1 parent c60737c commit e89bb70
Showing 1 changed file with 243 additions and 0 deletions.
243 changes: 243 additions & 0 deletions devlib/module/irq.py
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)

0 comments on commit e89bb70

Please sign in to comment.