-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathexport-kernel-tree.sh
More file actions
executable file
·1869 lines (1643 loc) · 96.6 KB
/
Copy pathexport-kernel-tree.sh
File metadata and controls
executable file
·1869 lines (1643 loc) · 96.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bash
#
# export-kernel-tree.sh — render the carried kernel series as a Linux-Kernel_MiSTer-style
# git tree: a pristine upstream tarball as one base commit, then one commit per patch.
#
# WHY
# ---
# This repo keeps the MiSTer kernel as {pinned upstream version + hash} + an ordered
# patch series. MiSTer-devel/Linux-Kernel_MiSTer keeps it as a materialized git tree:
# a squashed tarball commit (`v5.15.1`) with MiSTer commits replayed on top. Those are
# the SAME MODEL — tarball base plus ordered series — differing only in whether the
# base is stored as a hash or as 283MB of blobs. This script renders one into the other.
#
# It exists so the rendered tree is a BUILD OUTPUT, not a second source of truth. Edits
# belong in the patch series; this regenerates from it. Given the same inputs it emits
# byte-identical commits (see DETERMINISM), so re-running after no change is a no-op
# rather than a force-push of fresh SHAs.
#
# TWO SERIES: WHAT WE SHIP vs WHAT THIS TREE CARRIES
# --------------------------------------------------
# For most of this script's life those were the same set, and EXPORT.md said so: the
# exported tree WAS the shipped kernel, patch for patch. That is no longer true, and the
# difference is deliberate rather than drift.
#
# Linux-Kernel_MiSTer is upstream's kernel for every MiSTer, not just ours. Some of what
# it must carry, our image specifically does not want. The motivating case is the fork's
# `loop=` boot parameter (fork commit 3d95de58f, "Support for init loop device."), which
# patches init/do_mounts.c so the KERNEL itself mounts /media/fat and loop-mounts
# linux/linux.img as the root filesystem. That is upstream's boot mechanism — every stock
# MiSTer boots through it, and a 6.18 branch that dropped it would not boot on any of
# them. Our image replaced it with a real initramfs /init, so applying it here would add
# an unreachable second boot path to the kernel we ship (recorded as carried-upstream-only
# in docs/kernel-recon/reconciliation.md — carried for this tree, not for our image).
#
# Deleting it from the export to keep the two trees identical would be the wrong trade:
# it would break upstream's boot to preserve a documentation claim. Applying it to our
# image would be the other wrong trade. So there are two series:
#
# board/mister/de10nano/linux-patches/ carried — applied by BOTH Buildroot
# (BR2_LINUX_KERNEL_PATCH) and this
# script. The kernel we ship.
# board/mister/de10nano/linux-patches-upstream/ upstream-only — applied ONLY here.
# Buildroot never sees this directory.
#
# The second directory's path is DERIVED from the first ("${patch_dir}-upstream"), so a
# defconfig change moves both together and this script needs no edit. Numbering there
# starts at 0100 so a filename alone says which namespace it is in.
#
# The cost of the split is that EXPORT.md can no longer say "this tree is the shipped
# kernel". It must say what it now is: the shipped kernel PLUS exactly these N patches,
# each named, each with the reason it is not in our image. That is generated below from
# the files actually present — never hardcoded — and the export FAILS CLOSED if a patch
# in that directory has no stated reason, because a table row with a blank reason is
# worse than no table: it reads as reviewed when nothing reviewed it.
#
# WHAT YOU GET
# ------------
# <output>/ a fresh git repo, branch MiSTer-v<major.minor>, containing:
# - one base commit "Linux <ver>" — pristine upstream, hash-verified
# - one commit per carried patch, original authorship preserved
# - one commit per upstream-only patch, likewise (see TWO SERIES above)
# - arch/arm/configs/MiSTer_defconfig — so the tree builds standalone:
# make ARCH=arm MiSTer_defconfig && make ARCH=arm zImage
# which is the thing `make linux` inside Buildroot cannot hand someone.
# - EXPORT.md — states it is generated, names the source of truth, and records
# the fork commit we last reconciled against.
# - tag mister-<ver>
#
# WHERE THE BRANCH HANGS (--parent-repo/--parent)
# -----------------------------------------------
# Linux-Kernel_MiSTer is not one chain. Its tarball commits form a SPINE —
#
# e12ed6c19 v5.13.12 -> 137491a75 v5.14 -> b6f2ca1c4 v5.14.5 -> aba1ef4c1 v5.15.1
# |
# d9ac12a69 v6.18.38
#
# — and each MiSTer-vX.Y branch hangs off a spine point with the MiSTer series replayed
# on top (MiSTer-v5.15 = aba1ef4c1 + 112 commits). Every spine commit is a PRISTINE
# tarball with no MiSTer code in it, INCLUDING the newest one: upstream's own
# MiSTer-v6.18 work starts from d9ac12a69 "v6.18.38", a pristine 6.18.38 tarball commit
# whose parent is aba1ef4c1. A spine point is therefore not necessarily a leaf, and
# --parent must accept one that already has a branch hanging off it.
#
# So the right shape for a new kernel is to extend the spine the same way, parenting the
# base commit on the NEWEST spine point rather than on a branch tip. For 6.18 that means
# --parent d9ac12a691ead295c8bc6438754767b94c0f26a2, not aba1ef4c1:
#
# aba1ef4c1 v5.15.1 --+-- [112 MiSTer commits] --> MiSTer-v5.15 (theirs, untouched)
# |
# +-- d9ac12a69 v6.18.38 --+-- [his commits] -> MiSTer-v6.18 (theirs)
# |
# +-- v6.18.50 -- [our commits] -> ours
#
# That buys three things at once:
# - shared ancestry with MiSTer-v5.15 AND with upstream's own MiSTer-v6.18, so GitHub
# can compare and a PR is possible at all (across unrelated histories the compare
# API 404s: "No common ancestor");
# - a log with NO MiSTer commits of theirs in it — they are siblings, not ancestors —
# so nothing lists a change that is absent from the tree. Parenting on the branch TIP
# instead would list their commits whose changes this tree discards, and a reader
# would see "xone: update driver" and conclude xone is present when it is a
# Buildroot package now;
# - a base commit whose diff against its parent is PURE upstream — 6.18.38 -> 6.18.50
# stable, nothing else — because both trees are pristine tarballs. Parenting on
# aba1ef4c1 instead would still work, but that diff would then be the whole
# 5.15.1 -> 6.18.50 delta and useless for review.
#
# Their branch is never touched: it becomes a sibling, exactly as MiSTer-v5.14 already
# is. What each of its commits became — carried, superseded upstream, or dropped — is
# recorded in MISTER-KERNEL-PATCH-RECON.md, which cites the superseding vanilla commit.
# No git command can answer that: across this much context drift `git patch-id` matches
# nothing, so "is this commit in 6.18?" is semantic, not mechanical.
#
# Without --parent-repo the base commit is a root commit and the branch is an orphan —
# fine for a standalone tree, but it cannot be PR'd anywhere.
#
# This script NEVER touches a fork or a remote. To publish, fetch the orphan branch
# into a fork and push from there (see EXPORT.md, which spells out the two commands).
#
# DETERMINISM
# -----------
# Reproducibility comes from two choices:
# - `git am --committer-date-is-author-date`, so committer dates come from the
# patches rather than from the clock;
# - the base commit's date is the extracted Makefile's mtime. kernel.org tarballs are
# produced with `git archive`, so every file carries the tag's commit time — stable
# across machines and meaningful, unlike download time. Override with
# SOURCE_DATE_EPOCH.
#
# Usage: scripts/export-kernel-tree.sh --output DIR [--parent-repo R --parent C]
# [--onto COMMIT] [--fork-sync SHA] [--tarball FILE]
# [--upstream-patches DIR] [--no-upstream-patches]
#
# --output DIR where to build the tree (must not already exist)
# --parent-repo R clone R and work inside it, rather than starting a fresh root
# --parent C spine commit to extend; a base commit is created on top of it
# from the pinned tarball (requires --parent-repo). Use the NEWEST
# pristine tarball commit on the spine -- for 6.18 that is
# d9ac12a691ead295c8bc6438754767b94c0f26a2 ("v6.18.38"), which
# already has upstream's own MiSTer-v6.18 hanging off it; parenting
# there is what makes the base commit's diff pure stable 6.18.38 ->
# the pinned version.
# --onto COMMIT replay onto COMMIT, which must ALREADY BE the pinned kernel
# version -- no base commit is created and the tarball is not
# used for the kernel. Use when upstream has published its own
# vanilla base to PR against; the result fast-forwards onto it.
# Mutually exclusive with --parent (requires --parent-repo).
# --fork-sync SHA fork commit this export was reconciled against; recorded in
# EXPORT.md as the backport-queue starting point
# --tarball FILE use this tarball instead of the dl/ cache or a download
# --upstream-patches DIR
# the upstream-only series to replay after the carried one, instead
# of the derived default "<BR2_LINUX_KERNEL_PATCH>-upstream". These
# patches are NOT in the shipped image (see TWO SERIES above). An
# explicitly named directory must exist and be non-empty; the DERIVED
# one may be absent or empty, which simply means there are none.
# --no-upstream-patches
# skip the upstream-only series entirely. The result is exactly the
# kernel the image ships — useful for diffing this tree against a
# Buildroot build, where the extra patches are the only expected
# difference and so make the comparison useless. Not what you want
# for a tree you intend to publish. Mutually exclusive with
# --upstream-patches.
#
# Exit: 0 = tree built and verified; non-zero = anything failed (fails closed).
set -o errexit
set -o nounset
set -o pipefail
# Assigned then marked readonly separately: `readonly X="$(cmd)"` masks cmd's exit status
# (shellcheck SC2155), and the rest of scripts/ avoids that pattern.
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
readonly REPO_ROOT
readonly HASH_FILE="$REPO_ROOT/board/mister/de10nano/patches/linux/linux.hash"
# WHICH FRAGMENTS THIS SCRIPT READS -- and why it is a STACK, not one file
# -----------------------------------------------------------------------
# This used to be `DEFCONFIG=configs/fragments/de10nano.fragment`, a single file, and
# that was correct only for as long as one file held every symbol. The 2026-09 fragment
# split ended that: configs/fragments/stacks.mk now composes the de10nano image from
# `common de10nano image-common de10nano-image`, and the kernel-module package selections
# (BR2_PACKAGE_XONE, BR2_PACKAGE_RTL8852CU_MORROWNR) moved into de10nano-image.fragment
# while the kernel pin stayed in de10nano.fragment.
#
# Reading one file after that split is not a partial answer, it is a WRONG one, and it
# fails in the worst possible direction: section 6b greps for BR2_PACKAGE_*=y, would have
# found NONE of them in de10nano.fragment, and its "detected zero kernel-module packages"
# guard would have aborted every export -- or, had that guard not existed, silently
# shipped a tree with no Xbox controller and no WiFi. So the fragment list comes from
# stacks.mk, which docs/buildroot-config.md §1 names as the single source of truth for
# what a configuration is made of, parsed through the same helper the other checks use.
# A future fragment move then needs no edit here.
#
# DE10NANO (the IMAGE stack), not DE10NANO_KERNEL: the exported tree is the kernel the
# MiSTer image ships, and the kernel-only stack deliberately selects no packages at all
# (stacks.mk: `image-common` is in every image stack and no kernel-only one), so reading
# it would reintroduce exactly the zero-drivers bug from the other direction.
# Committer identity for the generated commits. Patch AUTHORS are preserved by `git am`;
# this only says who mechanically produced the tree, and it must be explicit so the
# script works on a runner with no git config.
readonly EXPORT_NAME="${EXPORT_COMMITTER_NAME:-MiSTer Buildroot export}"
readonly EXPORT_EMAIL="${EXPORT_COMMITTER_EMAIL:-export@mister-devel.invalid}"
die() { printf 'export-kernel-tree: %s\n' "$*" >&2; exit 1; }
say() { printf '\n=== %s\n' "$*"; }
# Resolved AFTER die(), which it uses. STACK_FILES is the merge-ordered list of fragment
# files that make up the exported board's configuration; every read of a pinned value
# below goes through all of them, last definition winning, exactly as kconfig would.
STACK_FILES=("$REPO_ROOT/configs/mister_de10nano_defconfig")
readonly STACK_FILES
[ -f "${STACK_FILES[0]}" ] || die "no ${STACK_FILES[0]} -- the DE10 defconfig is the single source of the kernel pin (ADR 0030)"
unset _frag
# Only set when we download rather than use the dl/ cache. Cleaned on exit: it holds a
# ~150MB kernel tarball, so leaking it on every run is not a rounding error. --output is
# deliberately NOT touched here -- it is the deliverable, and it must survive a failure
# for the failure to be diagnosable.
#
# cleanup() uses `if` rather than `[[ ... ]] && rm`: as an EXIT trap, the function's own
# return status becomes the script's exit status, and a bare `[[ -n $download_dir ]]`
# returns 1 whenever nothing was downloaded -- making every successful cache-hit run exit
# 1 despite printing PASS.
# scratch_dir holds `git mailinfo` output while we read the upstream-only patches'
# Subject: lines; it is tiny but there is no reason to leak one per run.
download_dir=''
scratch_dir=''
cleanup() {
if [[ -n $download_dir ]]; then
rm -rf "$download_dir"
fi
if [[ -n $scratch_dir ]]; then
rm -rf "$scratch_dir"
fi
}
trap cleanup EXIT
output=''
fork_sync=''
tarball_override=''
parent_repo=''
parent=''
onto=''
upstream_patch_dir=''
skip_upstream=false
# Filled in from the parent commit itself when --parent is used (section 3); EXPORT.md's
# spine section is generated from them rather than naming a spine point that upstream has
# since moved past.
parent_short=''
parent_subject=''
while (($#)); do
case "$1" in
--output) output="${2:-}"; shift 2 ;;
--parent-repo) parent_repo="${2:-}"; shift 2 ;;
--parent) parent="${2:-}"; shift 2 ;;
--onto) onto="${2:-}"; shift 2 ;;
--fork-sync) fork_sync="${2:-}"; shift 2 ;;
--tarball) tarball_override="${2:-}"; shift 2 ;;
--upstream-patches) upstream_patch_dir="${2:-}"; shift 2 ;;
--no-upstream-patches) skip_upstream=true; shift ;;
# `q` on the closing line, not a bare range. A sed range RE-ARMS after it closes, and
# this file contains a SECOND "# Usage:" -- the one in the build-mister-modules.sh
# heredoc emitted in section 6c. Without the quit, the range reopened there, found no
# second "# Exit:", and ran to end of file: --help printed ~300 lines of this script's
# own source after the banner. Quitting at the first "# Exit:" prints the banner and
# only the banner, regardless of what later sections contain.
-h | --help)
sed -n '/^# Usage:/,/^# Exit:/{p;/^# Exit:/q;}' "${BASH_SOURCE[0]}" |
sed 's/^# \?//'
exit 0
;;
*) die "unknown argument: $1 (try --help)" ;;
esac
done
[[ -n $output ]] || die 'missing --output DIR (try --help)'
[[ ! -e $output ]] || die "--output already exists: $output"
# A parent is meaningless without the repo it lives in, and cloning a repo without saying
# where to hang the branch would silently fall back to an orphan.
[[ -n $parent_repo && -z $parent && -z $onto ]] && die '--parent-repo requires --parent or --onto'
[[ -n $parent && -z $parent_repo ]] && die '--parent requires --parent-repo'
[[ -n $onto && -z $parent_repo ]] && die '--onto requires --parent-repo'
[[ -n $parent && -n $onto ]] && die '--parent and --onto are mutually exclusive:
--parent extends a spine and CREATES a base commit from the tarball; --onto replays onto
a base that already exists. Pick one.'
# Naming a directory and then asking for it to be skipped is not a resolvable intent, and
# guessing either way would silently produce a tree the caller did not ask for -- one of
# which (the skipped one) is missing upstream's boot path.
[[ -n $upstream_patch_dir ]] && $skip_upstream &&
die '--upstream-patches and --no-upstream-patches are mutually exclusive.'
# --- 1. Read the pinned inputs out of the config fragments -----------------------------
# The fragment stack is the single source of truth for what we build; nothing here is
# hardcoded, so a version bump is a one-line fragment edit and this script follows.
defconfig_value() {
# Values look like: BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.38"
#
# Do NOT anchor on the closing quote. These fragments carry trailing comments on
# some lines, and anchoring silently yields an empty value rather than failing --
# which for an optional setting (a config fragment) would mean quietly dropping it.
#
# All stack files in MERGE ORDER, `tail -1` last: that is what kconfig does when a
# later fragment redefines a symbol an earlier one set, so a value moved or overridden
# across the 2026-09 split still resolves to the one the image is built with.
sed -n "s/^$1=\"\([^\"]*\)\".*$/\1/p" "${STACK_FILES[@]}" | tail -1
}
# Buildroot spells the external tree's own path as a make variable inside the defconfig;
# resolve it the way Buildroot would.
resolve_br_path() {
printf '%s' "${1//\$(BR2_EXTERNAL_MISTER_PATH)/$REPO_ROOT}"
}
version="$(defconfig_value BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE)"
[[ -n $version ]] ||
die "BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE not set in the DE10 defconfig:
${STACK_FILES[*]}"
patch_dir="$(resolve_br_path "$(defconfig_value BR2_LINUX_KERNEL_PATCH)")"
config_file="$(resolve_br_path "$(defconfig_value BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE)")"
fragments="$(resolve_br_path "$(defconfig_value BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES)")"
[[ -d $patch_dir ]] || die "patch dir not found: $patch_dir"
[[ -f $config_file ]] || die "kernel config not found: $config_file"
# nullglob, or an empty patch dir yields an array holding the literal "*.patch" pattern
# -- length 1, so the guard below passes -- and `git am` then fails on a path that does
# not exist, blaming the patch rather than the empty directory.
shopt -s nullglob
series=("$patch_dir"/*.patch)
shopt -u nullglob
((${#series[@]})) || die "no patches in $patch_dir"
# --- 1b. The upstream-only series ------------------------------------------------------
# Patches this tree carries that the shipped image deliberately does not. See TWO SERIES
# at the top of this file for why that divergence exists and why it is not drift.
#
# The default path is DERIVED from the carried series rather than written out, for the
# same reason nothing else here is hardcoded: BR2_LINUX_KERNEL_PATCH is the one place
# that says where kernel patches live, and a second hardcoded copy of that path would go
# stale the first time the defconfig moved -- silently, by finding no directory and
# exporting a tree with upstream's boot mechanism quietly missing from it.
upstream_explicit=false
if [[ -n $upstream_patch_dir ]]; then
upstream_explicit=true
# Absolutize NOW, before anything globs it. The glob below runs in the invocation
# cwd, but `git am` runs after the `cd "$output"` in section 4 -- so a RELATIVE
# --upstream-patches yields relative paths that are unopenable by the time they are
# applied, and git am's failure is reported as patch rot. That sends the operator off
# to rebase a patch that was never broken. The carried series is immune only by
# accident: it goes through resolve_br_path(), which substitutes an absolute
# $REPO_ROOT. Left `-d`-guarded so a missing directory still hits the typo-vs-empty
# die below rather than failing here with a worse message.
if [[ -d $upstream_patch_dir ]]; then
upstream_patch_dir="$(cd "$upstream_patch_dir" && pwd)" ||
die "--upstream-patches: cannot resolve directory: $upstream_patch_dir"
fi
else
upstream_patch_dir="${patch_dir}-upstream"
fi
upstream_series=()
if $skip_upstream; then
say 'Skipping the upstream-only series (--no-upstream-patches)'
upstream_patch_dir=''
elif [[ -d $upstream_patch_dir ]]; then
shopt -s nullglob
upstream_series=("$upstream_patch_dir"/*.patch)
shopt -u nullglob
elif $upstream_explicit; then
# An explicitly named directory that is not there is a typo, not an empty series.
# Treating it as empty would export a tree missing exactly the patches the caller
# went out of their way to ask for, and report PASS.
die "--upstream-patches: no such directory: $upstream_patch_dir"
fi
# Same rule for an explicitly named directory that exists but holds nothing: the caller
# asked for a series, so producing none is a failure. The DERIVED directory is different
# -- absent or empty there legitimately means "there are no upstream-only patches", which
# is the state this repo was in before the loop= patch existed.
$upstream_explicit && ((${#upstream_series[@]} == 0)) &&
die "--upstream-patches: no *.patch files in $upstream_patch_dir"
# Read each upstream-only patch's Subject: and its reason for not being in the image, up
# front, BEFORE the tarball download and the whole series replay. A missing reason is a
# hard failure (see below), and discovering that after ten minutes of work would train
# people to skip the export rather than fix the patch.
#
# `git mailinfo` is used rather than a regex because it is the parser `git am` itself
# uses: it strips the "[PATCH 1/1] " prefix, unfolds continuation lines and decodes
# RFC2047-encoded headers, so the table below shows the same subject the commit will
# actually carry. scripts/lint-kernel-patches.sh checks the same thing in CI.
readonly NOT_IN_IMAGE_FILE='not-in-image'
upstream_subjects=()
upstream_reasons=()
if ((${#upstream_series[@]})); then
scratch_dir="$(mktemp -d "${TMPDIR:-/tmp}/export-kernel-tree-meta.XXXXXX")" ||
die 'could not create a temporary directory'
for up_patch in "${upstream_series[@]}"; do
up_name="$(basename "$up_patch")"
up_info="$(git mailinfo "$scratch_dir/msg" "$scratch_dir/patch" <"$up_patch" 2>/dev/null)" ||
die "git mailinfo could not parse $up_name. Run scripts/lint-kernel-patches.sh."
# The exit status above is NOT the signal for a malformed identity, and relying on
# it would leave the fast gate half-open. `git mailinfo` exits 0 while leaving
# Author/Email EMPTY for a `From:` it cannot parse -- which is why
# scripts/lint-kernel-patches.sh checks the fields rather than the status, and why
# that script exists at all: this repo shipped exactly that defect once
# (0013-hid-flydigi-vader.patch carried `From: Alexey Melnikov` with no <email>).
#
# `git am` hard-fails on it later with "fatal: empty ident name (for <>) not
# allowed" -- but "later" here means after the tarball download, the hash verify,
# the clone, the extract and a 31-patch replay, and the failure arrives wearing the
# generic series-replay error instead of naming the file and the line. Checking all
# three fields the same way the lint does keeps this gate honest: everything `git am`
# needs from the headers is validated before any expensive work starts.
#
# DUPLICATED ON PURPOSE -- KEEP IN SYNC WITH scripts/lint-kernel-patches.sh
# --------------------------------------------------------------------------
# The non-empty Author/Email/Subject criteria below are the same three checks
# lint-kernel-patches.sh makes (see the `problems+=(...)` block there). They are
# duplicated rather than shared because the two scripts have no common library and
# sourcing one from the other would couple a CI-only linter to the export's runtime.
# That is a deliberate trade, not an oversight: the cost is that a change to the
# criteria HERE must be mirrored THERE, or CI and the export start disagreeing about
# what a valid patch header is -- and the failure mode is a green lint followed by a
# failed export, which is the exact confusion this gate exists to prevent.
# If a third caller ever needs these checks, factor all three into a shared helper
# instead of adding another copy.
up_subject="$(sed -n 's/^Subject: //p' <<<"$up_info")"
up_author="$(sed -n 's/^Author: //p' <<<"$up_info")"
up_email="$(sed -n 's/^Email: //p' <<<"$up_info")"
[[ -n $up_subject ]] ||
die "no Subject: in $up_name — it would appear as a blank row in EXPORT.md's
upstream-only table, and 'git am' would have no commit message to write."
[[ -n $up_author && -n $up_email ]] ||
die "unparseable From: in $up_name — 'git am' needs \`Name <email>\` to write a
commit and dies with \"fatal: empty ident name (for <>) not allowed\".
got: $(grep -m1 '^From:' "$up_patch" || echo '(no From: line at all)')
Run scripts/lint-kernel-patches.sh, which checks both series the same way."
# WHY A REASON IS MANDATORY
# -------------------------
# EXPORT.md tells upstream reviewers, in a table, which patches are in this tree
# but not in the MiSTer image and why. A row with an empty reason is worse than
# no table at all: it has the shape of a reviewed decision without being one, and
# it is exactly the kind of claim that goes unchallenged for years. So the reason
# is an input to the export, not prose someone remembers to add afterwards.
#
# Two places it can come from, in this order:
#
# 1. a `Not-in-image:` line in the patch's own commit message — preferred,
# because it travels with the patch through rebases and re-exports;
# 2. a row in the series directory's `not-in-image` file, keyed by filename —
# for patches imported VERBATIM from the fork, where editing the commit
# message would mean rewriting someone else's commit text just to satisfy
# a tool of ours.
#
# Read from the mailinfo-split message body, not the raw file: grepping the raw
# patch would also match a `Not-in-image:` string inside a diff hunk.
up_reason="$(sed -n 's/^Not-in-image:[[:space:]]*//p' "$scratch_dir/msg" | head -1)"
if [[ -z $up_reason && -f "$upstream_patch_dir/$NOT_IN_IMAGE_FILE" ]]; then
# awk with an exact first-field match rather than sed: the key is a
# filename full of '.' and '-', which sed would read as a regex, and a
# near-miss would silently match the wrong row. Exact equality cannot.
# It also skips '#' comment lines for free -- their first field is the
# comment, which is never a patch filename.
up_reason="$(awk -v key="$up_name" \
'$1 == key { $1 = ""; sub(/^[[:space:]]+/, ""); print; exit }' \
"$upstream_patch_dir/$NOT_IN_IMAGE_FILE")"
fi
[[ -n $up_reason ]] || die "no stated reason why $up_name is absent from the MiSTer image.
Every patch in $upstream_patch_dir is carried for the exported
tree ONLY, and EXPORT.md publishes a table naming each one and why the image does not
apply it. Refusing to emit that table with a blank row. Add either:
* a line 'Not-in-image: <one-line reason>' to the patch's commit message, or
* a row '$up_name <one-line reason>' to
$upstream_patch_dir/$NOT_IN_IMAGE_FILE"
upstream_subjects+=("$up_subject")
upstream_reasons+=("$up_reason")
done
fi
branch="MiSTer-v${version%.*}" # 6.18.38 -> MiSTer-v6.18, matching the fork's convention
tag="mister-${version}"
say "Exporting Linux $version + ${#series[@]} carried patches + ${#upstream_series[@]} upstream-only -> $output (branch $branch)"
# --- 2. Get the tarball, and verify it against the signed-manifest hash ----------------
# Fails closed: an unverified kernel tarball is the whole reason linux.hash exists.
tarball="$tarball_override"
if [[ -n $onto ]]; then
# --onto: the base already exists upstream, so the kernel tarball is not needed and
# no base commit is created. The safety property still has to hold, though -- replaying
# a 6.18 series onto, say, a 5.15 base must not be attempted -- so the version is read
# back out of the target commit's own Makefile below rather than trusted.
say "Replaying onto existing base $onto (no base commit created)"
elif [[ -z $tarball ]]; then
cached="$REPO_ROOT/dl/linux/linux-$version.tar.xz"
if [[ -f $cached ]]; then
tarball="$cached"
say "Using cached tarball: $tarball"
else
# Explicit template, matching scripts/ci-tests.sh and scripts/check-linux-img.sh:
# bare `mktemp -d` is a GNU extension and errors out on BSD/macOS mktemp, which
# wants one. (`-t` is not the answer either -- GNU deprecates it and BSD reads
# its argument as a prefix rather than a template.)
download_dir="$(mktemp -d "${TMPDIR:-/tmp}/export-kernel-tree.XXXXXX")" ||
die 'could not create a temporary download directory'
tarball="$download_dir/linux-$version.tar.xz"
url="https://cdn.kernel.org/pub/linux/kernel/v${version%%.*}.x/linux-$version.tar.xz"
say "Downloading $url"
curl --fail --location --silent --show-error --output "$tarball" "$url" ||
die "download failed: $url"
fi
fi
expected=''
if [[ -z $onto ]]; then
[[ -f $tarball ]] || die "no such tarball: $tarball"
expected="$(sed -n "s/^sha256[[:space:]]\+\([0-9a-f]\{64\}\)[[:space:]]\+linux-$version\.tar\.xz$/\1/p" "$HASH_FILE" | tail -1)"
[[ -n $expected ]] || die "no sha256 for linux-$version.tar.xz in $HASH_FILE — bump the hash from kernel.org's signed manifest"
actual="$(sha256sum "$tarball" | cut -d' ' -f1)"
[[ $actual == "$expected" ]] || die "tarball hash mismatch for linux-$version.tar.xz
expected $expected (from $HASH_FILE)
actual $actual"
say "Tarball verified: sha256 $actual"
fi
# --- 3. Extract ------------------------------------------------------------------------
if [[ -n $onto ]]; then
# Resolve the ref in the SOURCE repo and carry the SHA into the clone. Ref names are
# ambiguous across a clone boundary and it is not a theoretical problem: `git clone`
# copies the source's LOCAL branches to origin/*, so `--onto origin/MiSTer-v6.18`
# resolves inside the clone to the source's own local MiSTer-v6.18 -- a different
# commit from the origin/MiSTer-v6.18 the caller meant. That silently replayed a
# series onto a tree that already had it applied, and the version check could not
# catch it because both trees were the same Linux version.
onto="$(git -C "$parent_repo" rev-parse --verify --quiet "$onto^{commit}")" ||
die "--onto is not a commit in $parent_repo"
say "Resolved --onto to $onto in $parent_repo"
say "Cloning $parent_repo"
git clone --quiet --no-checkout "$parent_repo" "$output" || die "clone failed: $parent_repo"
git -C "$output" rev-parse --verify --quiet "$onto^{commit}" >/dev/null ||
die "$onto is not reachable in the clone of $parent_repo"
git -C "$output" checkout --quiet --detach "$onto"
# The base is someone else's, so verify it is the version we are about to patch
# rather than assuming. Read it from the target's own Makefile: replaying a 6.18
# series onto a 5.15 base would otherwise fail deep in `git am` with conflicts that
# look like bad patches instead of a bad base.
onto_version="$(sed -nE 's/^VERSION = //p;s/^PATCHLEVEL = /./p;s/^SUBLEVEL = /./p' \
"$output/Makefile" | head -3 | tr -d '\n')"
[[ $onto_version == "$version" ]] || die "--onto $onto is Linux $onto_version, but this
repo pins $version (BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE). Refusing to replay a $version
patch series onto a $onto_version base."
say "Base verified: $onto is Linux $onto_version"
elif [[ -n $parent_repo ]]; then
say "Cloning $parent_repo to extend its spine at $parent"
git clone --quiet --no-checkout "$parent_repo" "$output" || die "clone failed: $parent_repo"
git -C "$output" rev-parse --verify --quiet "$parent^{commit}" >/dev/null ||
die "--parent $parent is not a commit in $parent_repo"
# Identify the spine point for EXPORT.md, from the commit itself rather than from a
# hardcoded name. The newest spine point moves with upstream -- it was aba1ef4c1
# "v5.15.1", it is d9ac12a69 "v6.18.38" now -- and a document that names the wrong
# one sends a reviewer to compute the wrong diff. Short SHA is asked for at 9 digits
# to match the width the rest of this repo's prose uses for this fork.
parent_short="$(git -C "$output" rev-parse --short=9 "$parent^{commit}")"
parent_subject="$(git -C "$output" log --format=%s -1 "$parent^{commit}")"
# Detach at the spine point, then replace the worktree wholesale with the new
# tarball. `git add --all` stages the deletions and the additions together, so the
# resulting commit's tree is the pristine tarball and its parent is the spine.
git -C "$output" checkout --quiet --detach "$parent"
find "$output" -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} +
else
mkdir -p "$output"
fi
if [[ -z $onto ]]; then
say "Extracting"
tar -xf "$tarball" -C "$output" --strip-components=1
fi
# kernel.org tarballs come from `git archive`, so every file's mtime is the tag's commit
# time. That makes this stable across machines, unlike the download time.
if [[ -n ${SOURCE_DATE_EPOCH:-} ]]; then
base_epoch="$SOURCE_DATE_EPOCH"
elif [[ -n $onto ]]; then
# No tarball here, so take the base's own commit date. Still deterministic: it is a
# property of the commit we were pointed at, not of when this script ran.
base_epoch="$(git -C "$output" log --format='%ct' -1 "$onto")"
else
base_epoch="$(stat -c %Y "$output/Makefile")"
fi
base_date="$(date -u -d "@$base_epoch" '+%Y-%m-%dT%H:%M:%S+00:00')"
# --- 4. Base commit: pristine upstream, on its own ---------------------------------------
# Kept as its own commit so `git diff <base> HEAD` is exactly the MiSTer delta and
# nothing else — the review question worth answering.
cd "$output"
[[ -n $parent_repo ]] || git init --quiet --initial-branch="$branch"
git config user.name "$EXPORT_NAME"
git config user.email "$EXPORT_EMAIL"
git config commit.gpgsign false
if [[ -n $onto ]]; then
# The base commit is upstream's; ours would be a duplicate. Branch and go straight to
# the series, so the result fast-forwards onto their branch and the PR is exactly our
# delta -- nothing of theirs restated.
base_commit="$(git rev-parse HEAD)"
# -B, not -b. `git clone` copies the source repo's LOCAL branches, so as soon as the
# parent repo has its own MiSTer-v6.18 checked out -- which it does the moment anyone
# fetches a previous export back into it -- `-b` dies with "a branch named
# 'MiSTer-v6.18' already exists" after the clone and the base verification have already
# succeeded. That made the export's success depend on the parent repo's branch state
# rather than on its commits, so it passed the first time and failed forever after.
# Overwriting is right here and not destructive: $output is a throwaway clone this
# script just created, the ref being replaced is a COPY of the parent's, and the real
# publish step is an explicit fetch out of this directory (see EXPORT.md).
git checkout --quiet -B "$branch"
else
# Subject is bare "v6.18.38" to match the spine's existing convention (v5.13.12, v5.14,
# v5.14.5, v5.15.1) — the branch should read as the next entry, not a foreign import.
#
# --force is load-bearing, not defensive. The kernel ships .gitignore files that match
# paths it also tracks, so a plain `git add` after a tarball extract silently drops
# them. That is not hypothetical: it is exactly why this repo's own v5.15.1 base is NOT
# byte-identical to kernel.org's v5.15.1 — 11 files (Documentation/.yamllint,
# fs/*/.kunitconfig, selftests/bpf/test_progs.c, selftests/arm64/tags/* to the `tags`
# ctags pattern, ...) are simply absent from it. Without --force we would reproduce that
# bug here and lose Documentation/.renames.txt from 6.18.38.
git add --all --force
GIT_AUTHOR_DATE="$base_date" GIT_COMMITTER_DATE="$base_date" \
git commit --quiet --file=- <<EOF
v$version
Pristine upstream kernel $version, unpacked from linux-$version.tar.xz as
published on kernel.org.
sha256 $expected
Verified against the pinned hash in Buildroot_MiSTer, itself transcribed from
kernel.org's PGP-signed release manifest.
No MiSTer change is present in this commit -- it is upstream and nothing else,
exactly like the v5.13.12/v5.14/v5.14.5/v5.15.1 commits it follows. Every MiSTer
delta is a separate commit on top, so a diff from this commit to the tip of
$branch is precisely the MiSTer patch series.
$(if [[ -n $parent_repo ]]; then printf '%s\n' "
Because this commit's parent is a pristine tarball commit too, the diff against
that parent is the pure upstream delta, with no MiSTer code on either side."; fi)
Generated by scripts/export-kernel-tree.sh in Buildroot_MiSTer. Do not edit this
tree directly; see EXPORT.md.
EOF
base_commit="$(git rev-parse HEAD)"
# -B for the same reason as the --onto path above: the clone carries a copy of the parent
# repo's local branches, so -b fails once the parent has a branch of this name.
[[ -n $parent_repo ]] && git checkout --quiet -B "$branch"
fi
# --- 5. Replay the carried series -------------------------------------------------------
# --committer-date-is-author-date keeps this reproducible: dates come from the patches,
# not the clock, so an unchanged series regenerates to identical SHAs.
#
# Author identity comes from each patch's own From:, which scripts/lint-kernel-patches.sh
# guarantees is parseable — `git am` hard-fails the whole series on a malformed one.
say "Replaying ${#series[@]} patches with git am"
if ! git am --committer-date-is-author-date "${series[@]}" >/dev/null 2>&1; then
git am --abort 2>/dev/null || true
die "git am failed. Run scripts/lint-kernel-patches.sh first — a malformed From:
line fails the whole series. If the headers are fine, a patch does not apply to
$version and the series needs rebasing onto it."
fi
applied="$(git rev-list --count "$base_commit"..HEAD)"
((applied == ${#series[@]})) ||
die "expected ${#series[@]} commits, got $applied"
say "Applied $applied/${#series[@]} carried patches cleanly"
# --- 5b. Replay the upstream-only series ------------------------------------------------
# AFTER the carried series and BEFORE the defconfig commit, deliberately. That ordering is
# what makes the tree's history readable as two contiguous blocks: everything from the
# base up to carried_tip is exactly the kernel the image ships, and the block after it is
# exactly what this tree adds for upstream. EXPORT.md publishes both as `git diff` ranges
# computed from that layout, so reordering these steps silently changes what those
# one-liners mean.
#
# Same --committer-date-is-author-date as above: dates come from the patches, not the
# clock, so an unchanged series regenerates to identical SHAs.
carried_tip="$(git rev-parse HEAD)"
upstream_applied=0
if ((${#upstream_series[@]})); then
say "Replaying ${#upstream_series[@]} upstream-only patches with git am"
if ! git am --committer-date-is-author-date "${upstream_series[@]}" >/dev/null 2>&1; then
git am --abort 2>/dev/null || true
die "git am failed on the upstream-only series in $upstream_patch_dir.
These patches are never applied by Buildroot, so unlike the carried series NOTHING ELSE
in this repo exercises them -- an image build stays green while they rot against a new
kernel. Run scripts/lint-kernel-patches.sh for a malformed From:; otherwise a patch no
longer applies to $version and needs rebasing onto it."
fi
upstream_applied="$(git rev-list --count "$carried_tip"..HEAD)"
((upstream_applied == ${#upstream_series[@]})) ||
die "expected ${#upstream_series[@]} upstream-only commits, got $upstream_applied"
say "Applied $upstream_applied/${#upstream_series[@]} upstream-only patches cleanly"
fi
# --- 6. In-tree defconfig, so the tree is usable without Buildroot ------------------------
# This is the step that makes the export worth shipping: `git clone && make` works, which
# is what a materialized tree is FOR and what `make linux` inside Buildroot cannot give.
#
# Buildroot consumes BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE by copying it to .config and
# running olddefconfig; `make MiSTer_defconfig` fills in defaults the same way, so the
# minimized file works unchanged as a defconfig.
say "Generating arch/arm/configs/MiSTer_defconfig"
if [[ -n $fragments ]]; then
# Merge with the kernel's OWN merge_config.sh rather than reimplementing Buildroot's
# merge. -m merges without invoking a compiler; -r keeps later fragments winning.
read -r -a frag_list <<<"$fragments"
KCONFIG_CONFIG=arch/arm/configs/MiSTer_defconfig \
./scripts/kconfig/merge_config.sh -m -r -O arch/arm/configs \
"$config_file" "${frag_list[@]}" >/dev/null 2>&1 ||
die 'merge_config.sh failed merging the config fragments'
mv arch/arm/configs/.config arch/arm/configs/MiSTer_defconfig 2>/dev/null || true
config_note="merged from $(basename "$config_file") + $(printf '%s ' "${frag_list[@]##*/}")"
else
cp "$config_file" arch/arm/configs/MiSTer_defconfig
config_note="copied verbatim from $(basename "$config_file")"
fi
git add arch/arm/configs/MiSTer_defconfig
GIT_AUTHOR_DATE="$base_date" GIT_COMMITTER_DATE="$base_date" \
git commit --quiet --file=- <<EOF
ARM: configs: add MiSTer_defconfig
The kernel configuration this board ships, in the kernel's own minimized
defconfig form, so the tree builds standalone without Buildroot:
make ARCH=arm MiSTer_defconfig
make ARCH=arm zImage
$config_note, which is the exact configuration Buildroot builds
(BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE) — the image and this tree cannot drift.
This is deliberately the minimized form rather than a full expanded .config: an
expanded one bakes in the generating toolchain (CONFIG_CC_VERSION_TEXT) and
every default, which pins a config to one machine and buries the ~500 lines that
are actually a decision under ~4000 that are not.
Linux-Kernel_MiSTer ships the OTHER form -- its arch/arm/configs/MiSTer_defconfig
is a full resolved .config, header and all ("Automatically generated file; DO
NOT EDIT", CONFIG_CC_VERSION_TEXT naming that maintainer's
arm-none-linux-gnueabihf-gcc 10.2.1). The two forms are not a disagreement about
the configuration:
make ARCH=arm MiSTer_defconfig
resolves this minimized file to the SAME .config his full form already spells
out, for his toolchain -- that is what a defconfig IS, and it is why kconfig
ships savedefconfig. The only lines that can differ are the ones kconfig
derives from the compiler in front of it (CONFIG_CC_VERSION_TEXT,
CONFIG_GCC_VERSION, CONFIG_AS_VERSION, CONFIG_LD_VERSION, the CONFIG_CC_HAS_*
and CONFIG_TOOLS_SUPPORT_* probes), which is precisely the machine-pinning this
form leaves out.
If you want his form in this tree, generate it -- do not hand-edit it:
make ARCH=arm MiSTer_defconfig && cp .config arch/arm/configs/MiSTer_defconfig
Generated by scripts/export-kernel-tree.sh in Buildroot_MiSTer.
EOF
# --- 6a. The DTB build-name alias upstream's Makefile expects ------------------------------
# GENERATED HERE, NEVER APPLIED BY BUILDROOT -- same class as the defconfig commit above.
#
# WHY THIS EXISTS
# ---------------
# Linux-Kernel_MiSTer carries its OWN board DTS as
# arch/arm/boot/dts/intel/socfpga/socfpga_cyclone5_de10_nano.dts -- note the underscore
# between "de10" and "nano" -- and lists socfpga_cyclone5_de10_nano.dtb in that
# directory's Makefile (verified on its MiSTer-v6.18 branch at c129b0fac, which lists BOTH
# names -- mainline's socfpga_cyclone5_de10nano.dtb and its own
# socfpga_cyclone5_de10_nano.dtb). That filename is upstream's build interface: the
# maintainer builds the shipped DTB with
#
# make ... intel/socfpga/socfpga_cyclone5_de10_nano.dtb
#
# and the DTB in the stock release is that file's output.
#
# We do not carry a second DTS. Our 0004 patch instead patches VANILLA's
# socfpga_cyclone5_de10nano.dts (no underscore, added upstream in 144616a80889, v6.14),
# which did not exist when the 5.15 branch was written -- that is the better shape,
# because it keeps our delta a reviewable diff against a mainline file instead of a
# 700-line vendor copy. The cost is exactly one thing: `make
# intel/socfpga/socfpga_cyclone5_de10_nano.dtb` fails in our tree with "No rule to make
# target", so the maintainer's own build command does not work on the tree we hand him.
#
# That is a one-line problem, so it gets a one-line fix rather than a policy argument: a
# DTS whose entire body is `#include` of the patched vanilla file, under the name his
# Makefile expects, plus the matching dtb- entry. Both names then build, byte-identical
# output, and nothing in the shipped image changes -- Buildroot never sees this commit,
# and BR2_LINUX_KERNEL_INTREE_DTS_NAME still names the vanilla file.
#
# scripts/check-export-tree.sh builds BOTH .dtb targets and fails if their bytes differ,
# so the alias cannot silently drift into a second board description.
say 'Adding the socfpga_cyclone5_de10_nano.dtb build-name alias'
readonly ALIAS_DTS='arch/arm/boot/dts/intel/socfpga/socfpga_cyclone5_de10_nano.dts'
readonly VANILLA_DTS='arch/arm/boot/dts/intel/socfpga/socfpga_cyclone5_de10nano.dts'
readonly SOCFPGA_DTS_MAKEFILE='arch/arm/boot/dts/intel/socfpga/Makefile'
# Fail closed rather than emit an alias to a file that is not there. If 0004 ever moves
# to a differently-named DTS, this commit would otherwise produce a tree in which the
# maintainer's build command fails at the #include instead of at the missing target --
# a strictly worse error, arriving later.
[[ -f $VANILLA_DTS ]] ||
die "$VANILLA_DTS is not in the tree, so the
$(basename "$ALIAS_DTS") build-name alias would #include a file that does not exist.
Has the carried DTS patch (0004) changed which file it patches?"
[[ ! -e $ALIAS_DTS ]] ||
die "$ALIAS_DTS already exists in the tree.
The carried series now provides it, so this generated alias would overwrite someone
else's file. Remove this section instead of shadowing it."
grep -q "$(basename "$VANILLA_DTS" .dts)\.dtb" "$SOCFPGA_DTS_MAKEFILE" ||
die "no $(basename "$VANILLA_DTS" .dts).dtb line in $SOCFPGA_DTS_MAKEFILE --
cannot place the alias next to it. Upstream restructured this Makefile; update this
section rather than appending the entry somewhere arbitrary."
cat >"$ALIAS_DTS" <<ALIASEOF
// SPDX-License-Identifier: GPL-2.0+
/*
* socfpga_cyclone5_de10_nano.dts -- build-name alias.
*
* Linux-Kernel_MiSTer keeps the MiSTer DE10-Nano board description in a file
* of THIS name (with the underscore) and builds the shipped DTB as
* socfpga_cyclone5_de10_nano.dtb. This tree instead patches mainline's
* socfpga_cyclone5_de10nano.dts (no underscore) -- same board, reviewable as a
* diff against upstream -- so this file exists purely so that the historical
* .dtb filename still builds, and builds the same bytes.
*
* There is no board content here and none may be added: put it in
* socfpga_cyclone5_de10nano.dts, which is what the MiSTer image actually ships.
*
* Generated by scripts/export-kernel-tree.sh in Buildroot_MiSTer.
*/
#include "$(basename "$VANILLA_DTS")"
ALIASEOF
# Insert the dtb- entry immediately after the vanilla one rather than appending. This is
# ONE backslash-continued `dtb-$(CONFIG_ARCH_INTEL_SOCFPGA) +=` assignment, so a line
# appended after the last entry lands after the line that has NO trailing backslash and
# is silently not part of the list at all -- the file still parses, the target still does
# not exist, and the only symptom is the failure this whole section exists to remove.
# Duplicating the vanilla line and renaming the copy also preserves the leading tab and
# the trailing " \" exactly, which is why the substitution is done on a copy of $0
# rather than by printing a hand-built line.
#
# The `/\\$/` guard is the other half of that: it refuses to duplicate an entry that is
# the LAST in the list, because the copy would then be the orphaned line described above.
# awk rather than `sed -i`: -i needs an argument on BSD sed and this script runs on both.
awk -v vanilla="$(basename "$VANILLA_DTS" .dts).dtb" \
-v alias="$(basename "$ALIAS_DTS" .dts).dtb" '
{ print }
!done && $1 == vanilla && /\\$/ {
line = $0
sub(vanilla, alias, line)
print line
done = 1
}
END { if (!done) exit 1 }
' "$SOCFPGA_DTS_MAKEFILE" >"$SOCFPGA_DTS_MAKEFILE.new" ||
die "could not place $(basename "$ALIAS_DTS" .dts).dtb next to
$(basename "$VANILLA_DTS" .dts).dtb in $SOCFPGA_DTS_MAKEFILE: no continued dtb- line
carries it. Upstream restructured this Makefile -- update this section."
mv "$SOCFPGA_DTS_MAKEFILE.new" "$SOCFPGA_DTS_MAKEFILE"
grep -q "$(basename "$ALIAS_DTS" .dts)\.dtb" "$SOCFPGA_DTS_MAKEFILE" ||
die "failed to add $(basename "$ALIAS_DTS" .dts).dtb to $SOCFPGA_DTS_MAKEFILE"
git add --force "$ALIAS_DTS" "$SOCFPGA_DTS_MAKEFILE"
GIT_AUTHOR_DATE="$base_date" GIT_COMMITTER_DATE="$base_date" \
git commit --quiet --file=- <<EOF
ARM: dts: socfpga: add socfpga_cyclone5_de10_nano.dts build-name alias
Linux-Kernel_MiSTer carries its own board DTS as socfpga_cyclone5_de10_nano.dts
(with the underscore) and builds the shipped device tree as
socfpga_cyclone5_de10_nano.dtb:
make ARCH=arm CROSS_COMPILE=... intel/socfpga/socfpga_cyclone5_de10_nano.dtb
This tree does not carry a second board DTS. It patches mainline's
socfpga_cyclone5_de10nano.dts (no underscore, 144616a80889, v6.14) instead, so
the MiSTer delta stays a reviewable diff against an upstream file rather than a
vendor copy. Without this commit the build command above fails with "No rule to
make target" on a tree that is otherwise complete.
So: a one-line DTS that #includes the patched file, under the name the old
Makefile entry expects, and the matching dtb-\$(CONFIG_ARCH_INTEL_SOCFPGA)
entry next to the existing one. Both .dtb names now build and their output is
byte-identical -- scripts/check-export-tree.sh builds both and compares them, so
this cannot drift into a second, diverging board description.
Not applied to the MiSTer image: Buildroot builds the vanilla name directly and
never reads this commit. Generated by scripts/export-kernel-tree.sh in
Buildroot_MiSTer.
EOF
# --- 6b. Vendor the out-of-tree kernel modules -------------------------------------------
# Without this the exported tree builds a kernel with no Xbox (xone) and no 11ac WiFi,
# while the 5.15 fork has both vendored in-tree — a silent feature regression for anyone
# who builds this tree expecting what MiSTer ships.
#
# WHY THE SOURCES ARE VENDORED BUT NOT WIRED INTO Kconfig
# -------------------------------------------------------
# The obvious thing is in-tree integration (Kconfig symbol + `obj-$(CONFIG_X) += dir/`),
# the way the fork does it. It is not safe for these packages, and the reason is in the
# Realtek Makefiles, above their own `ifneq ($(KERNELRELEASE),)` guard:
#
# export TopDIR ?= $(shell pwd)
# $(shell cp $(TopDIR)/autoconf_..._linux.h $(TopDIR)/include/autoconf.h)
#
# That is parse-time filesystem mutation keyed off `pwd`. In an in-tree build `pwd` is the
# KERNEL ROOT, not the module directory, so TopDIR points at the wrong tree and the
# driver's generated autoconf.h silently never appears -- `$(shell ...)` swallows the
# error. These 2594-line Makefiles are built on the assumption that they are never
# in-tree, across ~1900 files of driver. Wiring them in-tree would mean inventing hooks no
# upstream tests, then patching upstream Makefiles we would have to maintain forever.
#
# rtl8852cu-morrownr (the newer Realtek "phl" tree) reaches the same conclusion by a
# slightly different route, worth noting so nobody re-tests the old one and declares it
# fixed: it does still `export TopDIR ?= $(shell pwd)` (its Makefile:319) but its
# `$(shell cp ... autoconf.h)` is gated off by `CONFIG_AUTOCFG_CP = n` (:67, guard at
# :465), so THAT specific mutation is inactive. The pwd dependence is not -- `DRV_PATH ?=
# $(TopDIR)` (:323) is what `include $(wildcard $(DRV_PATH)/platform/*.mk)` resolves
# against, and in an in-tree build that misses platform/autodetect.mk entirely, taking
# -DCONFIG_IOCTL_CFG80211 and the rest of the flag set with it. Same verdict: out-of-tree
# only.
#
# So the sources go in at the paths the fork uses (the tree LOOKS like the fork's), and
# they are built through the exact out-of-tree invocation Buildroot already uses -- which
# is upstream's own supported path, and is proven daily by our own image builds. The
# recipe is read from each package's .mk rather than reinvented here, which is also what
# keeps a package bump cheap: change the pin in the .mk, re-run, done.
#
# xone is a partial exception -- 41 files, a clean Kbuild -- but its `obj-m :=` is declared
# UNCONDITIONALLY, never gated on CONFIG_XONE, so an in-tree Kconfig symbol for it would be
# decorative: present, and doing nothing. Two mechanisms in one tree is also harder to
# explain than one. It goes through the same path as the rest.
# Package -> in-tree path. The only hand-maintained mapping here, kept declarative on
# purpose. Every kernel-module package gets an entry, not just the currently-enabled ones,
# so flipping one on in the defconfig needs no edit here.
# The seven deselected Realtek fork rows this table used to carry (rtl8812au,
# rtl8814au-morrownr, rtl8821au-morrownr, rtl8821cu-morrownr, rtl88x2bu,
# rtl8188eu-aircrack-ng, rtl8188fu) went away with the packages on 2026-09-10.
# They were the "just-in-case" half of the note above; with the packages gone
# there is nothing for them to map. Every row below is now on the live path.
declare -A MODULE_PATH=(
[xone]='drivers/hid/xone'
# rtl8852cu-morrownr is a WiFi fork the image SHIPS (v10.2, ADR 0016 —
# mainline rtw89 has no rtw8852cu.c). Naming rule: the fork suffix is a
# Buildroot package-name concern, and the in-tree path uses the plain chip
# name the 5.15 fork would have used.
[rtl8852cu-morrownr]='drivers/net/wireless/realtek/rtl8852cu'
)
# Packages deliberately NOT exported, with the reason. This is a separate set
# from "has no MODULE_PATH" so that forgetting a mapping still fails closed --