arm: generate memory region tables from dts
This change adds infrastructure to automatically generate the physBase macro, the avail_p_regs array, and the dev_p_regs array based on a device tree. Platforms can opt-in to using this by adding DTS files to the KernelDTSList variable. The Python script uses the hardware.yml file to determine which devices in the device tree are of interest to the kernel and should be hidden from userspace and instead mapped into the kernel. Note that currently the kernel mappings are not (yet) generated, however most of the infrastructure needed to make that happen is present.
This commit is contained in:
parent
463c1cc211
commit
0ac0792339
5 changed files with 813 additions and 0 deletions
1
CHANGES
1
CHANGES
|
|
@ -22,6 +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.
|
||||
|
||||
## Upgrade Notes
|
||||
---
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ set(PYTHON "python" CACHE INTERNAL "")
|
|||
RequireTool(CPP_GEN_PATH cpp_gen.sh)
|
||||
RequireTool(CIRCULAR_INCLUDES circular_includes.py)
|
||||
RequireTool(BF_GEN_PATH bitfield_gen.py)
|
||||
RequireTool(HARDWARE_GEN_PATH hardware_gen.py)
|
||||
RequireTool(INVOCATION_ID_GEN_PATH invocation_header_gen.py)
|
||||
RequireTool(SYSCALL_ID_GEN_PATH syscall_header_gen.py)
|
||||
RequireTool(XMLLINT_PATH xmllint.sh)
|
||||
|
|
@ -275,6 +276,48 @@ add_custom_target(xml_headers_target DEPENDS ${xml_headers})
|
|||
add_dependencies(kernel_headers xml_headers_target)
|
||||
include_directories("${CMAKE_CURRENT_BINARY_DIR}/gen_headers")
|
||||
|
||||
if (DEFINED KernelDTSList)
|
||||
find_program(DTC_TOOL dtc)
|
||||
if ("${DTC_TOOL}" STREQUAL "DTC_TOOL-NOTFOUND")
|
||||
message(FATAL_ERROR "Cannot find 'dtc' program.")
|
||||
endif()
|
||||
|
||||
foreach(entry ${KernelDTSList})
|
||||
get_absolute_source_or_binary(dts_tmp ${entry})
|
||||
list(APPEND dts_list ${dts_tmp})
|
||||
endforeach()
|
||||
|
||||
# Generate final DTS based on Linux DTS + seL4 overlay[s]
|
||||
set(KernelDTSIntermediate "kernel.dts")
|
||||
add_custom_command(OUTPUT ${KernelDTSIntermediate}
|
||||
COMMAND cat ${dts_list} > "${KernelDTSIntermediate}"
|
||||
COMMENT "Generate kernel DTS"
|
||||
DEPENDS ${dts_list})
|
||||
|
||||
# Compile DTS to DTB
|
||||
set(KernelDTBPath "kernel.dtb")
|
||||
add_custom_command(OUTPUT ${KernelDTBPath}
|
||||
COMMAND ${DTC_TOOL} -q -I dts -O dtb -o ${KernelDTBPath} ${KernelDTSIntermediate}
|
||||
DEPENDS ${KernelDTSIntermediate})
|
||||
|
||||
# Generate devices_gen header based on DTB
|
||||
set(device_dest "gen_headers/plat/machine/devices_gen.h")
|
||||
set(config_file "${CMAKE_CURRENT_SOURCE_DIR}/tools/hardware.yml")
|
||||
set(config_schema "${CMAKE_CURRENT_SOURCE_DIR}/tools/hardware_schema.yml")
|
||||
add_custom_command(OUTPUT ${device_dest}
|
||||
COMMAND ${PYTHON} "${HARDWARE_GEN_PATH}" --dtb "${KernelDTBPath}" --output "${device_dest}" --config "${config_file}" --schema "${config_schema}"
|
||||
DEPENDS "${HARDWARE_GEN_PATH}" "${KernelDTBPath}" "${config_file}" "${config_schema}"
|
||||
COMMENT "Generate hardware description"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# Construct target for the generated hardware header
|
||||
add_custom_target(hardware_gen_target DEPENDS "${device_dest}")
|
||||
# Add the hardware header to all the kernel headers
|
||||
add_dependencies(kernel_headers hardware_gen_target)
|
||||
list(APPEND gen_files_list "${device_dest}")
|
||||
endif()
|
||||
|
||||
#######################
|
||||
# Prune list generation
|
||||
#######################
|
||||
|
|
|
|||
174
tools/hardware.yml
Normal file
174
tools/hardware.yml
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
#
|
||||
# Copyright 2018, Data61
|
||||
# Commonwealth Scientific and Industrial Research Organisation (CSIRO)
|
||||
# ABN 41 687 119 230.
|
||||
#
|
||||
# This software may be distributed and modified according to the terms of
|
||||
# the GNU General Public License version 2. Note that NO WARRANTY is provided.
|
||||
# See "LICENSE_GPLv2.txt" for details.
|
||||
#
|
||||
# @TAG(DATA61_GPL)
|
||||
#
|
||||
# Documentation for bindings is relative to Documentation/devicetree/bindings in Linux,
|
||||
# unless otherwise noted.
|
||||
buses:
|
||||
- simple-bus
|
||||
- qcom,gsbi-v1.0.0
|
||||
devices:
|
||||
# ARM GIC (interrupt-controller/arm,gic.txt)
|
||||
- compatible:
|
||||
- arm,cortex-a15-gic
|
||||
- arm,cortex-a7-gic
|
||||
- arm,cortex-a9-gic
|
||||
- arm,gic-400
|
||||
- qcom,msm-qgic2
|
||||
regions:
|
||||
# distributor
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: GIC_PL390_DISTRIBUTOR_PPTR
|
||||
# controller
|
||||
- executeNever: true
|
||||
index: 1
|
||||
kernel: GIC_PL390_CONTROLLER_PPTR
|
||||
# GICV
|
||||
- executeNever: false
|
||||
index: 2
|
||||
kernel: GIC_PL400_VCPUCTRL_PPTR
|
||||
macro: CONFIG_ARM_HYPERVISOR_SUPPORT
|
||||
user: false # never expose to userspace, even if macro is false.
|
||||
# other regions (i.e. GICH)
|
||||
- user: true
|
||||
# Broadcom second level IRQ controller (interrupt-controller/brcm,bcm2835-armctrl-ic.txt),
|
||||
# TI AM33XX/OMAP3 intc (interrupt-controller/ti,omap-intc-irq.txt)
|
||||
- compatible:
|
||||
- brcm,bcm2836-armctrl-ic
|
||||
- ti,am33xx-intc
|
||||
- ti,omap3-intc
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: INTC_PPTR
|
||||
# Broadcom top level IRQ controller (interrupt-controller/brcm,bcm2836-l1-intc.txt)
|
||||
- compatible:
|
||||
- brcm,bcm2836-l1-intc
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: ARM_LOCAL_PPTR
|
||||
# i.MX AVIC (no Linux docs, used in arch/arm/mach-imx/avic.c)
|
||||
- compatible:
|
||||
- fsl,imx31-avic
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: AVIC_PPTR
|
||||
# ARM PL310 L2 cache controller (arm/l2c2x0.txt)
|
||||
- compatible:
|
||||
- arm,pl310-cache
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: L2CC_L2C310_PPTR
|
||||
# i.MX31 L2CC (seL4 only)
|
||||
- compatible:
|
||||
- fsl,imx31-l2cc
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: L2CC_PPTR
|
||||
# Exynos multi core timer (timer/samsung,exynos4210-mct.txt)
|
||||
# Exynos4412 MCT is separate as we use it for the timer IRQ.
|
||||
# Other Exynos platforms use the ARM architecture timer.
|
||||
- compatible:
|
||||
- samsung,exynos4412-mct
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: EXYNOS_MCT_PPTR
|
||||
- compatible:
|
||||
- samsung,exynos4210-mct
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: EXYNOS_MCT_PPTR
|
||||
# Tegra SMMU (memory-controllers/nvidia,tegra30-mc.txt)
|
||||
- compatible:
|
||||
- nvidia,tegra124-mc
|
||||
regions:
|
||||
- executeNever: false
|
||||
index: 0
|
||||
kernel: SMMU_PPTR
|
||||
macro: CONFIG_ARM_SMMU
|
||||
# ARM per-core timer-watchdog (timer/arm,twd.txt)
|
||||
- compatible:
|
||||
- arm,cortex-a9-twd-timer
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: ARM_MP_PRIV_TIMER_PPTR
|
||||
# i.MX EPIT (no Linux binding, this is seL4-specific.)
|
||||
- compatible:
|
||||
- fsl,imx31-epit
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: EPIT_PPTR
|
||||
# QCOM Krait timer (timer/qcom,msm-timer.txt)
|
||||
- compatible:
|
||||
- qcom,kpss-timer
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: TIMER_PPTR
|
||||
# TI AM335x/OMAP3430 timer
|
||||
- compatible:
|
||||
- ti,am335x-timer
|
||||
- ti,omap3430-timer
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: TIMER_PPTR
|
||||
chosen: seL4,timer
|
||||
# TI prcm (arm/omap/prcm.txt)
|
||||
- compatible:
|
||||
- ti,omap4-cm
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: CMPER_PPTR
|
||||
user: true
|
||||
# TI watchdog
|
||||
- compatible:
|
||||
- ti,omap3-wdt
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: WDT1_PPTR
|
||||
user: true
|
||||
# various serial consoles (`grep <compatible> serial/*`)
|
||||
- compatible:
|
||||
- arm,pl011
|
||||
- brcm,bcm2835-aux-uart
|
||||
- fsl,imx31-uart
|
||||
- fsl,imx6q-uart
|
||||
- nvidia,tegra124-hsuart
|
||||
- nvidia,tegra20-uart
|
||||
- qcom,msm-uartdm
|
||||
- samsung,exynos4210-uart
|
||||
- snps,dw-apb-uart
|
||||
- ti,am3352-uart
|
||||
- ti,omap3-uart
|
||||
- xlnx,xuartps
|
||||
chosen: stdout-path
|
||||
regions:
|
||||
- executeNever: true
|
||||
index: 0
|
||||
kernel: UART_PPTR
|
||||
macro: CONFIG_PRINTING
|
||||
user: true
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
488
tools/hardware_gen.py
Executable file
488
tools/hardware_gen.py
Executable file
|
|
@ -0,0 +1,488 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2018, Data61
|
||||
# Commonwealth Scientific and Industrial Research Organisation (CSIRO)
|
||||
# ABN 41 687 119 230.
|
||||
#
|
||||
# This software may be distributed and modified according to the terms of
|
||||
# the GNU General Public License version 2. Note that NO WARRANTY is provided.
|
||||
# See "LICENSE_GPLv2.txt" for details.
|
||||
#
|
||||
# @TAG(DATA61_GPL)
|
||||
#
|
||||
|
||||
from __future__ import print_function, division
|
||||
import sys
|
||||
import os.path
|
||||
import argparse
|
||||
import logging
|
||||
import pyfdt.pyfdt
|
||||
import yaml
|
||||
from jinja2 import Environment, BaseLoader
|
||||
|
||||
try:
|
||||
from jsonschema import validate
|
||||
except ImportError:
|
||||
print("Skipping hardware YAML validation, `pip install jsonschema` to validate")
|
||||
def validate(*args, **kwargs):
|
||||
pass
|
||||
|
||||
def align_down(addr, boundary):
|
||||
return addr & ~(boundary - 1)
|
||||
|
||||
def align_up(addr, boundary):
|
||||
return (addr + (boundary - 1)) & ~(boundary - 1)
|
||||
|
||||
def make_number(cells, array):
|
||||
""" make a number with "cells" cells from the given list """
|
||||
ret = 0
|
||||
for i in range(cells):
|
||||
ret = (ret << 32)
|
||||
ret |= array.pop(0)
|
||||
return ret
|
||||
|
||||
class Region:
|
||||
def __init__(self, start, size, name, index=0):
|
||||
self.start = start
|
||||
self.size = size
|
||||
|
||||
if isinstance(name, Region):
|
||||
other = name
|
||||
self.names = set(other.names)
|
||||
self.index = other.index
|
||||
self.kaddr = other.kaddr
|
||||
self.kernel_var = other.kernel_var
|
||||
self.kernel_macro = other.kernel_macro
|
||||
self.user_macro = other.kernel_macro
|
||||
else:
|
||||
self.names = set()
|
||||
self.names.add(name)
|
||||
self.index = index
|
||||
self.kaddr = 0
|
||||
self.kernel_var = ""
|
||||
self.kernel_macro = ""
|
||||
self.user_macro = False
|
||||
|
||||
def __eq__(self, other):
|
||||
if other.start == self.start and other.size == self.size:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.start, self.size))
|
||||
|
||||
def __str__(self):
|
||||
return '%s(0x%x-0x%x)'%('+'.join(sorted(self.names)), self.start, self.start + self.size - 1)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def isempty(self):
|
||||
return self.size <= 0
|
||||
|
||||
def overlaps(self, other):
|
||||
sstart = self.start
|
||||
send = self.start + self.size
|
||||
ostart = other.start
|
||||
oend = other.start + other.size
|
||||
return (sstart <= ostart and send > ostart) or \
|
||||
(ostart <= sstart and oend > sstart)
|
||||
|
||||
def remove_subregions(self, subregions):
|
||||
new = []
|
||||
for i in range(len(subregions)):
|
||||
reg = subregions[i]
|
||||
base = reg['address']
|
||||
end = reg['size'] + base
|
||||
|
||||
if self.start <= base < (self.start + self.size):
|
||||
newsize = base - self.start
|
||||
newstart = end
|
||||
if newstart < (self.start + self.size):
|
||||
newreg = Region(newstart, (self.start + self.size - newstart), self)
|
||||
new += newreg.remove_subregions(subregions[i+1:])
|
||||
self.size = newsize
|
||||
new.append(self)
|
||||
return filter(lambda a: not a.isempty(), new)
|
||||
|
||||
|
||||
class Device:
|
||||
def __init__(self, node, parent, name):
|
||||
self.props = {}
|
||||
self.node = node
|
||||
self.parent = parent
|
||||
self.name = name
|
||||
for o in node:
|
||||
if isinstance(o, pyfdt.pyfdt.FdtNop):
|
||||
continue
|
||||
self.props[o.get_name()] = o
|
||||
|
||||
def get_addr_cells(self):
|
||||
if '#address-cells' in self.props:
|
||||
return self.props['#address-cells'].words[0]
|
||||
return 2
|
||||
|
||||
def get_size_cells(self):
|
||||
if '#size-cells' in self.props:
|
||||
return self.props['#size-cells'].words[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
|
||||
return addr
|
||||
if 'ranges' not in self.props or not isinstance(self.props['ranges'], pyfdt.pyfdt.FdtPropertyWords):
|
||||
return self.parent.translate_child_address(addr)
|
||||
|
||||
# ranges is a list with the following format:
|
||||
# <child-bus-address> <parent-bus-address> <length>
|
||||
# child-bus-address is self.get_addr_cells() cells long
|
||||
# parent-bus-address is self.parent.get_addr_cells() cells long
|
||||
# length is self.get_size_cells() cells long
|
||||
child_addr_cells = self.get_addr_cells()
|
||||
parent_addr_cells = self.parent.get_addr_cells()
|
||||
size_cells = self.get_size_cells()
|
||||
|
||||
# We assume that a child's region
|
||||
# will stay within one "ranges" entry.
|
||||
data = list(self.props['ranges'].words)
|
||||
while len(data) > 0:
|
||||
cbase = make_number(child_addr_cells, data)
|
||||
pbase = make_number(parent_addr_cells, data)
|
||||
length = make_number(size_cells, data)
|
||||
|
||||
if cbase <= addr < (cbase + length):
|
||||
return self.parent.translate_child_address(addr - cbase + pbase)
|
||||
# if we get here, the device tree is probably wrong - print out a warning
|
||||
# but continue as though the address was identity-mapped.
|
||||
logging.warning("Untranslatable address 0x%x in %s, not translating"%(addr, self.name))
|
||||
return self.parent.translate_child_address(addr)
|
||||
|
||||
def is_memory(self):
|
||||
return 'device_type' in self.props and self.props['device_type'].strings[0] == 'memory'
|
||||
|
||||
def regions(self, config, by_phandle):
|
||||
addr_cells = self.parent.get_addr_cells()
|
||||
size_cells = self.parent.get_size_cells()
|
||||
|
||||
if addr_cells == 0 or size_cells == 0 or 'reg' not in self:
|
||||
return (set(), set())
|
||||
|
||||
regions = []
|
||||
prop = self['reg']
|
||||
data = list(prop.words)
|
||||
while len(data) > 0:
|
||||
base = make_number(addr_cells, data)
|
||||
length = make_number(size_cells, data)
|
||||
|
||||
regions.append(Region(self.parent.translate_child_address(base), length, self.node.get_name()))
|
||||
|
||||
if not self.is_memory() and 'compatible' in self.props:
|
||||
(user, kernel) = config.split_regions(self, regions, by_phandle)
|
||||
else:
|
||||
user = set(regions)
|
||||
kernel = set()
|
||||
|
||||
return (user, kernel)
|
||||
|
||||
def __getitem__(self, val):
|
||||
return self.props[val]
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.props
|
||||
|
||||
class Config:
|
||||
def __init__(self, blob):
|
||||
self.devices = {}
|
||||
self.blob = blob
|
||||
self.chosen = None
|
||||
self.aliases = None
|
||||
self.buses = blob['buses']
|
||||
# wrangle the json a little so it's easier
|
||||
# to figure out which rules apply to a given device
|
||||
for dev in blob['devices']:
|
||||
for compat in dev['compatible']:
|
||||
if compat in self.devices:
|
||||
self.devices[compat].append(dev)
|
||||
else:
|
||||
self.devices[compat] = [dev]
|
||||
|
||||
def set_chosen(self, chosen):
|
||||
self.chosen = chosen
|
||||
|
||||
def set_aliases(self, aliases):
|
||||
self.aliases = aliases
|
||||
|
||||
def _apply_rule(self, region, rule):
|
||||
if 'kernel' in rule and rule['kernel'] != False:
|
||||
region.kernel_var = rule['kernel']
|
||||
region.kernel_macro = rule.get('macro', None)
|
||||
region.executeNever = rule['executeNever']
|
||||
region.user_macro = (not rule.get('user', False)) and region.kernel_macro is not None
|
||||
return region
|
||||
|
||||
def _lookup_alias(self, name):
|
||||
if self.aliases is None:
|
||||
return ''
|
||||
|
||||
if name not in self.aliases:
|
||||
return ''
|
||||
|
||||
return self.aliases[name].strings[0]
|
||||
|
||||
def _is_chosen(self, device, rule, by_phandle):
|
||||
if 'chosen' not in rule:
|
||||
return True
|
||||
if self.chosen is None:
|
||||
return False
|
||||
|
||||
prop = rule['chosen']
|
||||
if prop not in self.chosen:
|
||||
return False
|
||||
|
||||
val = self.chosen[prop]
|
||||
# a "chosen" field will either have a phandle, or
|
||||
# a path or an alias, then a ":", then other data.
|
||||
# the path/alias may not contain a ":".
|
||||
if isinstance(val, pyfdt.pyfdt.FdtPropertyWords):
|
||||
return 'phandle' in device and device['phandle'].words[0] == val.words[0]
|
||||
val = val.strings[0].split(':')[0]
|
||||
|
||||
# path starts with '/'.
|
||||
if val[0] == '/':
|
||||
return device.name == val
|
||||
else:
|
||||
return device.name == self._lookup_alias(val)
|
||||
|
||||
def split_regions(self, device, regions, by_phandle):
|
||||
compat = device['compatible']
|
||||
for compatible in compat.strings:
|
||||
if compatible not in self.devices:
|
||||
continue
|
||||
|
||||
user = set()
|
||||
kernel = set()
|
||||
|
||||
default_user = True
|
||||
for rule in self.devices[compatible]:
|
||||
regs = []
|
||||
if not self._is_chosen(device, rule, by_phandle):
|
||||
continue
|
||||
if 'regions' in rule:
|
||||
for reg_rule in rule['regions']:
|
||||
if 'index' not in reg_rule:
|
||||
default_user = reg_rule['user']
|
||||
continue
|
||||
if reg_rule['index'] >= len(regions):
|
||||
continue
|
||||
reg = self._apply_rule(regions[reg_rule['index']], reg_rule)
|
||||
if reg.user_macro or ('user' in reg_rule and reg_rule['user'] == True):
|
||||
user.add(reg)
|
||||
regs.append(reg)
|
||||
|
||||
kernel.update(set(regs))
|
||||
|
||||
if default_user:
|
||||
user = user.union(set(regions).difference(kernel))
|
||||
|
||||
return (user, kernel)
|
||||
return (set(regions), set())
|
||||
|
||||
def is_compatible(node, compatibles):
|
||||
""" returns True if node matches a compatible in the given list """
|
||||
try:
|
||||
prop = node.index("compatible")
|
||||
except ValueError:
|
||||
return False
|
||||
for c in compatibles:
|
||||
if c in node[prop].strings:
|
||||
return True
|
||||
return False
|
||||
|
||||
def should_parse_regions(root, node, buses):
|
||||
""" returns True if we should parse regions found in this node. """
|
||||
parent = node.get_parent_node()
|
||||
|
||||
return parent == root or is_compatible(parent, buses)
|
||||
|
||||
def find_devices(dtb, cfg):
|
||||
devices = {}
|
||||
nodes = {}
|
||||
by_phandle = {}
|
||||
chosen = None
|
||||
aliases = None
|
||||
root = dtb.get_rootnode()
|
||||
devices[root] = Device(root, None, '/')
|
||||
nodes[root] = devices[root]
|
||||
for child in root.walk(): # walk every node in the whole tree
|
||||
name = child[0]
|
||||
child = child[1]
|
||||
if not isinstance(child, pyfdt.pyfdt.FdtNode):
|
||||
continue
|
||||
if name == '/chosen':
|
||||
cfg.set_chosen(Device(child, None, name))
|
||||
elif name == '/aliases':
|
||||
cfg.set_aliases(Device(child, None, name))
|
||||
if should_parse_regions(root, child, cfg.buses):
|
||||
devices[child] = Device(child, devices[child.get_parent_node()], name)
|
||||
nodes[child] = devices[child]
|
||||
else:
|
||||
nodes[child] = Device(child, nodes[child.get_parent_node()], name)
|
||||
|
||||
try:
|
||||
idx = child.index('phandle')
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
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
|
||||
|
||||
def fixup_device_regions(regions, pagesz, merge=False):
|
||||
""" page align all regions and check for overlapping regions """
|
||||
ret = list()
|
||||
for r in regions:
|
||||
r.start = align_down(r.start, pagesz)
|
||||
r.size = align_up(r.size, pagesz)
|
||||
# we abuse the fact that regions will be
|
||||
# "equal" if they have different names but the same range.
|
||||
if r in ret:
|
||||
ret[ret.index(r)].names.update(r.names)
|
||||
else:
|
||||
ret.append(r)
|
||||
|
||||
if merge:
|
||||
ret = sorted(ret, key=lambda a: a.start)
|
||||
i = 1
|
||||
while i < len(ret):
|
||||
if ret[i].start == ret[i-1].start + ret[i-1].size:
|
||||
ret[i-1].size += ret[i].size
|
||||
ret[i-1].names.update(ret[i].names)
|
||||
del ret[i]
|
||||
else:
|
||||
i += 1
|
||||
|
||||
|
||||
return set(ret)
|
||||
|
||||
|
||||
HEADER_TEMPLATE = """
|
||||
/*
|
||||
* Copyright 2018, Data61
|
||||
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
|
||||
* ABN 41 687 119 230.
|
||||
*
|
||||
* This software may be distributed and modified according to the terms of
|
||||
* the GNU General Public License version 2. Note that NO WARRANTY is provided.
|
||||
* See "LICENSE_GPLv2.txt" for details.
|
||||
*
|
||||
* @TAG(DATA61_GPL)
|
||||
*/
|
||||
/*
|
||||
* This file is autogenerated by kernel/tools/hardware_gen.py from
|
||||
* {{args.dtb.name}}.
|
||||
*/
|
||||
|
||||
#ifndef __PLAT_DEVICES_GEN_H
|
||||
#define __PLAT_DEVICES_GEN_H
|
||||
|
||||
#define physBase {{ "0x%x" % physBase }}
|
||||
|
||||
#if 0
|
||||
static const kernel_frame_t BOOT_RODATA kernel_devices[] = {
|
||||
{%- for reg in kernel %}
|
||||
{%- if reg.kernel_macro %}
|
||||
#ifdef {{ reg.kernel_macro }}
|
||||
{%- endif %}
|
||||
#define {{ reg.kernel_var }} {{ "0x%x" % reg.kaddr }}
|
||||
{ /* {{ ' '.join(sorted(reg.names)) }} */
|
||||
{{ "0x%x" % reg.start }},
|
||||
{{ reg.kernel_var }},
|
||||
{{ "true" if reg.executeNever else "false" }} /* armExecuteNever */
|
||||
},
|
||||
{%- if reg.kernel_macro %}
|
||||
#endif /* {{ reg.kernel_macro }} */
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
};
|
||||
#endif
|
||||
|
||||
static const p_region_t BOOT_RODATA avail_p_regs[] = {
|
||||
{%- for reg in memory %}
|
||||
{ {{ "0x%x" % reg.start }}, {{ "0x%x" % (reg.start + reg.size) }} }, /* {{ ' '.join(sorted(reg.names)) }} */
|
||||
{%- endfor %}
|
||||
};
|
||||
|
||||
static const p_region_t BOOT_RODATA dev_p_regs[] = {
|
||||
{%- for reg in devices %}
|
||||
{%- if reg.user_macro %}
|
||||
#ifndef {{ reg.kernel_macro }}
|
||||
{%- endif %}
|
||||
{ {{ "0x%x" % reg.start }}, {{ "0x%x" % (reg.start + reg.size) }} }, /* {{ ' '.join(sorted(reg.names)) }} */
|
||||
{%- if reg.user_macro %}
|
||||
#endif /* {{ reg.kernel_macro }} */
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
};
|
||||
|
||||
#endif /* __PLAT_DEVICES_GEN_H */
|
||||
"""
|
||||
|
||||
def output_regions(args, devices, memory, kernel, 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)
|
||||
addr = 0x0 # args.kernel_base
|
||||
for reg in kernel:
|
||||
reg.kaddr = addr
|
||||
addr += align_up(reg.size, 1 << args.page_bits)
|
||||
# make sure physBase is at least 16MB (supersection) aligned for the ELF loader's sake.
|
||||
# TODO: this may need to be larger for aarch64. It seems to work OK on currently supported platforms though.
|
||||
paddr = align_up(memory[0].start, 1 << args.phys_align)
|
||||
memory[0].size -= paddr - memory[0].start
|
||||
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})
|
||||
fp.write(data)
|
||||
|
||||
def main(args):
|
||||
with open(args.schema) as fp:
|
||||
schema = yaml.load(fp)
|
||||
with open(args.config) as blob:
|
||||
kernel_devices = yaml.load(blob)
|
||||
validate(kernel_devices, schema)
|
||||
cfg = Config(kernel_devices)
|
||||
memory, user, kernel = set(), set(), set()
|
||||
fdt = pyfdt.pyfdt.FdtBlobParse(args.dtb).to_fdt()
|
||||
devices, by_phandle = find_devices(fdt, cfg)
|
||||
for d in devices.values():
|
||||
if d.is_memory():
|
||||
m, _ = d.regions(cfg, by_phandle) # second set is always empty for memory
|
||||
res = set()
|
||||
for e in m:
|
||||
res.update(set(e.remove_subregions(fdt.reserve_entries)))
|
||||
memory.update(res)
|
||||
else:
|
||||
(u, k) = d.regions(cfg, by_phandle)
|
||||
user.update(u)
|
||||
kernel.update(k)
|
||||
|
||||
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)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--dtb', help='device tree blob to use for generation', required=True, type=argparse.FileType())
|
||||
parser.add_argument('--output', help='output file for generated header', required=True, type=argparse.FileType('w'))
|
||||
parser.add_argument('--page-bits', help='number of bits per page', default=12, type=int)
|
||||
parser.add_argument('--phys-align', help='alignment in bits of the base address of the kernel', default=24, type=int)
|
||||
#parser.add_argument('--kernel-base', help='first address to use for kernel device mappings', type=lambda a: int(a, 0), required=True)
|
||||
parser.add_argument('--config', help='kernel device configuration', required=True)
|
||||
parser.add_argument('--schema', help='config file schema for validation', required=True)
|
||||
|
||||
args = parser.parse_args()
|
||||
main(args)
|
||||
107
tools/hardware_schema.yml
Normal file
107
tools/hardware_schema.yml
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#
|
||||
# Copyright 2018, Data61
|
||||
# Commonwealth Scientific and Industrial Research Organisation (CSIRO)
|
||||
# ABN 41 687 119 230.
|
||||
#
|
||||
# This software may be distributed and modified according to the terms of
|
||||
# the GNU General Public License version 2. Note that NO WARRANTY is provided.
|
||||
# See "LICENSE_GPLv2.txt" for details.
|
||||
#
|
||||
# @TAG(DATA61_GPL)
|
||||
#
|
||||
---
|
||||
$schema: http://json-schema.org/draft-07/schema#
|
||||
description: Schema for seL4 hardware.yml
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
buses:
|
||||
type: array
|
||||
uniqueItems: true
|
||||
description: list of compatibles to treat as MMIO buses
|
||||
items:
|
||||
type: string
|
||||
devices:
|
||||
type: array
|
||||
uniqueItems: true
|
||||
description: top-level list of devices
|
||||
items:
|
||||
$ref: '#/definitions/device'
|
||||
required: [devices]
|
||||
definitions:
|
||||
device:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
compatible:
|
||||
description: List of compatible strings to which this rule applies
|
||||
type: array
|
||||
minItems: 1
|
||||
uniqueItems: true
|
||||
regions:
|
||||
description: Memory regions this device has that the kernel might use
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/region'
|
||||
uniqueItems: true
|
||||
chosen:
|
||||
description: >
|
||||
Apply this rule only to the device referenced by the correspondingly named
|
||||
property in the chosen node. For instance, a dts like this
|
||||
|
||||
aliases {
|
||||
serial0 = &serial_0;
|
||||
};
|
||||
|
||||
chosen {
|
||||
stdout-path = "serial0";
|
||||
};
|
||||
|
||||
serial_0: serial@13800000 {
|
||||
compatible = "samsung,exynos4210-uart";
|
||||
reg = <0x13800000 0x100>;
|
||||
/* etc */
|
||||
};
|
||||
|
||||
serial_1: serial@13810000 {
|
||||
compatible = "samsung,exynos4210-uart";
|
||||
reg = <0x13810000 0x100>;
|
||||
/* etc */
|
||||
};
|
||||
|
||||
and a device with the property chosen set to stdout-path will only match the serial@13800000
|
||||
device and not any other device with the samsung,exynos4210-uart compatible.
|
||||
type: string
|
||||
required: [compatible]
|
||||
region:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
executeNever:
|
||||
description: should this region be mapped as execute never?
|
||||
type: boolean
|
||||
index:
|
||||
description: Region index this property should apply to
|
||||
type: integer
|
||||
minimum: 0
|
||||
kernel:
|
||||
description: kernel macro used to access this region. If not present, region will not be mapped.
|
||||
$ref: '#/definitions/macro'
|
||||
macro:
|
||||
description: only map the region to the kernel if this macro is defined
|
||||
$ref: '#/definitions/macro'
|
||||
user:
|
||||
description: >
|
||||
Whether or not to make a device untyped for userspace.
|
||||
If true, will always expose this device to userspace.
|
||||
If there is a macro and the macro is undefined, and user is not present,
|
||||
the device will be made.
|
||||
If false, the device will never be made.
|
||||
type: boolean
|
||||
dependencies:
|
||||
kernel: [executeNever, index]
|
||||
executeNever: [kernel]
|
||||
macro:
|
||||
type: string
|
||||
pattern: '^[A-Za-z_][A-Za-z0-9_]*$'
|
||||
minLength: 1
|
||||
Loading…
Reference in a new issue