diff --git a/meta/classes-recipe/baremetal-image.bbclass b/meta/classes-recipe/baremetal-image.bbclass index 7938c0a83f..4afc171314 100644 --- a/meta/classes-recipe/baremetal-image.bbclass +++ b/meta/classes-recipe/baremetal-image.bbclass @@ -16,8 +16,8 @@ # See meta-skeleton for a working example. -# Toolchain should be baremetal or newlib based. -# TCLIBC="baremetal" or TCLIBC="newlib" +# Toolchain should be baremetal or newlib/picolibc based. +# TCLIBC="baremetal" or TCLIBC="newlib" or TCLIBC="picolibc" COMPATIBLE_HOST:libc-musl:class-target = "null" COMPATIBLE_HOST:libc-glibc:class-target = "null" diff --git a/meta/classes-recipe/cross-canadian.bbclass b/meta/classes-recipe/cross-canadian.bbclass index 1670217d69..059d9aa95f 100644 --- a/meta/classes-recipe/cross-canadian.bbclass +++ b/meta/classes-recipe/cross-canadian.bbclass @@ -36,7 +36,7 @@ python () { if d.getVar("MODIFYTOS") != "1": return - if d.getVar("TCLIBC") in [ 'baremetal', 'newlib' ]: + if d.getVar("TCLIBC") in [ 'baremetal', 'newlib', 'picolibc' ]: return tos = d.getVar("TARGET_OS") diff --git a/meta/conf/distro/include/maintainers.inc b/meta/conf/distro/include/maintainers.inc index 37ad293e32..cf9fda812f 100644 --- a/meta/conf/distro/include/maintainers.inc +++ b/meta/conf/distro/include/maintainers.inc @@ -576,6 +576,8 @@ RECIPE_MAINTAINER:pn-pcmanfm = "Alexander Kanavin " RECIPE_MAINTAINER:pn-perf = "Bruce Ashfield " RECIPE_MAINTAINER:pn-perl = "Alexander Kanavin " RECIPE_MAINTAINER:pn-perlcross = "Alexander Kanavin " +RECIPE_MAINTAINER:pn-picolibc = "Alejandro Hernandez " +RECIPE_MAINTAINER:pn-picolibc-helloworld = "Alejandro Hernandez " RECIPE_MAINTAINER:pn-piglit = "Ross Burton " RECIPE_MAINTAINER:pn-pigz = "Hongxu Jia " RECIPE_MAINTAINER:pn-pinentry = "Unassigned " diff --git a/meta/conf/distro/include/tclibc-picolibc.inc b/meta/conf/distro/include/tclibc-picolibc.inc new file mode 100644 index 0000000000..203765dfcb --- /dev/null +++ b/meta/conf/distro/include/tclibc-picolibc.inc @@ -0,0 +1,40 @@ +# +# Picolibc configuration +# + +LIBCEXTENSION = "-picolibc" +LIBCOVERRIDE = ":libc-picolibc" + +PREFERRED_PROVIDER_virtual/libc ?= "picolibc" +PREFERRED_PROVIDER_virtual/libiconv ?= "picolibc" +PREFERRED_PROVIDER_virtual/libintl ?= "picolibc" +PREFERRED_PROVIDER_virtual/nativesdk-libintl ?= "nativesdk-glibc" +PREFERRED_PROVIDER_virtual/nativesdk-libiconv ?= "nativesdk-glibc" + +DISTRO_FEATURES_BACKFILL_CONSIDERED += "ldconfig" + +IMAGE_LINGUAS = "" + +LIBC_DEPENDENCIES = " \ + picolibc-dbg \ + picolibc-dev \ + libgcc-dev \ + libgcc-dbg \ + libstdc++-dev \ + libstdc++-staticdev \ +" + +ASSUME_PROVIDED += "virtual/crypt" + +TARGET_OS = "elf" +TARGET_OS:arm = "eabi" + +TOOLCHAIN_HOST_TASK ?= "packagegroup-cross-canadian-${MACHINE} nativesdk-qemu nativesdk-sdk-provides-dummy" +TOOLCHAIN_TARGET_TASK ?= "${LIBC_DEPENDENCIES}" +TOOLCHAIN_NEED_CONFIGSITE_CACHE:remove = "zlib ncurses" + +# RISCV linker doesnt support PIE +SECURITY_CFLAGS:libc-picolibc:qemuriscv32 = "${SECURITY_NOPIE_CFLAGS}" +SECURITY_CFLAGS:libc-picolibc:qemuriscv64 = "${SECURITY_NOPIE_CFLAGS}" + + diff --git a/meta/conf/documentation.conf b/meta/conf/documentation.conf index 155353eafc..e912e91265 100644 --- a/meta/conf/documentation.conf +++ b/meta/conf/documentation.conf @@ -421,7 +421,7 @@ TARGET_FPU[doc] = "Specifies the method for handling FPU code. For FPU-less targ TARGET_OS[doc] = "Specifies the target's operating system." TARGET_PREFIX[doc] = "The prefix for the cross-compile toolchain (e.g. arm-linux-)." TARGET_SYS[doc] = "The target system is comprised of TARGET_ARCH,TARGET_VENDOR and TARGET_OS." -TCLIBC[doc] = "Specifies C library (libc) variant to use during the build process. You can select 'baremetal', 'glibc', 'musl' or 'newlib'." +TCLIBC[doc] = "Specifies C library (libc) variant to use during the build process. You can select 'baremetal', 'glibc', 'musl', 'newlib', or 'picolibc'." TCMODE[doc] = "Enables an external toolchain (where provided by an additional layer) if set to a value other than 'default'." TESTIMAGE_AUTO[doc] = "Enables test booting of virtual machine images under the QEMU emulator after any root filesystems are created and runs tests against those images each time an image is built." TEST_QEMUBOOT_TIMEOUT[doc] = "The time in seconds allowed for an image to boot before automated runtime tests begin to run against an image." diff --git a/meta/conf/machine/include/riscv/arch-riscv.inc b/meta/conf/machine/include/riscv/arch-riscv.inc index 230a266563..b34064e78f 100644 --- a/meta/conf/machine/include/riscv/arch-riscv.inc +++ b/meta/conf/machine/include/riscv/arch-riscv.inc @@ -11,5 +11,6 @@ TUNE_CCARGS:append = "${@bb.utils.contains('TUNE_FEATURES', 'riscv64nc', ' -marc # Fix: ld: unrecognized option '--hash-style=sysv' LINKER_HASH_STYLE:libc-newlib = "" +LINKER_HASH_STYLE:libc-picolibc = "" # Fix: ld: unrecognized option '--hash-style=gnu' LINKER_HASH_STYLE:libc-baremetal = "" diff --git a/meta/lib/oeqa/selftest/cases/distrodata.py b/meta/lib/oeqa/selftest/cases/distrodata.py index bd37552364..7771a42e2b 100644 --- a/meta/lib/oeqa/selftest/cases/distrodata.py +++ b/meta/lib/oeqa/selftest/cases/distrodata.py @@ -55,7 +55,7 @@ but their recipes claim otherwise by setting UPSTREAM_VERSION_UNKNOWN. Please re return False def is_maintainer_exception(entry): - exceptions = ["musl", "newlib", "linux-yocto", "linux-dummy", "mesa-gl", "libgfortran", "libx11-compose-data", + exceptions = ["musl", "newlib", "picolibc", "linux-yocto", "linux-dummy", "mesa-gl", "libgfortran", "libx11-compose-data", "cve-update-nvd2-native",] for i in exceptions: if i in entry: diff --git a/meta/lib/oeqa/selftest/cases/picolibc.py b/meta/lib/oeqa/selftest/cases/picolibc.py new file mode 100644 index 0000000000..e40b4fc3d3 --- /dev/null +++ b/meta/lib/oeqa/selftest/cases/picolibc.py @@ -0,0 +1,18 @@ +# +# Copyright OpenEmbedded Contributors +# +# SPDX-License-Identifier: MIT +# + +from oeqa.selftest.case import OESelftestTestCase +from oeqa.utils.commands import bitbake, get_bb_var + +class PicolibcTest(OESelftestTestCase): + + def test_picolibc(self): + compatible_machines = ['qemuarm', 'qemuarm64', 'qemuriscv32', 'qemuriscv64'] + machine = get_bb_var('MACHINE') + if machine not in compatible_machines: + self.skipTest('This test only works with machines : %s' % ' '.join(compatible_machines)) + self.write_config('TCLIBC = "picolibc"') + bitbake("picolibc-helloworld") diff --git a/meta/recipes-core/picolibc/picolibc-helloworld_git.bb b/meta/recipes-core/picolibc/picolibc-helloworld_git.bb new file mode 100644 index 0000000000..573a571c24 --- /dev/null +++ b/meta/recipes-core/picolibc/picolibc-helloworld_git.bb @@ -0,0 +1,40 @@ +require picolibc.inc + +# baremetal-image overrides +BAREMETAL_BINNAME ?= "hello_picolibc_${MACHINE}" +IMAGE_LINK_NAME ?= "baremetal-picolibc-image-${MACHINE}" +IMAGE_NAME_SUFFIX ?= "" +QB_DEFAULT_KERNEL ?= "${IMAGE_LINK_NAME}.elf" + +inherit baremetal-image + +COMPATIBLE_MACHINE = "qemuarm|qemuarm64|qemuriscv32|qemuriscv64" + +# Use semihosting to test via QEMU +QB_OPT_APPEND:append = " -semihosting-config enable=on" + +# picolibc comes with a set of linker scripts, set the file +# according to the architecture being built. +PICOLIBC_LINKERSCRIPT:qemuarm64 = "aarch64.ld" +PICOLIBC_LINKERSCRIPT:qemuarm = "arm.ld" +PICOLIBC_LINKERSCRIPT:qemuriscv32 = "riscv.ld" +PICOLIBC_LINKERSCRIPT:qemuriscv64 = "riscv.ld" + +# Simple compile function that manually exemplifies usage; as noted, +# use a custom linker script, the GCC specs provided by picolibc +# and semihost to be able to test via QEMU's monitor +do_compile(){ + ${CC} ${CFLAGS} ${LDFLAGS} --verbose -T${S}/hello-world/${PICOLIBC_LINKERSCRIPT} -specs=picolibc.specs --oslib=semihost -o ${BAREMETAL_BINNAME}.elf ${S}/hello-world/hello-world.c + ${OBJCOPY} -O binary ${BAREMETAL_BINNAME}.elf ${BAREMETAL_BINNAME}.bin +} + +do_install(){ + install -d ${D}/${base_libdir}/firmware + install -m 755 ${B}/${BAREMETAL_BINNAME}.elf ${D}/${base_libdir}/firmware/${BAREMETAL_BINNAME}.elf + install -m 755 ${B}/${BAREMETAL_BINNAME}.bin ${D}/${base_libdir}/firmware/${BAREMETAL_BINNAME}.bin +} + +FILES:${PN} += " \ + ${base_libdir}/firmware/${BAREMETAL_BINNAME}.elf \ + ${base_libdir}/firmware/${BAREMETAL_BINNAME}.bin \ +" diff --git a/meta/recipes-core/picolibc/picolibc.inc b/meta/recipes-core/picolibc/picolibc.inc new file mode 100644 index 0000000000..3b380fe7af --- /dev/null +++ b/meta/recipes-core/picolibc/picolibc.inc @@ -0,0 +1,21 @@ +SUMMARY = "C Libraries for Smaller Embedded Systems" +HOMEPAGE = "https://keithp.com/picolibc" +DESCRIPTION = "Picolibc is a set of standard C libraries, both libc and libm, designed for smaller embedded systems with limited ROM and RAM. Picolibc includes code from Newlib and AVR Libc." +SECTION = "libs" + +# Newlib based code but GPL related bits removed, test/printf-tests.c and test/testcases.c +# are GPLv2 and GeneratePicolibcCrossFile.sh is AGPL3 but not part of the artifacts. +LICENSE = "BSD-2-Clause & BSD-3-Clause" +LIC_FILES_CHKSUM = " \ + file://COPYING.GPL2;md5=59530bdf33659b29e73d4adb9f9f6552 \ + file://COPYING.NEWLIB;md5=08ae03456feb75b81cfdb359e0f1ef85 \ + file://COPYING.picolibc;md5=e50fa9458a40929689861ed472d46bc7 \ + " + +BASEVER = "1.8.6" +PV = "${BASEVER}+git" +SRC_URI = "git://github.com/picolibc/picolibc.git;protocol=https;branch=main" +SRCREV="764ef4e401a8f4c6a86ab723533841f072885a5b" + +S = "${WORKDIR}/git" +B = "${WORKDIR}/build" diff --git a/meta/recipes-core/picolibc/picolibc/avoid_polluting_cross_directories.patch b/meta/recipes-core/picolibc/picolibc/avoid_polluting_cross_directories.patch new file mode 100644 index 0000000000..da6460c95c --- /dev/null +++ b/meta/recipes-core/picolibc/picolibc/avoid_polluting_cross_directories.patch @@ -0,0 +1,119 @@ +Upstream-Status: Pending + +Picolibc uses its own specs file: picolibc.specs to facilitate compilation, this +needs to be passed down to GCC via the -specs argument. + +Using this specs file overrides some of the default options our toolchain was +built with, in this case, they modify the include_dir and lib_dir paths used for +compilation, their intention was to add support for -picolibc-prefix and +-picolibc-buildtype arguments via the C preprocessor. + +-isystem %{-picolibc-prefix=*:%*/include/; -picolibc-buildtype=*:/usr/include/%*; :/usr/include} %(picolibc_cpp) + +This had the unwanted effect of defaulting to /usr/include for include_dir if +those arguments are not being passed, this works fine for their flow but for us +it pollutes the include directories with paths from the host. The same effect is +applicable for lib_dir and for the c runtime file. + +Our toolchain relies on --sysroot to avoid using any paths from the host, here we +manually add support for a third possible argument: -sysroot , if this is passed +then the paths used by the compiler will be relative to the path passed by the +--sysroot= cmdline argument, setting back the behavior that we intended in the +first place. + + +Signed-off-by: Alejandro Enedino Hernandez Samaniego + +Index: git/meson.build +=================================================================== +--- git.orig/meson.build ++++ git/meson.build +@@ -622,12 +622,13 @@ else + # + picolibc_prefix_format = '-picolibc-prefix=*:@0@' + picolibc_buildtype_format = '-picolibc-buildtype=*:@0@' ++sysroot_format = '-sysroot=*:@0@' + gen_format = '@0@' + + # + # How to glue the three options together + # +-specs_option_format = '%{@0@; @1@; :@2@}' ++specs_option_format = '%{@0@; @1@; @2@; :@3@}' + + # + # Build the -isystem value +@@ -639,10 +640,13 @@ isystem_prefix = picolibc_prefix_format. + buildtype_include_dir = specs_prefix_format.format(get_option('includedir') / '%*') + isystem_buildtype = picolibc_buildtype_format.format(buildtype_include_dir) + ++sysroot_include_dir = '%*' ++isystem_sysroot = sysroot_format.format(sysroot_include_dir) ++ + gen_include_dir = specs_prefix_format.format(get_option('includedir')) + isystem_gen = gen_format.format(gen_include_dir) + +-specs_isystem = '-isystem ' + specs_option_format.format(isystem_prefix, isystem_buildtype, isystem_gen) ++specs_isystem = '-isystem ' + specs_option_format.format(isystem_prefix, isystem_buildtype, isystem_sysroot, isystem_gen) + + # + # Build the non-multilib -L value +@@ -654,10 +658,13 @@ lib_prefix = picolibc_prefix_format.form + buildtype_lib_dir = specs_prefix_format.format(get_option('libdir') / '%*') + lib_buildtype = picolibc_buildtype_format.format(buildtype_lib_dir) + ++sysroot_lib_dir = '%*' ++lib_sysroot = sysroot_format.format(sysroot_lib_dir) ++ + gen_lib_dir = specs_prefix_format.format(get_option('libdir')) + lib_gen = gen_format.format(gen_lib_dir) + +-specs_libpath = '-L' + specs_option_format.format(lib_prefix, lib_buildtype, lib_gen) ++specs_libpath = '-L' + specs_option_format.format(lib_prefix, lib_buildtype, lib_sysroot, lib_gen) + + # + # Build the non-multilib *startfile options +@@ -669,6 +676,9 @@ crt0_prefix = picolibc_prefix_format.for + buildtype_crt0_path = specs_prefix_format.format(get_option('libdir') / '%*' / crt0_expr) + crt0_buildtype = picolibc_buildtype_format.format(buildtype_crt0_path) + ++sysroot_crt0_path = '%*' + '/' + get_option('libdir') + '/' + '%*' + '/' + crt0_expr ++crt0_sysroot = picolibc_buildtype_format.format(sysroot_crt0_path) ++ + gen_crt0_path = specs_prefix_format.format(get_option('libdir') / crt0_expr) + crt0_gen = gen_format.format(gen_crt0_path) + +@@ -686,10 +696,13 @@ if enable_multilib + buildtype_multilib_dir = specs_prefix_format.format(get_option('libdir') / '%*/%M') + multilib_buildtype = picolibc_buildtype_format.format(buildtype_multilib_dir) + ++ sysroot_multilib_dir = '%*' + '/' + get_option('libdir') + '/' + '%*/%M' ++ multilib_sysroot = sysroot_format.format(sysroot_multilib_dir) ++ + gen_multilib_dir = specs_prefix_format.format(get_option('libdir') / '%M') + multilib_gen = gen_format.format(gen_multilib_dir) + +- specs_multilibpath = '-L' + specs_option_format.format(multilib_prefix, multilib_buildtype, multilib_gen) ++ specs_multilibpath = '-L' + specs_option_format.format(multilib_prefix, multilib_buildtype, multilib_sysroot, multilib_gen) + + # + # Prepend the multilib -L option to the non-multilib option +@@ -705,6 +718,9 @@ if enable_multilib + buildtype_multilib_crt0_path = specs_prefix_format.format(get_option('libdir') / '%*/%M' / crt0_expr) + crt0_buildtype = picolibc_buildtype_format.format(buildtype_multilib_crt0_path) + ++ sysroot_multilib_crt0_path = '%*' + prefix + '/' + get_option('libdir') + '/' + '/%M' + '/' + crt0_expr ++ crt0_sysroot = sysroot_format.format(sysroot_multilib_crt0_path) ++ + gen_multilib_crt0_path = specs_prefix_format.format(get_option('libdir') / '%M' / crt0_expr) + crt0_gen = gen_format.format(gen_multilib_crt0_path) + endif +@@ -714,7 +730,7 @@ endif + # above. As there's only one value, it's either the + # multilib path or the non-multilib path + # +-specs_startfile = specs_option_format.format(crt0_prefix, crt0_buildtype, crt0_gen) ++specs_startfile = specs_option_format.format(crt0_prefix, crt0_buildtype, crt0_sysroot, crt0_gen) + endif + + specs_data = configuration_data() diff --git a/meta/recipes-core/picolibc/picolibc/no-early-compiler-checks.cross b/meta/recipes-core/picolibc/picolibc/no-early-compiler-checks.cross new file mode 100644 index 0000000000..87bfbad3c5 --- /dev/null +++ b/meta/recipes-core/picolibc/picolibc/no-early-compiler-checks.cross @@ -0,0 +1,6 @@ +# We need to explicitly bypass mesons sanity check to avoid early compiler errors +# otherwise meson will try to compile AND run test applications: +# ../git/meson.build:35:0: ERROR: Executables created by c compiler are not runnable... + +[properties] +skip_sanity_check=true \ No newline at end of file diff --git a/meta/recipes-core/picolibc/picolibc_git.bb b/meta/recipes-core/picolibc/picolibc_git.bb new file mode 100644 index 0000000000..fdb159328f --- /dev/null +++ b/meta/recipes-core/picolibc/picolibc_git.bb @@ -0,0 +1,35 @@ +require picolibc.inc + +INHIBIT_DEFAULT_DEPS = "1" +DEPENDS = "virtual/${TARGET_PREFIX}gcc" + +PROVIDES += "virtual/libc virtual/libiconv virtual/libintl" + +COMPATIBLE_HOST:libc-musl:class-target = "null" +COMPATIBLE_HOST:libc-glibc:class-target = "null" +COMPATIBLE_MACHINE = "qemuarm|qemuarm64|qemuriscv32|qemuriscv64" + +SRC_URI:append = " file://avoid_polluting_cross_directories.patch" +SRC_URI:append = " file://no-early-compiler-checks.cross" + +# This is being added by picolibc meson files as well to avoid +# early compiler tests from failing, cant remember why I added it +# to the newlib recipe but I would assume it was for the same reason +TARGET_CC_ARCH:append = " -nostdlib" + +# When using RISCV64 use medany for both C library and application recipes +TARGET_CFLAGS:append:qemuriscv64 = " -mcmodel=medany" + +inherit meson + +MESON_CROSS_FILE:append = " --cross-file=${UNPACKDIR}/no-early-compiler-checks.cross" + +PACKAGECONFIG ??= " specsdir" +# Install GCC specs on libdir +PACKAGECONFIG[specsdir] = "-Dspecsdir=${libdir},-Dspecsdir=none" + + +FILES:${PN}-dev:append = " ${libdir}/*.specs ${libdir}/*.ld" + +# No rpm package is actually created but -dev depends on it, avoid dnf error +DEV_PKG_DEPENDENCY:libc-picolibc = "" diff --git a/meta/recipes-devtools/gcc/gcc-cross.inc b/meta/recipes-devtools/gcc/gcc-cross.inc index 5b0ca15d47..c04177df5a 100644 --- a/meta/recipes-devtools/gcc/gcc-cross.inc +++ b/meta/recipes-devtools/gcc/gcc-cross.inc @@ -34,6 +34,7 @@ EXTRA_OECONF += "\ EXTRA_OECONF:append:libc-baremetal = " --without-headers" EXTRA_OECONF:remove:libc-baremetal = "--enable-threads=posix" EXTRA_OECONF:remove:libc-newlib = "--enable-threads=posix" +EXTRA_OECONF:remove:libc-picolibc = "--enable-threads=posix" EXTRA_OECONF_PATHS = "\ --with-gxx-include-dir=/not/exist${target_includedir}/c++/${BINV} \ diff --git a/meta/recipes-devtools/gcc/gcc-runtime.inc b/meta/recipes-devtools/gcc/gcc-runtime.inc index ad9798530f..8e0d1a6889 100644 --- a/meta/recipes-devtools/gcc/gcc-runtime.inc +++ b/meta/recipes-devtools/gcc/gcc-runtime.inc @@ -17,6 +17,7 @@ EXTRA_OECONF_PATHS = "\ EXTRA_OECONF:append:linuxstdbase = " --enable-clocale=gnu" EXTRA_OECONF:append = " --cache-file=${B}/config.cache" EXTRA_OECONF:append:libc-newlib = " --with-newlib --with-target-subdir" +EXTRA_OECONF:append:libc-picolibc = " --with-newlib --with-target-subdir" EXTRA_OECONF:append:libc-baremetal = " --with-target-subdir" # Disable ifuncs for libatomic on arm conflicts -march/-mcpu @@ -27,6 +28,7 @@ DISABLE_STATIC:class-nativesdk ?= "" # Newlib does not support symbol versioning on libsdtcc++ SYMVERS_CONF:libc-newlib = "" +SYMVERS_CONF:libc-picolibc = "" # Building with thumb enabled on armv6t fails ARM_INSTRUCTION_SET:armv6 = "arm" @@ -47,6 +49,7 @@ RUNTIMETARGET = "${RUNTIMELIBSSP} libstdc++-v3 libgomp libatomic ${RUNTIMELIBITM " # Only build libstdc++ for newlib RUNTIMETARGET:libc-newlib = "libstdc++-v3" +RUNTIMETARGET:libc-picolibc = "libstdc++-v3" # libiberty # libgfortran needs separate recipe due to libquadmath dependency diff --git a/meta/recipes-devtools/gcc/libgcc-common.inc b/meta/recipes-devtools/gcc/libgcc-common.inc index d9084af51a..e3db17d700 100644 --- a/meta/recipes-devtools/gcc/libgcc-common.inc +++ b/meta/recipes-devtools/gcc/libgcc-common.inc @@ -53,6 +53,11 @@ do_install:append:libc-newlib () { rmdir ${D}${base_libdir} fi } +do_install:append:libc-picolibc () { + if [ "${base_libdir}" != "${libdir}" ]; then + rmdir ${D}${base_libdir} + fi +} # No rpm package is actually created but -dev depends on it, avoid dnf error DEV_PKG_DEPENDENCY:libc-baremetal = ""