seL4/tools/hardware/utils/rule.py
Curtis Millar ab3d8c44cb
riscv: Map devices with large pages on 32 & 64-bit
For 64-bit, this adds a 2nd-level page table for mapping devices using
2MiB frames instead of 1GiB frames.

The boot mapping and hardware header generator have also been fixed to
correctly report the number of large frames needed for devices rather
than only reporting the first. The frame size is also specified
correctly (rather than assuming mapping with 4KiB frames).

This likely fixes an issue whereby only the first 4KiB frame of a device
was reserved but the remaining region of that kernel device could be
mapped at user level.

Signed-off-by: Curtis Millar <curtis.millar@data61.csiro.au>
2020-12-03 10:31:16 +11:00

246 lines
9 KiB
Python

#
# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
#
# SPDX-License-Identifier: GPL-2.0-only
#
from collections import defaultdict
from functools import lru_cache
from typing import Dict, List
import logging
from hardware.config import Config
from hardware.device import WrappedNode
from hardware.fdt import FdtParser
from hardware.memory import Region
def get_macro_str(macro: str) -> str:
''' Helper function that returns the appropriate C preprocessor line for a given macro '''
if macro is None:
return ''
if macro[0] == '!':
return '#ifndef ' + macro[1:]
return '#ifdef ' + macro
def get_endif(macro: str) -> str:
''' Helper function that returns the appropriate endif line for a given macro '''
if macro is None:
return ''
return '#endif /* {} */'.format(macro)
class KernelRegionGroup:
''' wraps a contiguous region of memory that is mapped into the kernel. '''
def __init__(self, region: Region, kernel_name: str, page_bits: int, max_size: int, condition_macro: str = None, user_ok: bool = False):
self.macro = condition_macro
self.desc = region.owner.path
self.kernel_offset = -1
self.page_bits = page_bits
self.labels = {} # dict of label => offset within region.
self.user_ok = user_ok
region.size = min(max_size, region.size)
aligned = region.align_size(page_bits)
self.size = aligned.size
self.base = aligned.base
self.regions = aligned.make_chunks(1 << page_bits)
self.labels[kernel_name] = region.base - aligned.base
def has_macro(self):
''' True if this group has a macro '''
return self.macro is not None
def take_labels(self, other_group: 'KernelRegionGroup'):
''' Take another group's labels and add them to our own '''
if self != other_group:
raise ValueError('need to have equal size and base to take labels')
for (k, v) in other_group.labels.items():
self.labels[k] = v
self.desc += ', ' + other_group.desc
def get_macro(self):
''' Get the #ifdef line for this region group '''
return get_macro_str(self.macro)
def get_endif(self):
''' Get the #endif line for this region group '''
return get_endif(self.macro)
def set_kernel_offset(self, offset):
''' Set the base offset that this region is mapped at in the kernel.
Returns the next free address in the kernel (i.e. base offset + region size) '''
self.kernel_offset = offset
return offset + self.size
def get_labelled_addresses(self):
''' Get a dict of address -> label for the kernel '''
ret = {}
for (k, v) in self.labels.items():
ret[v + self.kernel_offset] = k
return ret
def get_map_offset(self, reg):
''' Get the offset that the given region is mapped at. '''
index = self.regions.index(reg)
return self.kernel_offset + (index * (1 << self.page_bits))
def get_desc(self):
''' Get this region group's description '''
return self.desc
def __repr__(self):
return 'KernelRegion(reg={},labels={})'.format(self.regions, self.labels)
def __eq__(self, other):
return other.base == self.base and other.size == self.size
class KernelInterrupt:
''' Represents an interrupt that is used by the kernel. '''
def __init__(self, label: str, irq: int, prio: int = 0, sel_macro: str = None, false_irq: int = -1, enable_macro: str = None, desc: str = None):
self.label = label
self.irq = irq
self.prio = prio
self.sel_macro = sel_macro
self.false_irq = false_irq
self.enable_macro = enable_macro
self.desc = desc
def get_enable_macro_str(self):
''' Get the enable macro #ifdef line '''
return get_macro_str(self.enable_macro)
def has_enable(self):
''' True if this interrupt has an enable macro '''
return self.enable_macro is not None
def get_enable_endif(self):
''' Get the enable macro #endif line '''
return get_endif(self.enable_macro)
def get_sel_macro_str(self):
''' Get the select macro #ifdef line '''
return get_macro_str(self.sel_macro)
def has_sel(self):
''' True if this interrupt has a select macro '''
return self.sel_macro is not None
def get_sel_endif(self):
''' Get the select macro #endif line '''
return get_endif(self.sel_macro)
def __repr__(self):
return 'KernelInterrupt(label={},irq={},sel_macro={},false_irq={})'.format(self.label, self.irq, self.sel_macro, self.false_irq)
class DeviceRule:
''' Represents a single rule in hardware.yml '''
def __init__(self, rule: dict, config: Config):
self.rule = rule
self.regions: Dict[int, Dict] = {}
self.interrupts = rule.get('interrupts', {})
self.config = config
for reg in rule.get('regions', []):
self.regions[reg['index']] = reg
@lru_cache()
def get_regions(self, node: WrappedNode) -> List[KernelRegionGroup]:
''' Returns a list of KernelRegionGroups that this rule specifies should be mapped into the kernel for this device. '''
ret = []
regions = node.get_regions()
for (i, rule) in self.regions.items():
if i >= len(regions):
# XXX: skip this rule silently
continue
reg = regions[i]
kernel_name = rule['kernel']
user = rule.get('user', False)
macro = rule.get('macro', None)
max_size = 1 << self.config.get_device_page_bits()
if 'kernel_size' in rule:
max_size = rule['kernel_size']
elif max_size < reg.size:
logging.warning(
"Only mapping {}/{} bytes from node {}, region {}. Set kernel_size in YAML to silence.".format(max_size, reg.size, node.path, i))
ret.append(KernelRegionGroup(reg, kernel_name,
self.config.get_device_page_bits(), max_size, macro, user))
return ret
@lru_cache()
def get_interrupts(self, tree: FdtParser, node: WrappedNode) -> List[KernelInterrupt]:
''' Returns a list of KernelInterrupts that this rule says are used by the kernel for this device. '''
ret = []
interrupts = node.get_interrupts(tree)
for name, rule in self.interrupts.items():
irq_desc = '{} generated from {}'.format(name, node.path)
if type(rule) == dict:
en_macro = rule.get('enable_macro', None)
if rule['index'] >= len(interrupts):
# XXX: skip this rule silently.
continue
defaultIrq = interrupts[rule['index']]
sel_macro = rule.get('sel_macro', None)
falseIrq = interrupts[rule['undef_index']] if 'undef_index' in rule else -1
prio = rule.get('priority', 0)
irq = KernelInterrupt(name, defaultIrq, prio, sel_macro,
falseIrq, en_macro, desc=irq_desc)
elif type(rule) == int:
if rule >= len(interrupts):
# XXX: skip this rule silently.
continue
irq = KernelInterrupt(name, interrupts[rule], desc=irq_desc)
else: # rule == 'boot-cpu'
affinities = node.get_interrupt_affinities()
boot_cpu = tree.get_boot_cpu()
idx = affinities.index(boot_cpu)
irq = KernelInterrupt(name, interrupts[idx])
ret.append(irq)
return ret
class HardwareYaml:
''' Represents the hardware configuration file '''
def __init__(self, yaml: dict, config: Config):
self.rules = {}
for dev in yaml['devices']:
rule = DeviceRule(dev, config)
for compat in dev['compatible']:
self.rules[compat] = rule
def get_rule(self, device: WrappedNode) -> DeviceRule:
''' Returns the matching DeviceRule for this device. '''
if not device.has_prop('compatible'):
raise ValueError(
'Not sure what to do with node {} with no compatible!'.format(device.path))
for compat in device.get_prop('compatible').strings:
if compat in self.rules:
return self.rules[compat]
raise ValueError('Failed to match compatibles "{}" for node {}!'.format(
', '.join(device.get_prop('compatible').strings), device.path))
def get_matched_compatible(self, device: WrappedNode) -> str:
''' Returns the best matching compatible string for this device '''
if not device.has_prop('compatible'):
raise ValueError(
'Not sure what to do with node {} with no compatible!'.format(device.path))
for compat in device.get_prop('compatible').strings:
if compat in self.rules:
return compat
return None