Embedded HandbookApps
All sections › XXVI. From prototype to product
STM32F103 · level intermediate · 13 min read
Open in the interactive handbook

205б. Open-source licenses and SBOM: what you may put into firmware

Section 205а put together the ESKD documents for the device: the schematic, the parts list, the specification. The parts list contains every resistor. But the firmware of the same device usually contains more third-party code than your own: CMSIS, HAL, FreeRTOS, a file system, a TCP/IP stack, libraries from sections 42, 42а, 106в and 115г. Each piece has its own owner and its own license, and the license says on what terms this code may be passed on — including inside a device. This section covers the licenses of the code already met in the handbook, what they say and how to draw up a list of the software components of the firmware (SBOM, software bill of materials) as strictly as a parts list.

This is not legal advice. The section restates only what is written in the license texts themselves, as they lie on disk, with clause numbers. How to apply them to a specific device and contract is a question for a lawyer.

Sources: the STM32CubeF1 1.8.7 archive (License.md, Package_license.md, sbom_cdx.json, the components' LICENSE* files, the SLA0044 text in Projects/…/Applications/LICENSE.txt); modules of the Zephyr tree (littlefs, nanopb, zcbor, Mbed TLS 3.6) and the Zephyr SPDX generator (scripts/west_commands/zspdx); the texts of GPL-2.0, LGPL-3.0 and the GCC runtime library exception — from zephyr-sdk-1.0.1. All the matching is done by our own generator sbom205.py; the check is _verify/lic205c_check.py.

Terms used in the documentation

Term Meaning
permissive license conditions: keep the notice, include the text
copyleft derived work is passed on under the same terms, with sources
notice / attribution the copyright notice and the license text
object code / binary form compiled code — including code programmed into a device
combined work (LGPL) a program linked with the library
runtime library exception the GCC exception for its runtime library
SBOM the list of software components
SPDX license identifier the short license name from the SPDX list: MIT, BSD-3-Clause
license expression an expression such as Apache-2.0 OR GPL-2.0-or-later
LicenseRef-… your own name for a license that is not in the SPDX list

205б.1 What has already been taken, and under which licenses

The experiment: the device's source tree is assembled from archives on disk — 12 components, 39 files, 2490629 bytes. The generator determined the license of each file from its header, and the license of a component from its LICENSE file:

Component Version License Where the version comes from
CMSIS-Core 5.4.0 Apache-2.0 ARM.CMSIS.pdsc
CMSIS-Device STM32F1 4.3.5 Apache-2.0 stm32f1xx.h
HAL STM32F1 1.1.10 BSD-3-Clause stm32f1xx_hal.c
FreeRTOS 10.3.1 MIT task.h
USB Device Library ST 2.5.3 SLA0044 Release_Notes.html
FatFs R0.11 its own, one condition (ff.c); ST, five conditions (ff_gen_drv.c) ff.h
lwIP 2.0.3 BSD-3-Clause init.h
littlefs 2.11 BSD-3-Clause lfs.h
nanopb 0.4.9 Zlib library.json
zcbor 0.9.1 Apache-2.0 VERSION
Mbed TLS 3.6.6 Apache-2.0 OR GPL-2.0-or-later build_info.h
the device's own code 1.4.2 proprietary (LicenseRef-Proprietary) —

The firmware tree of 12 components and 39 files: on the left, how the generator learned each file's license (SPDX tag in 13 files, recognized text in the header in 13, a reference to the LICENSE file in 9, nothing in 4); on the right, the license families: permissive Apache-2.0, MIT, BSD-3-Clause, Zlib and FatFs; ST-chips-only SLA0044 and the ST five-clause license; dual-licensed Mbed TLS; and your own code

HAL and CMSIS-Device have no license in the file header — only a sentence saying that the terms are in the component's LICENSE file. And that file says: inside the package the terms of Package_license apply, outside the package BSD-3-Clause (for HAL) and Apache-2.0 (for CMSIS-Device). The Package_license.md of the F1 package is the ST agreement SLA0048, and its table lists these components with BSD-3-Clause and Apache-2.0.

The FatFs component shows that a license must be read file by file, not by the folder name: ff.c from ChaN carries a license with one condition, while ff_gen_drv.c from ST lying next to it carries a license with five conditions, and the fourth one requires the code to run only on ST chips.


205б.2 What the permissive licenses say

The main question for firmware: what does the license require when the code is passed on in binary form — programmed into a device. A summary from the texts on disk:

License What it says about binary form Where in the text
Apache-2.0 (CMSIS, zcbor, Mbed TLS) may be distributed "in Source or Object form" on these conditions: the recipient gets a copy of the license, modified files are marked, notices are kept, the NOTICE file, if any, is passed on sec. 4 (a)–(d)
MIT (FreeRTOS) the notice and the permission text are included in all copies or substantial portions the only paragraph of conditions
BSD-3-Clause (lwIP, littlefs, HAL outside the package) in binary form — reproduce the notice, the conditions and the disclaimer in the documentation or other materials condition 2
Zlib (nanopb) an acknowledgment in the product documentation is appreciated but not required; mark modified sources conditions 1 and 2
FatFs R0.11 (ChaN) one condition — for source code: keep the notice condition 1

There is no NOTICE file in the CMSIS, zcbor or Mbed TLS directories on disk. For permissive licenses the texts lead to one and the same conclusion: the code may be passed on inside the device, but the device documentation must contain the notices and license texts of those components that require them. The generator collects these texts into one file LICENSES-THIRD-PARTY.txt (58774 bytes for the nine components that have a LICENSE file) — it is attached to the manual.

ST licenses. SLA0044 (the text is in the examples' LICENSE.txt, Rev6, October 2025) allows redistribution in binary form without a notice if the code is embedded in an ST chip (condition 2), requires the code to run only on ST chips or together with them (condition 4) and prohibits any use that would bring the code under open-source license terms — GPL, EPL, Apache, BSD, MIT are named explicitly (condition 5). The package's SLA0048 contains conditions 2, 4 and 5 with the same meaning.


205б.3 GPL and LGPL in firmware

Copyleft licenses have a different condition: the sources go together with the binary.

A separate issue is the clash with the ST licenses. Condition 5 of SLA0044 prohibits using ST code in a way that brings it under open-source terms; GPL is named in it explicitly. GPL code and the ST USB library in one firmware are two texts with opposite requirements for one and the same image.


205б.4 SBOM: two formats and two ready examples on disk

An SBOM is a table of a program's components: name, version, supplier, license, checksum. There are two formats, both JSON:

Ready examples are already on disk. ST puts a file sbom_cdx.json into the STM32CubeF1 archive — CycloneDX 1.5, 46 components, made by a third-party analysis tool. Zephyr ships its own SPDX generator (west spdx, the zspdx directory): it writes SPDX 2.3 in tag-value form, uses the SPDX license list version 3.12 (499 identifiers), and looks for a file's license only by the SPDX-License-Identifier: tag in the first 20 lines.

That is not enough for ST code and old libraries: of the 39 files in the experiment, only 13 have an SPDX tag. The rest need their header text parsed.


205б.5 Our own generator: over the source tree on a PC

sbom205.py assembles the device tree from the archives on disk and describes it. It looks for a file's license in the first 80 lines — first the SPDX tag, then known texts:

def detect(text):
    """Лицензия по тексту заголовка или файла LICENSE. None — ничего не сказано; 'SEE-LICENSE' — отсылка к файлу."""
    m = re.search(r"SPDX-License-Identifier:\s*(.+)", text)
    if m:
        return m.group(1).strip().rstrip("*/").strip()
    t = re.sub(r"\s+", " ", re.sub(r"^[ \t]*[*/#;]+ ?", "", text, flags=re.M))
    if "Ultimate Liberty license SLA0044" in t or "terms of the SLA0044 license shall apply" in t:
        return "LicenseRef-ST-SLA0044"

The order matters. The ST five-clause license starts with the same words as BSD — "Redistribution and use in source and binary forms" — so the "ST chips only" marker is searched for first, and only then are the three BSD conditions counted:

        if "must execute solely and exclusively on microcontroller" in t:
            return "LicenseRef-ST-5-Clause"                 # похоже на BSD, но условий пять, и четвёртое — только кристаллы ST

Result for the 39 files: SPDX tag — 13, recognized text — 13, reference to the component's LICENSE — 9, nothing in the header — 4 (three nanopb files and pbuf.c from lwIP, whose license text starts at line 81). If a file is silent, the component's license from its LICENSE is used; if there is none either, the file gets NOASSERTION and the release stops.

That is exactly what happened on the first run: your own code contained app/ringbuf.c, taken "from somewhere", without a license line. The generator replied нет лицензии: ./app/ringbuf.c ("no license"), and the component's result was NOASSERTION. After the SPDX-License-Identifier: line was added, there were 0 errors.

The output is three files: sbom.spdx.json, sbom.cdx.json and LICENSES-THIRD-PARTY.txt. The date inside is fixed, the SPDX namespace and the CycloneDX serial number are derived from the file checksums — two runs gave reports that matched byte for byte. The SBOM is reproducible just like the firmware in section 201а, and it can be stored next to merged.hex in the release log.


205б.6 Cross-check: all files found, licenses match

The section's check compares the report with the tree and with two independent implementations:

What was compared Result
files in the tree / in SPDX 39 / 39, SHA-1 and SHA-256 recomputed for each
verification code of 12 packages matches the calculateVerificationCode function from zspdx
license identifiers 5 — in the SPDX 3.12 list from Zephyr; 4 — LicenseRef-… with an explanation in hasExtractedLicensingInfos
SPDX tag in the first 20 lines (Zephyr scanner) 13 files, all with the same answer as our own generator
license in the header ↔ component license 0 mismatches
SBOM from ST (sbom_cdx.json) 7 common components: versions match for all, licenses for 6

There is one mismatch — FatFs: ST's SBOM says BSD-3-Clause, while the header of ff.c of the same version R0.11 has one condition, and ff_gen_drv.c has five ST conditions. Whose SBOM is "right" is not a question for the generator; the generator showed what is written in the files and pointed to where they disagree.

Under Windows the Zephyr scanner first found 11 files, not 13: it opens files in the system encoding (here cp1251), and two files of our own code with Russian comments in UTF-8 could not be read. With PYTHONUTF8=1 it found 13.


205б.7 Pitfalls

"Same license as the folder". One FatFs folder holds a license with one condition and an ST license with five. The SBOM is built per file, and the component's result is the expression LicenseRef-FatFs-ChaN AND LicenseRef-ST-5-Clause.

"Looks like BSD". The ST five-clause license starts with the words of BSD. A search by the first sentence will record it as BSD-3-Clause and lose the "ST chips only" condition. Markers of restrictions are checked before general ones.

A license below line twenty. In pbuf.c the text starts at line 81, after the module description. A scanner of the first lines does not see it; the component's LICENSE saves you — if there is one.

A file "from somewhere". A piece of code without a license line is the most common hole. The generator must stop the release on NOASSERTION rather than silently assign the project's license.

Two ST agreement numbers in one package. License.md names SLA0044 for the USB library, Package_license.md of the same package names SLA0048, the library files name SLA0044. The SBOM records what is in the file, and the mismatch is noted.

A version "from the header". nanopb's pb.h says nanopb-1.0.0-dev, while library.json says 0.4.9. For CMSIS, cmsis_version.h gives 5.1 (the version of the CMSIS-Core core), while the package description says 5.4.0. The SBOM states where the version was taken from, and it is taken from the same place for every release.

Scanner encoding. A third-party scanner that reads files in the system encoding skips files with Russian comments in UTF-8 — without an error, simply "license not found".

GPL "through a dependency". Mbed TLS lets you choose GPL-2.0, but the choice is made by the recipient of the code. If the choice is not recorded in the release documents, an SBOM with an OR expression remains a question, not an answer.


205б.8 Summary

Next: section 206 "Certification: conformity assessment and radio modules".

Open in the interactive handbook