SchOS/docs/licensing.md
Nexoniarz 901ae03a69 ldls: Apache-2.0, and where the boundary actually is
Answers the question directly in docs/licensing.md §6, because "LDLS runs
Linux drivers" reads like it contradicts §2 and does not.

LDLS is Apache-2.0. Every file under ldls/ is ours; the loader came from the
ELF-64 gABI and the x86-64 psABI. The licence of LDLS and the licence status
of what happens at run time are two different questions and only the first is
about us:

  ldls/                       Apache-2.0    distributed by us
  a .ko                       GPL-2.0       distributed by the user's distro
  the two in one process      --            distributed by nobody

GPLv2 §0 says where its obligations attach -- "the act of running the Program
is not restricted" -- so no combined work is distributed by anyone and no
obligation is triggered. Same position FreeBSD has shipped from for a decade.
Stated honestly in the same section: the *user* cannot redistribute the
combination either, which is why RAMDISK_EXCLUDE exists and why .gitignore
refuses *.ko.

EXPORT_SYMBOL_GPL was left open in the plan; it is now answered and the answer
is cleaner than expected. That marking gates *Linux's own* symbols. Every
symbol LDLS provides is ours, reimplemented from facts, so there is no gate
being bypassed because no Linux symbol is being exposed. LDLS implements no
licence check -- it reads the declared licence and prints it before loading
anything.

And the sharpest edge in rule 3 mostly goes away. The uncomfortable row was
struct layouts, which a compiled driver indexes by offset, so the shim must
reproduce them exactly -- and a field-for-field match looks like transcription
even when it is not. It does not have to be in our source: kernels built with
CONFIG_DEBUG_INFO_BTF publish /sys/kernel/btf/vmlinux, and that carries both
the layouts and the function prototypes as generated type data about the
kernel the user already has. Verified on this machine: struct pci_dev field by
field, and FUNC 'pci_iomap' with its FUNC_PROTO. LDLS reads them at load time
on the user's machine; our source contains neither.

That also mostly dissolves rule 4, since BTF describes the kernel that is
actually installed rather than one pinned by guessing. What BTF does not give
is behaviour -- what napi_complete_done *does* is still ours to write from
documentation, and that is the whole of the real work.

Also adds the SPDX header ldls/ko.h was missing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-14 07:52:01 +02:00

342 lines
15 KiB
Markdown

# Licensing
The one page to check before copying anyone else's code into SchOS.
Referenced from `LICENSE`, `LICENSE.MIT` and `NOTICE`, all three of which
pointed here long before this file existed.
---
## 1. SchOS is Apache-2.0
Copyright Nexoniarz. See `LICENSE`.
Apache-2.0 was chosen over MIT/BSD for one reason that actually matters at OS
scale: it contains an **express patent grant** and a patent-retaliation clause.
MIT and BSD are silent on patents, which means a contributor can hand you code
and later assert a patent over it. For a kernel that is intended to accept
outside contributions, that silence is the expensive kind.
What that grant means in practice: Apache-2.0 §2 gives everyone a perpetual,
worldwide, irrevocable licence to reproduce, modify, sublicense and distribute
SchOS. Any additional restriction stated elsewhere — in a README, a website,
or a policy — does not narrow it. The licence text is what holds up.
If restricted distribution is ever genuinely wanted, that is a licence change
(a bespoke proprietary licence, or something like BUSL-1.1 with a conversion
date), not a wording change, and it means giving up the Apache grant.
---
## 2. What can be copied in
| Licence | Verdict | Obligation |
|---|---|---|
| Public domain, 0BSD, CC0 | **Yes** | None |
| MIT, ISC | **Yes** | Keep the notice |
| BSD 2-clause | **Yes** | Keep the notice |
| BSD 3-clause | **Yes** | Keep the notice; no endorsement claims |
| BSD **4-clause** (advertising) | **Avoid** — see §3 | Notice *and* an acknowledgement in advertising materials |
| Apache-2.0 | **Yes** | Keep NOTICE contents |
| MPL-2.0 | Case by case | File-level copyleft; keep those files separate |
| **GPL-2.0-only** | **No** | — |
| **GPL-3.0 / LGPL-3.0** | **No** for linked code | — |
**Every "yes" above requires an entry in `NOTICE`.** That file is the project's
attribution record and the thing that makes the permissive licences actually
satisfied rather than merely tolerated.
### Why GPL-2.0-only is a hard no
Apache-2.0 imposes conditions GPLv2 treats as "further restrictions" under its
§6 — principally the patent-retaliation term. The FSF and the ASF agree on
this: **Apache-2.0 code and GPL-2.0-only code cannot be combined into one
distributable work.** It is not a matter of taste or of how carefully the files
are kept apart in the tree; it is that the combined binary cannot satisfy both
licences at once.
GPL-3.0 fixed this in the other direction (Apache-2.0 is compatible *into*
GPLv3), but that does not help: the result would have to ship under GPLv3, and
SchOS does not.
**Practical consequence:** BSD drivers can be adopted. Linux drivers cannot,
ever, no matter how convenient. This is why the USB stack was written from the
specification rather than ported.
**"Cannot be adopted" is not the same as "cannot be run."** A driver the *user*
obtains and loads on their own machine is their act, not a distribution by us —
see §4 and [plan/ldls.md](plan/ldls.md), which is the layer built on exactly
that distinction and which ships no Linux code of any kind.
---
## 3. NetBSD in particular
`src-trunk/` is a NetBSD checkout kept as reference material and a parts bin
(see [plan/schapi.md](plan/schapi.md) §5). NetBSD is **not
uniformly licensed**, and the difference matters per file:
| Template | Status |
|---|---|
| 2-clause BSD — modern NetBSD Foundation files | **Fine** |
| 3-clause, "Regents of the University of California" | **Fine.** Berkeley formally rescinded the advertising clause in 1999 |
| 4-clause with the advertising clause, **non-UC** copyright holder | **Avoid.** Berkeley's rescission covers Berkeley's copyright, not other people's |
The last row is real, not hypothetical. Measured on this checkout:
```
sys/kern 26 of 220 files carry the advertising clause
sys/dev 500 of 3565
lib/libc 101 of 1575
sys/net 5 of 180 ← the TCP/IP stack is nearly clean
```
4-clause is still permissive and still redistributable; the burden is an
advertising obligation that accumulates quietly and is tedious to honour
properly. Prefer a 2-clause or UC-Regents file. If a 4-clause file is genuinely
the right answer, it goes into `NOTICE` with the full clause quoted.
Check before copying, not after:
```sh
grep -l "All advertising materials" <file>
```
### And check *whose* clause it is
A file can carry several licence blocks from several copyright holders. Finding
the advertising clause is only half the check — Berkeley's rescission covers
Berkeley's copyright and nobody else's, so the question is always *whose*
clause you found.
Measured on `src-trunk`, the networking tree:
```
sys/net 106 files, 5 with the advertising clause
sys/netinet 121 files, 25 " " "
sys/netinet6 51 files, 5 " " "
```
Encouraging at first glance — the 25 in `sys/netinet` are almost all SCTP,
DCCP and multicast routing, none of which SchOS needs. `ip_input.c`,
`ip_output.c`, `udp_usrreq.c`, `if_arp.c`, `ip_icmp.c`, `if.c` and `route.c`
are all UC 3-clause, i.e. fine.
**But `tcp_input.c` and `tcp_output.c` are not.** Their advertising clause
belongs to the **WIDE Project** (the KAME IPv6 people), whose copyright sits at
the top of the file; the UC block is a separate licence further down. Berkeley
never spoke for WIDE, so the rescission does not reach it. The two most
valuable files in the stack are the two encumbered ones.
That is not fatal — 4-clause is still permissive — but it is exactly the case
where a **second BSD tree earns its disk space**: FreeBSD's TCP has been
through a relicensing effort NetBSD's has not, so the same file there may well
be 2-clause. Check it the same way before assuming either:
```sh
grep -n "Copyright" <file> | head # who claims it
grep -n -B6 "All advertising" <file> # whose clause is it
```
### FreeBSD's TCP is clean where NetBSD's is not
Checked, 2026-08-08, exactly that way. Against FreeBSD `main`:
`sys/netinet/tcp_input.c` contains **no advertising clause at all**. Its
copyright holders are the UC Regents, Swinburne University, Lawrence Stewart,
the FreeBSD Foundation and Juniper Networks -- all BSD-2 or BSD-3.
So if BSD networking code is taken, it must come from **FreeBSD, not NetBSD**,
and the specific reason is TCP. This reverses the earlier assumption in
`docs/plan/roadmap.md` that NetBSD was the intended starting point; NetBSD remains
the better-written reference to *read*, but FreeBSD is the one that can be
copied from.
### The third option: lwIP
Before porting either BSD stack, weigh **lwIP** against both. Verified
2026-08-08 from its `COPYING`: 3-clause BSD, Swedish Institute of Computer
Science, **no advertising clause anywhere in the file**. Compatible, one NOTICE
entry, done.
The licence is the smaller half of the argument. The larger half is that
FreeBSD's `sys/netinet` is not a library -- `tcp_input.c` alone is 122 KB and
reaches into mbufs, `epoch(9)`, netisr, VNET, `uma(9)`, sockbufs, inpcb hash
tables and FreeBSD's locking. Taking it means porting FreeBSD's kernel
infrastructure first and finding the TCP somewhere underneath, which is the same
conclusion `docs/guides/adding-a-driver.md` already reached about `sys/dev/usb`.
lwIP was written for the case SchOS is in: a small operating system with a raw
Ethernet driver and no infrastructure to speak of. Its port layer is a handful
of files, it has no dependency on the host kernel's allocator or locking, and it
brings ARP, IPv4, ICMP, UDP, TCP, DHCP and DNS.
### Fonts
Chosen 2026-08-09, both licences checked at source rather than assumed:
| Font | Where | Licence |
|---|---|---|
| Roboto | desktop, window manager, compositor | **Apache-2.0** |
| JetBrains Mono | terminal, TTY | **SIL OFL 1.1** |
Roboto is the same licence as SchOS, so it is the easy case: a NOTICE entry
and nothing else.
OFL is permissive and bundling is precisely what it is for, with two conditions
worth knowing before anyone subsets or converts a font:
- **The font stays under OFL** even inside an Apache-2.0 system. It does not
infect anything else; it just cannot be relicensed.
- **It may not be sold on its own.** Shipping it as part of an OS is fine.
- **Reserved Font Names.** JetBrains Mono declares *none* -- its copyright line
names no reserved name -- so a modified version may keep the name. Most OFL
fonts do reserve theirs, and then a derivative must be renamed.
Pre-rasterising a font into bitmaps at build time **is** a modification, so
this is not a theoretical point for this project.
---
## 4. Things that are deliberately not shipped
Some code is fine to *build and run locally* and impossible to *distribute*
with SchOS. Keeping the distinction sharp is what stops it eroding:
- **`Refrence/`** — a FreeBSD checkout (`Refrence/freebsd-src`), kept as
reference material and a parts bin. Never built, never in an image. The
directory name is spelled as it is on disk; renaming it is a one-line change
if it ever grates.
- **`src-trunk/`** — a NetBSD checkout, same rules. Currently absent; it was
deleted for disk space and can be re-fetched sparsely if needed.
- **`dist/`** — upstream tarballs and other downloaded blobs. Cached so a
rebuild never needs the network; not part of any image, and not all of it is
redistributable.
The mechanisms that keep this from eroding, since good intentions have already
failed once here:
- `NOTPORTS` in `ports/Makefile` — a port that lives in the tree but is never
built by `make`.
- `RAMDISK_EXCLUDE` in the root `Makefile` — paths kept out of the ramdisk root.
This one matters more than it looks: the ramdisk image is linked *into the
kernel binary*, so anything sitting in `sysroot/` when that image is built
becomes part of a distributable artifact. A GPL-2.0 or non-redistributable
file staged there does not merely ride along, it makes the kernel itself
undistributable.
A previous acceptance test — a ported 1993 shooter, GPL-2.0 engine on
non-redistributable shareware data — was carried under exactly these rules and
has since been removed. It is worth recording *why* it was safe: it was never in
`PORTS`, and its 33 MB of data was excluded from the ramdisk. It is equally
worth recording that the safety depended on two separate opt-outs staying
correct, which is a lot of standing risk for a test that had stopped finding
bugs. See `docs/guides/testing.md`.
---
## 5. Specifications are not code
A published specification carries no copyright over an independent
implementation of it. The xHCI, USB, PCI, fbdev and ELF specifications were all
read and implemented directly, which is why those drivers have no licence
entanglement at all despite equivalents existing in both Linux and BSD.
When a subsystem is small enough to write from the spec, that is usually the
cheapest option in the long run — not because copying is hard, but because a
copied file is a file to be understood, attributed and maintained forever.
---
## 6. LDLS, and the distribution boundary
[plan/ldls.md](plan/ldls.md) runs Linux drivers. That sounds like it should
contradict §2, and it does not, because the two questions below are different
ones and only the first is about us.
### LDLS is Apache-2.0
Every file under `ldls/` is ours. The loader was written from the ELF-64 gABI
and the x86-64 psABI; the shim is written under the rule in
[plan/ldls.md](plan/ldls.md) §3. **No Linux code is in it and none ever will
be**, which is the same standard the xHCI and USB stacks already meet (§5).
### The combination is not ours to make
| | licence | who distributes it |
|---|---|---|
| `ldls/` — loader, shim, host | **Apache-2.0** | us |
| a `.ko` | GPL-2.0-only, usually | the user's distribution |
| the two running together in a process | — | **nobody** |
GPLv2 §0 is explicit about where its obligations attach:
> Activities other than copying, distribution and modification are not covered
> by this License; they are outside its scope. **The act of running the Program
> is not restricted.**
So: we ship Apache-2.0 code containing no GPL. The user obtains a GPL module
from wherever they get their software. The user runs them together on their own
machine. No combined work is distributed by anyone, and no GPL obligation is
triggered — not on us, and not on them.
This is the position FreeBSD has shipped from for about a decade: a
BSD-licensed `linuxkpi` in base, GPL `drm-kmod` installed by the user from
ports.
**The consequence for the user, stated plainly:** they cannot redistribute the
combination either. An image with a `.ko` baked into it is a single work that
would have to satisfy Apache-2.0 and GPL-2.0-only at once, which §2 explains is
impossible. That is why `RAMDISK_EXCLUDE` exists, and why `.gitignore` refuses
`*.ko` outright — the boundary is kept mechanically rather than by intention.
### `EXPORT_SYMBOL_GPL` does not apply
Linux marks much of its internal API `EXPORT_SYMBOL_GPL`, and modules carry
`MODULE_LICENSE`. Supplying those symbols while asserting a licence one does
not hold would be circumvention, and it is worth saying why LDLS is not doing
that.
**LDLS has no Linux symbols to gate.** `EXPORT_SYMBOL_GPL` is Linux marking
*Linux's own* symbols, and the check it drives protects those. Every symbol
LDLS provides is ours — reimplemented from facts, under Apache-2.0. There is no
gate being bypassed, because there is no Linux symbol being exposed.
So LDLS implements no licence check. It **reads** the module's declared licence
and prints it before loading anything, because that is the one property that
decides whether an object should be on a machine at all and it costs nothing to
show.
### BTF: the layouts are not in our source at all
§3 of the plan has one uncomfortable row — struct layouts. A compiled driver
indexes fields by offset, so the shim has to reproduce them exactly, and a
field-for-field match *looks* like transcription even when it is not.
It does not have to be in our source. Kernels built with `CONFIG_DEBUG_INFO_BTF`
— which is most distribution kernels — publish `/sys/kernel/btf/vmlinux`, and
that file carries both halves of what the shim needs:
```
struct pci_dev {
struct list_head bus_list; /* 0 16 */
struct pci_bus * bus; /* 16 8 */
...
[97814] FUNC 'pci_iomap' type_id=97813 linkage=static
[97815] FUNC_PROTO '(anon)' ret_type_id=108 vlen=4
```
Layouts **and** prototypes, as generated binary type data describing the kernel
the user already has installed. LDLS can read them at load time, on the user's
machine, from the user's own kernel — so our source contains neither, and there
is nothing to have transcribed.
Three things improve together: the provenance question largely disappears, the
version-pinning problem (plan §4) mostly dissolves because BTF describes the
kernel that is actually there, and it is less work than writing hundreds of
struct definitions by hand.
**What BTF does not give is behaviour.** It says `napi_complete_done` takes
these arguments and returns that type. What it *does* is still ours to
implement from documentation, and that is the real work — BTF does not touch
it.