diff --git a/freedom-metal.make b/freedom-metal.make new file mode 100644 index 00000000..23b413f8 --- /dev/null +++ b/freedom-metal.make @@ -0,0 +1,71 @@ +# Copyright © 2020 Keith Packard # +# SPDX-License-Identifier: Apache-2.0 # + +.DEFAULT_GOAL = $(PROGRAM) + +ifeq ($(RISCV_LIBC),picolibc) +include $(FREEDOM_METAL)/metal_pico.make +endif + +ifeq ($(RISCV_LIBC),nano) +include $(FREEDOM_METAL)/metal_nano.make +endif + +include $(FREEDOM_METAL)/metal-depend.make + +include metal.mk + +SOURCE ?= . + +SOURCE_VPATH ?= $(SOURCE) + +vpath %.c $(SOURCE_VPATH):$(METAL_HELPER_VPATH):$(TARGET_C_VPATH) +vpath %.S $(SOURCE_VPATH):$(METAL_HELPER_VPATH):$(TARGET_S_VPATH) + +CC = riscv64-unknown-elf-gcc + +LDFLAGS = -nostartfiles -Wl,--gc-sections -T$(LDSCRIPT) + +ABIFLAGS = $(METAL_CFLAGS) -msave-restore + +INCFLAGS = -I. -I$(FREEDOM_METAL) + +OPT ?= -Os -g + +CFLAGS = --specs=$(RISCV_LIBC).specs -ffunction-sections -fdata-sections $(OPT) $(ABIFLAGS) $(INCFLAGS) $(LDFLAGS) $(SOURCE_CFLAGS) +LIBS = $(SOURCE_LIBS) + +SRC_C += $(METAL_HELPER_C) $(TARGET_C) +SRC_S += $(METAL_HELPER_S) $(TARGET_S) +OBJ = $(notdir $(SRC_C:.c=.o)) $(notdir $(SRC_S:.S=.o)) +GEN_HDR = metal/machine.h metal/machine/platform.h metal/machine/inline.h metal/machine-inline.h +HDR = $(wildcard *.h) $(GEN_HDR) + +ifndef quiet + +V?=0 +# The user has explicitly enabled quiet compilation. +ifeq ($(V),0) +quiet = @printf " $1 $2 $@\n"; $($1) +endif + +# Otherwise, print the full command line. +quiet ?= $($1) + +.c.o: + $(call quiet,CC) -c $(CFLAGS) -o $@ $< + +.S.o: + $(call quiet,CC) -c $(CFLAGS) -o $@ $< +endif + +$(PROGRAM): $(OBJ) $(LDSCRIPT) + $(CC) $(CFLAGS) -o $@ $(OBJ) -Wl,-Map=$(PROGRAM).map $(LIBS) + +$(OBJ): $(HDR) + +clean:: + rm -f $(PROGRAM) $(PROGRAM).map *.o + +echo:: + echo $(OBJ) diff --git a/metal-depend.make b/metal-depend.make new file mode 100644 index 00000000..63e8c334 --- /dev/null +++ b/metal-depend.make @@ -0,0 +1,49 @@ +# Copyright © 2020 Keith Packard # +# SPDX-License-Identifier: Apache-2.0 # + +METAL_DEPEND ?= $(FREEDOM_METAL)/scripts/metal_depend/metal_depend.py +LDSCRIPT_GENERATOR ?= $(FREEDOM_METAL)/../scripts/ldscript-generator/generate_ldscript.py + +LDSCRIPT ?= metal.default.lds + +METAL_HDR = metal/machine.h metal/machine/platform.h metal/machine/inline.h metal/machine-inline.h + +METAL_FILES = metal.mk $(METAL_HDR) design.dtb $(LDSCRIPT) + +ifneq ($(METAL_FEATURES),) +METAL_FEATURES_FLAG=-f $(METAL_FEATURES) +endif + +include $(BSP)/settings.mk + +METAL_CFLAGS=-march=$(RISCV_ARCH) -mabi=$(RISCV_ABI) -mcmodel=$(RISCV_CMODEL) + +metal.mk: $(BSP)/design.dts + python3 $(METAL_DEPEND) -o $@ -d $(BSP)/design.dts $(METAL_FEATURES_FLAG) -m $(FREEDOM_METAL) + +metal/machine.h: metal design.dtb + freedom-metal_header-generator -d design.dtb -o $@ + +metal/machine/platform.h: metal/machine design.dtb + freedom-bare_header-generator -d design.dtb -o $@ + +metal/machine-inline.h: metal/machine.h + +metal/machine/inline.h: metal/machine + echo "#include " > $@ + +metal: + mkdir $@ + +metal/machine: + mkdir -p $@ + +design.dtb: $(BSP)/design.dts + dtc -I dts -O dtb -o $@ $^ + +$(LDSCRIPT): $(BSP)/design.dts + $(LDSCRIPT_GENERATOR) -d $(BSP)/design.dts -o $@ + +clean:: + $(RM) $(METAL_FILES) + $(RM) -r metal diff --git a/metal_nano.make b/metal_nano.make new file mode 100644 index 00000000..b4aa8244 --- /dev/null +++ b/metal_nano.make @@ -0,0 +1,39 @@ +METAL_HELPER_C = \ + nanosleep.c \ + sys_access.c \ + sys_chdir.c \ + sys_chmod.c \ + sys_chown.c \ + sys_clock_gettime.c \ + sys_close.c \ + sys_execve.c \ + sys_exit.c \ + sys_faccessat.c \ + sys_fork.c \ + sys_fstat.c \ + sys_fstatat.c \ + sys_ftime.c \ + sys_getcwd.c \ + sys_getpid.c \ + sys_gettimeofday.c \ + sys_isatty.c \ + sys_kill.c \ + sys_link.c \ + sys_lseek.c \ + sys_lstat.c \ + sys_open.c \ + sys_openat.c \ + sys_read.c \ + sys_sbrk.c \ + sys_stat.c \ + sys_sysconf.c \ + sys_times.c \ + sys_unlink.c \ + sys_utime.c \ + sys_wait.c \ + sys_write.c + +METAL_HELPER_S = \ + crt0.S + +METAL_HELPER_VPATH=$(FREEDOM_METAL)/gloss diff --git a/metal_pico.make b/metal_pico.make new file mode 100644 index 00000000..648288f8 --- /dev/null +++ b/metal_pico.make @@ -0,0 +1,14 @@ +METAL_HELPER_C = \ + iob.c \ + sys_sbrk.c \ + sys_exit.c \ + sys_times.c \ + sys_sysconf.c \ + sys_gettimeofday.c \ + sys_clock_gettime.c \ + sys_write.c + +METAL_HELPER_S = \ + crt0.S + +METAL_HELPER_VPATH=$(FREEDOM_METAL)/pico:$(FREEDOM_METAL)/gloss diff --git a/pico/iob.c b/pico/iob.c index 481c014f..8e1aa466 100644 --- a/pico/iob.c +++ b/pico/iob.c @@ -41,4 +41,5 @@ static int metal_flush(FILE *file) { return 0; } static FILE __stdio = FDEV_SETUP_STREAM(metal_putc, metal_getc, metal_flush, _FDEV_SETUP_RW); -FILE *const __iob[3] = {&__stdio, &__stdio, &__stdio}; +/* Let applications replace this if they want */ +FILE *const __iob[3] __attribute__((weak)) = {&__stdio, &__stdio, &__stdio}; diff --git a/scripts/metal_depend/.gitignore b/scripts/metal_depend/.gitignore new file mode 100644 index 00000000..82adb58b --- /dev/null +++ b/scripts/metal_depend/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +venv diff --git a/scripts/metal_depend/Makefile b/scripts/metal_depend/Makefile new file mode 100644 index 00000000..a94636ee --- /dev/null +++ b/scripts/metal_depend/Makefile @@ -0,0 +1,26 @@ +# Copyright (c) 2020 SiFive Inc. +# SPDX-License-Identifier: Apache-2.0 + +all: virtualenv + +.PHONY: virtualenv +virtualenv: venv/.stamp + +venv/.stamp: venv/bin/activate requirements.txt + . venv/bin/activate && pip install --upgrade pip + . venv/bin/activate && pip install -r requirements.txt + @echo "Remember to source venv/bin/activate!" + touch $@ + +venv/bin/activate: + python3 -m venv venv + +.PHONY: test-lint +test-lint: virtualenv + . venv/bin/activate && pylint *.py + +.PHONY: test +test: test-lint + +clean: + -rm -rf venv __pycache__ diff --git a/scripts/metal_depend/README.md b/scripts/metal_depend/README.md new file mode 100644 index 00000000..79846b3b --- /dev/null +++ b/scripts/metal_depend/README.md @@ -0,0 +1,51 @@ +# metal_depend + +This is a python tool based on pydevicetree +([GitHub](https://github.com/sifive/pydevicetree)/[PyPI](https://pypi.org/project/pydevicetree/)) +which computes the set of FreedomMetal sources needed on a particular platform. + +## Usage + +``` +usage: metal_depend.py [-h] -d DTS -o OUTPUT + +Compute freedom-metal depedencies from Devicetrees + +optional arguments: + -h, --help show this help message and exit + -d DTS, --dts DTS The path to the Devicetree for the target + -o OUTPUT, --output OUTPUT + The path of the linker script file to output +``` + +## Required Devicetree Properties + +This source dependency generator expects that the Devicetree has annotated +each block with the compatible hardware definition. That property is: + + - `compatible`, which describes which interface this block supports + +Each of these properties is a `string` that describes the interface supported. + +For example, the chosen node may include the following property: +``` +compatible = "sifive,fe310-g000,hfxosc"; +``` +This maps to a driver named sifive_fe310-g000_hfxosc.c, which is found +in freedom-metal/src/drivers + +## Example Invocation + +``` +$ ./metal_depend.py -d e31.dts -o metal.mk +Discovering source dependencies for e31.dts + +$ head -n 20 metal.mk +``` + +## Copyright and License + +Copyright (c) 2020 SiFive Inc. + +The contents of this repository are distributed according to the terms described in the LICENSE +file. diff --git a/scripts/metal_depend/build.wake b/scripts/metal_depend/build.wake new file mode 100644 index 00000000..2b9a5134 --- /dev/null +++ b/scripts/metal_depend/build.wake @@ -0,0 +1,77 @@ +tuple MetalDependOptions = + global TopDTSFile: Path + global OtherDTSFiles: List Path + global OutputFile: String + global Layout: MetalDependLayout + +global data MetalDependLayout = + # The default mode tries to strike a good balance for all use-cases. + # If you don't need scratchpad or ramrodata modes, use the default one. + METALDEPEND_DEFAULT + # Scratchpad mode places the entire binary contents into the memory + # selected by metal,ram in the chosen node. + METALDEPEND_SCRATCHPAD + # Ramrodata mode places all read-only data into writable memory under + # the assumption that this decreases read latency for benchmarks. + # Also, if the memory selected by metal,itim is large enough, this places + # all non-init text into the ITIM. + METALDEPEND_RAMRODATA + # This layout is used for freertos use when pmp is need, it introduce symbole + # and sections for memory isolation (based on defauult lds) + METALDEPEND_FREERTOS + +# Instead of copying the script sources into build/{here}, we'll just +# execute directly out of the package directory, since nothing gets +# modified during execution. +# +# The virtualenv does get placed in build/{here} by +# addPythonRequirementsEnv, along with a copy of requirements.txt +def generatorDir = here + +####################################################################### +# makeMetalDependOptions takes the following parameters: +# - topDTSFile: The top-level Devicetree source file +# - otherDTSFiles: any other Devicetree source files included in the +# hierarchy of Devicetree source files +# - layout: one of {METALDEPEND_DEFAULT, METALDEPEND_SCRATCHPAD, +# METALDEPEND_RAMRODATA} +# - outputFile: A string representing the path of the output file to +# produce +####################################################################### +global def makeMetalDependOptions topDTSFile otherDTSFiles layout outputFile = + MetalDependOptions topDTSFile otherDTSFiles outputFile layout + +global def runMetalDepend options = + def topDTSFile = options.getMetalDependOptionsTopDTSFile + def otherDTSFiles = options.getMetalDependOptionsOtherDTSFiles + + def inputs = + # During execution, the generator needs access to both + # Python sources and the linker script template files + def generatorSources = sources here `.*\.(py|lds)` + def dtsSources = topDTSFile, otherDTSFiles + generatorSources ++ dtsSources + + def outputs = options.getMetalDependOptionsOutputFile, Nil + + def args = + def base = + "-d", topDTSFile.getPathName, + "-o", options.getMetalDependOptionsOutputFile, + Nil + + match options.getMetalDependOptionsLayout + METALDEPEND_DEFAULT = base + METALDEPEND_SCRATCHPAD = "--scratchpad", base + METALDEPEND_RAMRODATA = "--ramrodata", base + METALDEPEND_FREERTOS = "--freertos", base + + makePlan (pythonCommand "{generatorDir}/metal-depend.py" args) inputs + | addPlanRelativePath "PYTHONPATH" generatorDir + | addPythonRequirementsEnv generatorDir + | setPlanFnOutputs (\_ outputs) + | runJob + +# This allows the python virtualenv to be created prior to running a build +# with `wake preinstall Unit`. +publish preinstall = (pythonRequirementsInstaller generatorDir), Nil diff --git a/scripts/metal_depend/metal_depend.py b/scripts/metal_depend/metal_depend.py new file mode 100755 index 00000000..8c19b8d8 --- /dev/null +++ b/scripts/metal_depend/metal_depend.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 SiFive Inc. +# SPDX-License-Identifier: Apache-2.0 + +"""Generate list of freedom-metal drivers from devicetree source files""" + +import argparse +import sys +import os + +import jinja2 +import pydevicetree + +from sources import get_sources, find_source + +TEMPLATES_PATH = "templates" + +def missingvalue(message): + """ + Raise an UndefinedError + This function is made available to the template so that it can report + when required values are not present and cause template rendering to + fail. + """ + raise jinja2.UndefinedError(message) + + +def parse_arguments(argv): + """Parse the arguments into a dictionary with argparse""" + arg_parser = argparse.ArgumentParser( + description="Generate linker scripts from Devicetrees") + + arg_parser.add_argument("-d", "--dts", required=True, + help="The path to the Devicetree for the target") + arg_parser.add_argument("-m", "--metal", required=True, + help="The path to the freedom-metal source tree") + arg_parser.add_argument("-f", "--features", + help="A comma-separated list of metal features to enable") + arg_parser.add_argument("-o", "--output", + help="The path of the source list file to output") + arg_parser.add_argument("--template", + help="Emits a source list using the specified template (default=make)") + + return arg_parser.parse_args(argv) + + +def get_template(parsed_args): + """Initialize jinja2 and return the right template""" + env = jinja2.Environment( + loader=jinja2.PackageLoader(__name__, TEMPLATES_PATH), + trim_blocks=True, lstrip_blocks=True, + ) + # Make the missingvalue() function available in the template so that the + # template fails to render if we don't provide the values it needs. + env.globals["missingvalue"] = missingvalue + + template_name = parsed_args.template + if not template_name: + template_name = "make" + + template = env.get_template("%s.mk" % template_name) + print("Generating source list with %s template" % template_name, file=sys.stderr) + + return template + + +METAL_DIRS = ('src', 'src/drivers') + +HELPERS_C = ( + ("uart0", "uart.c"), + ("wdog0", "watchdog.c"), + ("clic0", "interrupt.c"), + ("plic0", "interrupt.c"), + ("clint0", "interrupt.c"), + ("gpio0", "gpio.c"), + ) + +METAL_C = ('init.c', 'synchronize_harts.c', 'inline.c', 'cpu.c', 'clock.c', 'timer.c') + +METAL_S = ('entry.S', 'trap.S', 'vector.S') + +def find_dirs(files): + """Find the set of directories containing files""" + ret = [] + for file in files: + direct = os.path.dirname(file) + if direct not in ret: + ret += [direct] + ret.sort() + return ret + +def find_paths(names, dirs): + """Find the full paths for the given names""" + ret = [] + for name in names: + path = find_source(name, dirs) + if path and path not in ret: + ret += [path] + ret.sort() + return ret + +FEATURE_C = { + "stdio": ['tty.c'], + "exit": ['shutdown.c'], +} + +def find_features(feature_arg, dirs): + """Get the sources needed for the requested features""" + + # Convert comma-separated argument into list + features = () + if feature_arg: + features = feature_arg.split(',') + + feature_c = [] + + for feature in features: + if feature in FEATURE_C: + feature_c += find_paths(FEATURE_C[feature], dirs) + + return feature_c + +def main(argv): + """Parse arguments, extract data, and render the linker script to file""" + parsed_args = parse_arguments(argv) + + template = get_template(parsed_args) + + dts = pydevicetree.Devicetree.parseFile( + parsed_args.dts, followIncludes=True) + + metal = parsed_args.metal + + dirs = [os.path.join(metal, sub) for sub in METAL_DIRS] + + (target_c, target_s) = get_sources(dts, dirs) + + # Add helper sources based on pattern matching source filenames + + helpers_c = [] + for t_c in target_c: + for helper_c in HELPERS_C: + if helper_c[0] in t_c: + helpers_c += [helper_c[1]] + + target_c += find_paths(helpers_c, dirs) + + # Add sources to support requested features + + target_c += find_features(parsed_args.features, dirs) + + # Add required metal sources + + target_c += find_paths(METAL_C, dirs) + target_s += find_paths(METAL_S, dirs) + + # Pass sorted sources to the template generator so that the generated + # file is reproducible. + + target_c.sort() + target_s.sort() + + target_c_dir = find_dirs(target_c) + target_s_dir = find_dirs(target_s) + + values = { + "target_c": target_c, + "target_s": target_s, + "target_c_dir": target_c_dir, + "target_s_dir": target_s_dir + } + + if parsed_args.output: + with open(parsed_args.output, 'w', encoding='utf-8') as outfile: + outfile.write(template.render(values)) + else: + print(template.render(values)) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/scripts/metal_depend/requirements.txt b/scripts/metal_depend/requirements.txt new file mode 100644 index 00000000..f047cfdb --- /dev/null +++ b/scripts/metal_depend/requirements.txt @@ -0,0 +1,4 @@ +Jinja2==2.10.1 +MarkupSafe==1.1.1 +pydevicetree==0.0.12 +pylint==2.4.4 diff --git a/scripts/metal_depend/sources.py b/scripts/metal_depend/sources.py new file mode 100644 index 00000000..bf925587 --- /dev/null +++ b/scripts/metal_depend/sources.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 SiFive Inc. +# SPDX-License-Identifier: Apache-2.0 + +"""Functions for finding freedom-metal sources in a Device Tree""" + +import os + +def has_compat(node) -> bool: + """ Checks whether the given node has a "compatible" value""" + return node.get_fields("compatible") is not None + +# Watchdog lives in AON block, but the driver is called wdog0 + +COMPATIBLE_MAP = { + "sifive,aon0": "sifive,wdog0", +} + +def get_compatibles(tree): + """Given a Devicetree, get the list of 'compatible' values""" + + compatibles = tree.filter(has_compat) + + return compatibles + +EXTENSIONS = ('.c', '.S') + +def make_filename(compatible): + """Convert a compatible value into a + freedom-metal style filename""" + return compatible.replace(',', '_') + +def find_source(basename, dirs): + """Locate the named file in one of the listed directories""" + for direc in dirs: + path = os.path.join(direc, basename) + if os.path.exists(path): + return path + return None + +def find_path(compat, dirs): + """Given a compatible string, find a matching file""" + file = make_filename(compat) + for ext in EXTENSIONS: + path = find_source(file + ext, dirs) + if path: + return path + if compat in COMPATIBLE_MAP: + return find_path(COMPATIBLE_MAP[compat], dirs) + return None + + +def get_sources(tree, dirs): + """Given a Devicetree, get the list of source files available""" + + compatibles = get_compatibles(tree) + sources_c = [] + sources_s = [] + for compatible in compatibles: + device_types = compatible.get_fields("device_type") + if device_types is None: + device_types = [None] + else: + # pylint: disable=R1721 + # this comprehension converts device_types into a list + device_types = [None] + [d for d in device_types] + path = None + for compat in compatible.get_fields("compatible"): + for device_type in device_types: + if device_type is not None: + compat += '_' + device_type + path = find_path(compat, dirs) + if path and path not in sources_c and path not in sources_s: + if path.endswith('.c'): + sources_c += [path] + else: + sources_s += [path] + + return (sources_c, sources_s)