hardware_gen: pull interrupts from DTS

This adds support for extracting interrupt numbers from DTS
to the hardware header file generator, so that the majority
of the per-platform interrupt listings can be removed.
This commit is contained in:
Simon Shields 2019-01-10 11:39:36 +11:00
parent fc4447708a
commit 8440f0339a
4 changed files with 405 additions and 4 deletions

View file

@ -22,7 +22,7 @@ Upcoming release: BREAKING
- a new constant in libsel4 that contains the first virtual address unavailable to
user level.
* Support added for Aarch64 hypervisor mode (EL2) for Nvidia TX1 and TX2. This is not verified.
* Support for generating ARM machine header files (memory regions) based on a device tree.
* Support for generating ARM machine header files (memory regions and interrupts) based on a device tree.
## Upgrade Notes
---

View file

@ -39,6 +39,8 @@ devices:
user: false # never expose to userspace, even if macro is false.
# other regions (i.e. GICH)
- user: true
interrupts:
INTERRUPT_VGIC_MAINTENANCE: 0
# Broadcom second level IRQ controller (interrupt-controller/brcm,bcm2835-armctrl-ic.txt),
# TI AM33XX/OMAP3 intc (interrupt-controller/ti,omap-intc-irq.txt)
- compatible:
@ -86,12 +88,27 @@ devices:
- executeNever: true
index: 0
kernel: EXYNOS_MCT_PPTR
interrupts:
KERNEL_TIMER_IRQ: 0
- compatible:
- samsung,exynos4210-mct
regions:
- executeNever: true
index: 0
kernel: EXYNOS_MCT_PPTR
# ARM PMU (arm/pmu.txt)
- compatible:
- arm,armv8-pmuv3
- arm,cortex-a15-pmu
- arm,cortex-a7-pmu
- arm,cortex-a9-pmu
interrupts:
KERNEL_PMU_IRQ: boot-cpu
# i.MX evtmon (no Linux binding, this one is seL4-specific.)
- compatible:
- fsl,imx31-evtmon
interrupts:
KERNEL_PMU_IRQ: 0
# Tegra SMMU (memory-controllers/nvidia,tegra30-mc.txt)
- compatible:
- nvidia,tegra124-mc
@ -100,6 +117,32 @@ devices:
index: 0
kernel: SMMU_PPTR
macro: CONFIG_ARM_SMMU
interrupts:
INTERRUPT_SMMU: 0
# ARM architected timer (timer/arm,arch_timer.txt)
- compatible:
- arm,armv7-timer
- arm,armv8-timer
interrupts:
KERNEL_TIMER_IRQ:
index:
macro: CONFIG_ARM_HYPERVISOR_SUPPORT
defined: 3
undefined: 2
# Allwinner A10 Timer (timer/allwinner,sun4i-timer.txt)
- compatible:
- allwinner,sun4i-a10-timer
regions:
- executeNever: true
index: 0
kernel: TIMER0_PPTR
interrupts:
# A20 also has the ARM architected timer,
# but we want to use the allwinner timer
# for now at least.
KERNEL_TIMER_IRQ:
index: 0
priority: 1
# ARM per-core timer-watchdog (timer/arm,twd.txt)
- compatible:
- arm,cortex-a9-twd-timer
@ -107,6 +150,8 @@ devices:
- executeNever: true
index: 0
kernel: ARM_MP_PRIV_TIMER_PPTR
interrupts:
KERNEL_TIMER_IRQ: 0
# i.MX EPIT (no Linux binding, this is seL4-specific.)
- compatible:
- fsl,imx31-epit
@ -114,6 +159,8 @@ devices:
- executeNever: true
index: 0
kernel: EPIT_PPTR
interrupts:
KERNEL_TIMER_IRQ: 0
# QCOM Krait timer (timer/qcom,msm-timer.txt)
- compatible:
- qcom,kpss-timer
@ -121,6 +168,8 @@ devices:
- executeNever: true
index: 0
kernel: TIMER_PPTR
interrupts:
KERNEL_TIMER_IRQ: 0
# TI AM335x/OMAP3430 timer
- compatible:
- ti,am335x-timer
@ -129,6 +178,8 @@ devices:
- executeNever: true
index: 0
kernel: TIMER_PPTR
interrupts:
KERNEL_TIMER_IRQ: 0
chosen: seL4,timer
# TI prcm (arm/omap/prcm.txt)
- compatible:

View file

@ -41,6 +41,84 @@ def make_number(cells, array):
ret |= array.pop(0)
return ret
def parse_arm_gic_irq(self, child, by_phandle, data):
# 3 cells:
# first cell is 1 if PPI, 0 if SPI
# second cell: PPI or SPI number
# third cell: interrupt trigger flags, ignored by us
is_spi = data.pop(0) == 0
number = data.pop(0)
flags = data.pop(0)
number += 16 # SGI takes 0-15
if is_spi:
number += 16 # PPI is 16-31
return number
def parse_raw_irq(self, child, by_phandle, data):
# first cells is interrupt number, may have flags or other data
# we don't need afterwards.
num = data.pop(0)
cells = self.get_interrupt_cells(by_phandle)
while cells > 1:
data.pop(0)
cells -= 1
return num
def parse_bcm2836_armctrl_irq(self, child, by_phandle, data):
# two cells, first is bank, second is number within bank
bank = data.pop(0)
num = data.pop(0)
bank += 1
return (32 * bank) + num
def parse_passthrough_irq(self, child, by_phandle, data):
# IRQ just passes through to this node's interrupt-parent.
parent = self.get_interrupt_parent(by_phandle)
return parent.parse_interrupt(child, by_phandle, data)
interrupt_controllers = {
'arm,cortex-a15-gic': parse_arm_gic_irq,
'arm,cortex-a7-gic': parse_arm_gic_irq,
'arm,cortex-a9-gic': parse_arm_gic_irq,
'arm,gic-400': parse_arm_gic_irq,
'brcm,bcm2836-l1-intc': parse_raw_irq,
'brcm,bcm2836-armctrl-ic': parse_bcm2836_armctrl_irq, # maybe not actually needed?
'fsl,avic': parse_raw_irq,
'fsl,imx6q-gpc': parse_passthrough_irq,
'fsl,imx7d-gpc': parse_passthrough_irq,
'nvidia,tegra124-ictlr': parse_passthrough_irq,
'qcom,msm-qgic2': parse_arm_gic_irq,
'ti,am33xx-intc': parse_raw_irq,
'ti,omap3-intc': parse_raw_irq,
}
class Interrupt:
def __init__(self, num, name, macro=None, prio=0):
self.num = num
self.name = name
self.macro = macro
self.priority = prio
self.desc = ""
def get_macro_name(self):
return self.macro[1:] if self.macro[0] == '!' else self.macro
def get_macro_conditional(self):
return 'ifdef' if self.macro[0] != '!' else 'ifndef'
def __hash__(self):
return hash(self.num)
def __str__(self):
return 'Interrupt(num=%d,name=%s,macro=%s)'%(self.num, self.name, self.macro)
def __repr__(self):
return str(self)
class Region:
def __init__(self, start, size, name, index=0):
self.start = start
@ -127,6 +205,125 @@ class Device:
return self.props['#size-cells'].words[0]
return 1
def get_interrupt_cells(self, by_phandle):
if '#interrupt-cells' in self.props:
return self.props['#interrupt-cells'].words[0]
parent = self.get_interrupt_parent(by_phandle)
return parent.get_interrupt_cells(by_phandle)
def get_interrupt_parent(self, by_phandle):
if 'interrupt-parent' not in self.props:
return self.parent.get_interrupt_parent(by_phandle)
phandle = self.props['interrupt-parent'].words[0]
return by_phandle[phandle]
def get_interrupts(self, config, by_phandle):
irqs = []
if 'compatible' not in self.props:
return set()
if 'interrupts' in self.props:
interrupt_parent = self.get_interrupt_parent(by_phandle)
data = list(self.props['interrupts'].words)
while len(data) > 0:
irqs.append(interrupt_parent.parse_interrupt(self, by_phandle, data))
elif 'interrupts-extended' in self.props:
data = list(self.props['interrupts-extended'].words)
while len(data) > 0:
phandle = data.pop(0)
interrupt_parent = by_phandle[phandle]
irqs.append(interrupt_parent.parse_interrupt(self, by_phandle, data))
if len(irqs) > 0:
affinities = []
if 'interrupt-affinity' in self.props:
affinities = list(self.props['interrupt-affinity'].words)
return set(config.get_irqs(self, irqs, affinities, by_phandle))
return set()
def _recursive_get_addr_cells(self):
if '#address-cells' in self:
return self['#address-cells'].words[0]
return self.parent._recursive_get_addr_cells()
def _parse_interrupt_nexus(self, child, by_phandle, data):
nexus_data = list(self.props['interrupt-map'].words)
# interrupt-map is a list of the following:
# <<child unit address> <child interrupt specifier> <interrupt parent>
# <parent unit address> <parent interrupt specifier>>
# "child unit address" seems to be special: the docs say one thing, but
# Linux implements something else. We go with the Linux implementation here:
# child unit address size is specified by '#address-cells' in the nexus node,
# or the first '#address-cells' specified in a parent node. (note: not interrupt parent)
chaddr_cells = self._recursive_get_addr_cells()
chint_cells = self.props['#interrupt-cells'].words[0]
# we only care about the first address, like Linux.
addr = make_number(chaddr_cells, list(child['reg'].words)) if 'reg' in child else 0
addr_mask = (1 << (32 * chaddr_cells)) - 1
intspec = make_number(chint_cells, data)
int_mask = (1 << (32 * chint_cells)) - 1
if 'interrupt-map-mask' in self.props:
masks = list(self.props['interrupt-map-mask'].words)
addr_mask = make_number(chaddr_cells, masks)
int_mask = make_number(chint_cells, masks)
addr &= addr_mask
intspec &= int_mask
# next: figure out which interrupt it is that we're interested in.
ok = False
while len(nexus_data) > 0:
my_addr = make_number(chaddr_cells, nexus_data) & addr_mask
my_intspec = make_number(chint_cells, nexus_data) & int_mask
node = by_phandle[nexus_data.pop(0)]
# found it!
if my_addr == addr and my_intspec == intspec:
ok = True
break
# not yet: keep going. get address-cells and interrupt-cells so we know how much to skip
cells = node['#address-cells'].words[0] if '#address-cells' in node else 0
cells += node['#interrupt-cells'].words[0]
if cells > len(nexus_data):
logging.warning("malformed device tree!")
return -1
# slice off specifier for this entry
nexus_data = nexus_data[cells:]
if not ok:
logging.warning("could not find match in interrupt nexus :(")
return -1
# okay. now we figure out what the number is
return node.parse_interrupt(child, by_phandle, nexus_data)
def parse_interrupt(self, child, by_phandle, data):
if 'interrupt-map' in self.props:
# this is an "interrupt nexus". Actual interrupt numbers are determined elsewhere,
# we just need to decode the interrupt info and pass it off.
return self._parse_interrupt_nexus(child, by_phandle, data)
if 'compatible' not in self.props:
if '#interrupt-cells' in self.props:
for i in range(self.props['#interrupt-cells'].words[0]):
data.pop(0)
else:
data.pop(0)
return -1
for compat in self.props['compatible'].strings:
if compat in interrupt_controllers:
return interrupt_controllers[compat](self, child, by_phandle, data)
logging.info('Unknown interrupt controller: "%s"'%'", "'.join(self.props['compatible'].strings))
if '#interrupt-cells' in self.props:
for i in range(self.props['#interrupt-cells'].words[0]):
data.pop(0)
else:
data.pop(0)
return -1
def translate_child_address(self, addr):
if self.parent is None:
# the root node does not need to do any translation of addresses
@ -288,6 +485,57 @@ class Config:
return (user, kernel)
return (set(regions), set())
def get_irqs(self, device, irqs, affinities, by_phandle):
ret = set()
compat = device['compatible']
for compatible in compat.strings:
if compatible not in self.devices:
continue
for rule in self.devices[compatible]:
if 'interrupts' not in rule or not self._is_chosen(device, rule, by_phandle):
continue
for irq in rule['interrupts']:
irq_rule = rule['interrupts'][irq]
if type(irq_rule) is dict:
idx = irq_rule['index']
macro = irq_rule.get('macro', None)
prio = irq_rule.get('priority', 0)
else:
idx = irq_rule
macro = None
prio = 0
if idx == 'boot-cpu':
if self.chosen is not None and 'seL4,boot-cpu' in self.chosen:
bootcpu = self.chosen['seL4,boot-cpu'].words[0]
if bootcpu in affinities:
num = affinities.index(bootcpu)
else:
# skip this rule - no matching IRQ.
continue
else:
num = 0
elif type(idx) is dict:
res = Interrupt(irqs[idx['defined']], irq, idx['macro'], prio)
res.desc = "%s '%s' IRQ %d"%(device.name, compatible, idx['defined'])
ret.add(res)
res = Interrupt(irqs[idx['undefined']], irq, '!' + idx['macro'], prio)
res.desc = "%s '%s' IRQ %d"%(device.name, compatible, idx['undefined'])
ret.add(res)
continue
elif type(irq_rule) is int:
num = irq_rule
else:
num = idx
if num < len(irqs):
irq = Interrupt(irqs[num], irq, macro if macro != 'all' else None, prio)
irq.desc = "%s '%s' IRQ %d"%(device.name, compatible, num)
ret.add(irq)
if len(ret) > 0:
break
return ret
def is_compatible(node, compatibles):
""" returns True if node matches a compatible in the given list """
try:
@ -336,6 +584,7 @@ def find_devices(dtb, cfg):
prop = child[idx]
by_phandle[prop.words[0]] = nodes[child]
# the root node is not actually a device, so remove it.
del devices[root]
return {d.name: d for d in devices.values()}, by_phandle
@ -427,10 +676,25 @@ static const p_region_t BOOT_RODATA dev_p_regs[] = {
{%- endfor %}
};
/* INTERRUPTS */
{%- for irq in interrupts %}
{%- if irq.macro %}
#{{ irq.get_macro_conditional() }} {{ irq.get_macro_name() }}
{%- endif %}
{%- if irq.num == -1 %}
/*#define {{ irq.name }} {{ irq.num }} {{ irq.desc }} */
{%- else %}
#define {{ irq.name }} {{ irq.num }} /* {{ irq.desc }} */
{%- endif %}
{%- if irq.macro %}
#endif
{%- endif %}
{%- endfor %}
#endif /* __PLAT_DEVICES_GEN_H */
"""
def output_regions(args, devices, memory, kernel, fp):
def output_regions(args, devices, memory, kernel, irqs, fp):
""" generate the device list for the C header file """
memory = sorted(memory, key=lambda a: a.start)
kernel = sorted(kernel, key=lambda a: a.start)
@ -445,7 +709,7 @@ def output_regions(args, devices, memory, kernel, fp):
memory[0].start = paddr
template = Environment(loader=BaseLoader, trim_blocks=False, lstrip_blocks=False).from_string(HEADER_TEMPLATE)
data = template.render({'args': args, 'devices': sorted(devices, key=lambda a: a.start), 'sorted': sorted,
'memory': memory, 'physBase': paddr, 'kernel': kernel})
'memory': memory, 'physBase': paddr, 'kernel': kernel, 'interrupts': irqs})
fp.write(data)
def main(args):
@ -458,7 +722,9 @@ def main(args):
memory, user, kernel = set(), set(), set()
fdt = pyfdt.pyfdt.FdtBlobParse(args.dtb).to_fdt()
devices, by_phandle = find_devices(fdt, cfg)
kernel_irqs = set()
for d in devices.values():
kernel_irqs.update(d.get_interrupts(cfg, by_phandle))
if d.is_memory():
m, _ = d.regions(cfg, by_phandle) # second set is always empty for memory
res = set()
@ -470,9 +736,26 @@ def main(args):
user.update(u)
kernel.update(k)
irq_dict = {}
for el in kernel_irqs:
if el.name in irq_dict:
irq_dict[el.name].append(el)
else:
irq_dict[el.name] = [el]
kernel_irqs = []
for el in irq_dict:
irq_dict[el].sort(key=lambda a: a.priority, reverse=True)
max_prio = irq_dict[el][0].priority
for irq in irq_dict[el]:
if irq.priority != max_prio:
break
kernel_irqs.append(irq)
user = fixup_device_regions(user, 1 << args.page_bits, merge=True)
kernel = fixup_device_regions(kernel, 1 << args.page_bits)
output_regions(args, user, memory, kernel, args.output)
output_regions(args, user, memory, kernel, kernel_irqs, args.output)
if __name__ == '__main__':
parser = argparse.ArgumentParser()

View file

@ -44,6 +44,13 @@ definitions:
items:
$ref: '#/definitions/region'
uniqueItems: true
interrupts:
description: Interrupts this device has that the kernel might use.
type: object
additionalProperties: false
patternProperties:
'^[A-Za-z_][A-Za-z0-9_]*$':
$ref: '#/definitions/interrupt'
chosen:
description: >
Apply this rule only to the device referenced by the correspondingly named
@ -101,7 +108,67 @@ definitions:
dependencies:
kernel: [executeNever, index]
executeNever: [kernel]
interrupt:
oneOf:
- type: object
additionalProperties: false
properties:
macro:
description: only set interrupt if this macro is defined
$ref: '#/definitions/macro'
index:
description: index of interrupt in device's interrupts array
$ref: '#/definitions/interrupt_index'
priority:
description: if multiple conflicting IRQs are present, the IRQ with the highest priority will be selected.
type: integer
required: [index]
- type: integer
description: index of interrupt in device's interrupts array
minimum: 0
- $ref: '#/definitions/boot-cpu'
interrupt_index:
oneOf:
- type: object
additionalProperties: false
properties:
macro:
description: macro to use when picking index
$ref: '#/definitions/macro'
defined:
description: if macro is defined, use this index in the interrupts array
type: integer
minimum: 0
undefined:
description: if macro is undefined, use this index in the interrupts array.
type: integer
minimum: 0
required: [macro, defined, undefined]
- type: integer
description: index of interrupt in device's interrupts array
minimum: 0
- $ref: '#/definitions/boot-cpu'
macro:
type: string
pattern: '^[A-Za-z_][A-Za-z0-9_]*$'
minLength: 1
boot-cpu:
type: string # TODO: why does 'const' not work here?
description: >
Use interrupt associated with the seL4,boot-cpu set in the chosen node.
For instance, a chosen node like
chosen {
seL4,boot-cpu = <&cpu2>;
}
and a device like
device {
interrupts = <0x1 0x2 0x3 0x4>;
interrupt-affinity = <&cpu0 &cpu1 &cpu2 &cpu3>;
}
would use interrupt 0x3 if the boot-cpu option was used as the index.
pattern: '^boot-cpu$'