From 213042bba186b995bc2f25c8c2d06a9652177fa3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= <cynerd@email.cz>
Date: Tue, 27 Sep 2022 17:09:49 +0200
Subject: Import Turris OS kernel patches

---
 nixos/modules/kernel-patches.nix                   |  286 +-
 ...s-turris-omnia-enable-LED-controller-node.patch |   53 -
 ...1-net-dsa-mv88e6xxx-disable-ATU-violation.patch |   26 +
 ...-Add-slot-power-limit-milliwatt-PCIe-port.patch |   46 +
 ...-omnia-support-HW-controlled-mode-via-pri.patch |  125 -
 ...dge-emul-Set-position-of-PCI-capabilities.patch |  164 +
 ...-omnia-initialize-multi-intensity-to-full.patch |   38 -
 ...se-devm_request_irq-for-registering-inter.patch |   99 +
 ...-omnia-change-max-brightness-from-255-to-.patch |   36 -
 ...ispose-INTx-irqs-prior-to-removing-INTx-d.patch |   47 +
 ...eric-Mangle-bootloader-s-kernel-arguments.patch |  217 -
 .../0006-PCI-Assign-PCI-domain-by-ida_alloc.patch  |  215 +
 ...bu-indicate-failure-to-enter-deeper-sleep.patch |   46 -
 ...ix-endianity-when-accessing-pci-emul-brid.patch |   68 +
 .../0007-pci-mvebu-time-out-reset-on-link-up.patch |   65 -
 ...ada-38x-Fix-assigned-addresses-for-every-.patch |   76 +
 ...bu-armada-385-turris-omnia-separate-dts-f.patch |  213 -
 ...-PCI-mvebu-Update-information-about-error.patch |   32 +
 ...-phy-mvebu-a3700-comphy-Change-2500base-x.patch |   63 -
 ...mplement-support-for-interrupts-on-emulat.patch |  437 ++
 ...ce-tree-for-Omnia-back-as-that-is-what-is.patch |   32 -
 ...ada-38x.dtsi-Add-node-for-MPIC-SoC-Error-.patch |   35 +
 ...-Enable-DLLSC-interrupt-only-if-supported.patch |  139 +
 ...Enable-Command-Completed-Interrupt-only-i.patch |   38 +
 ...dd-support-for-PCI_EXP_SLTSTA_DLLSC-via-h.patch |  297 ++
 ...se-BIT-and-GENMASK-macros-instead-of-hard.patch |   47 +
 ...or-consistency-add-_OFF-suffix-to-all-reg.patch |  167 +
 ...k-Add-support-for-PCI-Bridge-Subsystem-Ve.patch |   45 +
 ...k-Dispose-INTx-irqs-prior-to-removing-INT.patch |   44 +
 ...k-Add-support-for-AER-registers-on-emulat.patch |  139 +
 ...k-Dispose-bridge-irq-prior-to-removing-br.patch |   41 +
 ...k-Add-support-for-DLLSC-and-hotplug-inter.patch |  268 +
 ...ardvark-Send-Set_Slot_Power_Limit-message.patch |  148 +
 .../0023-PCI-aardvark-Add-clock-support.patch      |   93 +
 ...4-PCI-aardvark-Add-suspend-to-RAM-support.patch |   74 +
 ...k-Replace-custom-PCIE_CORE_ERR_CAPCTL_-ma.patch |   51 +
 ...k-Don-t-write-read-only-bits-explicitly-i.patch |   39 +
 ...only-include-asm-rwonce.h-for-kernel-code.patch |   37 +
 ...-swab-use-stddefs.h-instead-of-compiler.h.patch |   26 +
 ...29-net-sfp-move-quirk-handling-into-sfp.c.patch |  299 ++
 ...-sfp-move-Alcatel-Lucent-3FE46541AA-fixup.patch |   75 +
 .../0031-net-sfp-move-Huawei-MA5671A-fixup.patch   |   52 +
 .../0032-net-sfp-redo-soft-state-polling.patch     |   84 +
 ...c_node_mem_map-with-ARCH_PFN_OFFSET-calcu.patch |   92 +
 ...4-rtc-rs5c372-support-alarms-up-to-1-week.patch |  104 +
 ...-let-the-alarm-to-be-used-as-wakeup-sourc.patch |   80 +
 ...a-config-option-for-keeping-the-kallsyms-.patch |  133 +
 ...-KALLSYMS-is-disabled-print-module-addres.patch |   49 +
 .../0038-usr-sanitize-deps_initramfs-list.patch    |   37 +
 ...reless-make-the-wl12xx-glue-code-availabl.patch |   27 +
 ...-generic-platform-mikrotik-build-bits-5.4.patch |   38 +
 ...-errors-in-unresolved-weak-symbols-on-arm.patch |   29 +
 .../0042-arc-add-OWRTDTB-section.patch             |   93 +
 ...rc-enable-unaligned-access-in-kernel-mode.patch |   31 +
 .../kernel-patches/0044-mtd-mtdsplit-support.patch |  340 ++
 ...-write-support-for-minor-aligned-partitio.patch |  238 +
 ...or-including-unpartitioned-space-in-the-r.patch |   49 +
 .../0047-Add-myloader-partition-table-parser.patch |  247 +
 ...part-check-for-bad-blocks-when-calculatin.patch |   81 +
 ...-mtd-bcm47xxpart-detect-T_Meter-partition.patch |   47 +
 ...nel-disable-cfi-cmdset-0002-erase-suspend.patch |   32 +
 ...ead-after-Write-Buffer-Load-command-to-en.patch |   26 +
 ...d-spinand-add-support-for-ESMT-F50x1G41LB.patch |  155 +
 ...d-add-EOF-marker-support-to-the-UBI-layer.patch |   69 +
 .../0054-mtd-core-add-get_mtd_device_by_node.patch |   82 +
 ...dings-add-bindings-for-mtd-concat-devices.patch |   58 +
 ...tdconcat-add-dt-driver-for-concat-devices.patch |  232 +
 .../0057-fs-add-cdrom-dependency.patch             |   63 +
 ...2-lzma-support-not-activated-by-default-y.patch | 5235 ++++++++++++++++++++
 .../kernel-patches/0059-fs-jffs2-EOF-marker.patch  |   74 +
 ...dd-support-for-flushing-conntrack-via-pro.patch |   95 +
 ...a-new-version-of-my-netfilter-speedup-pat.patch |  120 +
 ...0062-netfilter-reduce-match-memory-access.patch |   29 +
 .../0063-netfilter-optional-tcp-window-check.patch |   96 +
 ...ed-codel-do-not-defer-queue-length-update.patch |   97 +
 ...optimization-for-dealing-with-raw-sockets.patch |  149 +
 ...a-few-patches-for-avoiding-unnecessary-sk.patch |   28 +
 ...0067-Add-support-for-MAP-E-FMRs-mesh-mode.patch |  522 ++
 ...rejecting-with-source-address-failed-poli.patch |  287 ++
 ...-defines-for-_POLICY_FAILED-until-all-cod.patch |   62 +
 ...-of_net-add-mac-address-increment-support.patch |   95 +
 ...rite-back-netdev-MAC-address-to-device-tr.patch |   59 +
 ...-add-detach-callback-to-struct-phy_driver.patch |   47 +
 ...et-dsa-tag_mtk-add-padding-for-tx-packets.patch |   37 +
 ...8e6xxx-Request-assisted-learning-on-CPU-p.patch |   33 +
 ...d-add-missing-linux-if_ether.h-for-ETH_AL.patch |   49 +
 ...C-device-struct-copy-its-DMA-params-to-th.patch |   85 +
 ...pio-gpio-cascade-add-generic-GPIO-cascade.patch |  189 +
 ...-kernel-config-option-to-disabling-common.patch |   72 +
 .../0079-debloat-disable-common-USB-quirks.patch   |  126 +
 ...-fix-problem-with-platfom-data-in-w1-gpio.patch |   31 +
 .../0081-hwrng-bcm2835-set-quality-to-1000.patch   |   31 +
 ...k-Make-main-irq_chip-structure-a-static-d.patch |  107 +
 ...NFIG_MANGLE_BOOTARGS-and-disable-it-by-de.patch |   81 +
 ...ada-385.dtsi-Add-definitions-for-PCIe-err.patch |   71 +
 ...k-Implement-workaround-for-PCIe-Completio.patch |   84 +
 ...ris-omnia-configure-LED-0-pin-function-to.patch |   43 +
 ...s-turris-omnia-enable-LED-controller-node.patch |   53 +
 ...-omnia-support-HW-controlled-mode-via-pri.patch |  125 +
 ...-omnia-initialize-multi-intensity-to-full.patch |   38 +
 ...-omnia-change-max-brightness-from-255-to-.patch |   36 +
 ...eric-Mangle-bootloader-s-kernel-arguments.patch |  217 +
 .../0092-ARM-mvebu-385-ap-Add-partitions.patch     |   40 +
 ...-armada388-clearfog-emmc-on-clearfog-base.patch |   95 +
 ...ada-xp-linksys-mamba-Increase-kernel-part.patch |   42 +
 ...-phy-mvebu-a3700-comphy-Change-2500base-x.patch |   63 +
 ...bu-indicate-failure-to-enter-deeper-sleep.patch |   46 +
 107 files changed, 14941 insertions(+), 902 deletions(-)
 delete mode 100644 nixos/modules/kernel-patches/0001-ARM-dts-turris-omnia-enable-LED-controller-node.patch
 create mode 100644 nixos/modules/kernel-patches/0001-net-dsa-mv88e6xxx-disable-ATU-violation.patch
 create mode 100644 nixos/modules/kernel-patches/0002-dt-bindings-Add-slot-power-limit-milliwatt-PCIe-port.patch
 delete mode 100644 nixos/modules/kernel-patches/0002-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch
 create mode 100644 nixos/modules/kernel-patches/0003-PCI-pci-bridge-emul-Set-position-of-PCI-capabilities.patch
 delete mode 100644 nixos/modules/kernel-patches/0003-leds-turris-omnia-initialize-multi-intensity-to-full.patch
 create mode 100644 nixos/modules/kernel-patches/0004-PCI-mvebu-Use-devm_request_irq-for-registering-inter.patch
 delete mode 100644 nixos/modules/kernel-patches/0004-leds-turris-omnia-change-max-brightness-from-255-to-.patch
 create mode 100644 nixos/modules/kernel-patches/0005-PCI-mvebu-Dispose-INTx-irqs-prior-to-removing-INTx-d.patch
 delete mode 100644 nixos/modules/kernel-patches/0005-generic-Mangle-bootloader-s-kernel-arguments.patch
 create mode 100644 nixos/modules/kernel-patches/0006-PCI-Assign-PCI-domain-by-ida_alloc.patch
 delete mode 100644 nixos/modules/kernel-patches/0006-cpuidle-mvebu-indicate-failure-to-enter-deeper-sleep.patch
 create mode 100644 nixos/modules/kernel-patches/0007-PCI-mvebu-Fix-endianity-when-accessing-pci-emul-brid.patch
 delete mode 100644 nixos/modules/kernel-patches/0007-pci-mvebu-time-out-reset-on-link-up.patch
 create mode 100644 nixos/modules/kernel-patches/0008-ARM-dts-armada-38x-Fix-assigned-addresses-for-every-.patch
 delete mode 100644 nixos/modules/kernel-patches/0008-ARM-dts-mvebu-armada-385-turris-omnia-separate-dts-f.patch
 create mode 100644 nixos/modules/kernel-patches/0009-dt-bindings-PCI-mvebu-Update-information-about-error.patch
 delete mode 100644 nixos/modules/kernel-patches/0009-phy-marvell-phy-mvebu-a3700-comphy-Change-2500base-x.patch
 create mode 100644 nixos/modules/kernel-patches/0010-PCI-mvebu-Implement-support-for-interrupts-on-emulat.patch
 delete mode 100644 nixos/modules/kernel-patches/0010-Rename-device-tree-for-Omnia-back-as-that-is-what-is.patch
 create mode 100644 nixos/modules/kernel-patches/0011-ARM-dts-armada-38x.dtsi-Add-node-for-MPIC-SoC-Error-.patch
 create mode 100644 nixos/modules/kernel-patches/0012-PCI-pciehp-Enable-DLLSC-interrupt-only-if-supported.patch
 create mode 100644 nixos/modules/kernel-patches/0013-PCI-pciehp-Enable-Command-Completed-Interrupt-only-i.patch
 create mode 100644 nixos/modules/kernel-patches/0014-PCI-mvebu-Add-support-for-PCI_EXP_SLTSTA_DLLSC-via-h.patch
 create mode 100644 nixos/modules/kernel-patches/0015-PCI-mvebu-use-BIT-and-GENMASK-macros-instead-of-hard.patch
 create mode 100644 nixos/modules/kernel-patches/0016-PCI-mvebu-For-consistency-add-_OFF-suffix-to-all-reg.patch
 create mode 100644 nixos/modules/kernel-patches/0017-PCI-aardvark-Add-support-for-PCI-Bridge-Subsystem-Ve.patch
 create mode 100644 nixos/modules/kernel-patches/0018-PCI-aardvark-Dispose-INTx-irqs-prior-to-removing-INT.patch
 create mode 100644 nixos/modules/kernel-patches/0019-PCI-aardvark-Add-support-for-AER-registers-on-emulat.patch
 create mode 100644 nixos/modules/kernel-patches/0020-PCI-aardvark-Dispose-bridge-irq-prior-to-removing-br.patch
 create mode 100644 nixos/modules/kernel-patches/0021-PCI-aardvark-Add-support-for-DLLSC-and-hotplug-inter.patch
 create mode 100644 nixos/modules/kernel-patches/0022-PCI-aardvark-Send-Set_Slot_Power_Limit-message.patch
 create mode 100644 nixos/modules/kernel-patches/0023-PCI-aardvark-Add-clock-support.patch
 create mode 100644 nixos/modules/kernel-patches/0024-PCI-aardvark-Add-suspend-to-RAM-support.patch
 create mode 100644 nixos/modules/kernel-patches/0025-PCI-aardvark-Replace-custom-PCIE_CORE_ERR_CAPCTL_-ma.patch
 create mode 100644 nixos/modules/kernel-patches/0026-PCI-aardvark-Don-t-write-read-only-bits-explicitly-i.patch
 create mode 100644 nixos/modules/kernel-patches/0027-compiler.h-only-include-asm-rwonce.h-for-kernel-code.patch
 create mode 100644 nixos/modules/kernel-patches/0028-swab-use-stddefs.h-instead-of-compiler.h.patch
 create mode 100644 nixos/modules/kernel-patches/0029-net-sfp-move-quirk-handling-into-sfp.c.patch
 create mode 100644 nixos/modules/kernel-patches/0030-net-sfp-move-Alcatel-Lucent-3FE46541AA-fixup.patch
 create mode 100644 nixos/modules/kernel-patches/0031-net-sfp-move-Huawei-MA5671A-fixup.patch
 create mode 100644 nixos/modules/kernel-patches/0032-net-sfp-redo-soft-state-polling.patch
 create mode 100644 nixos/modules/kernel-patches/0033-mm-Fix-alloc_node_mem_map-with-ARCH_PFN_OFFSET-calcu.patch
 create mode 100644 nixos/modules/kernel-patches/0034-rtc-rs5c372-support-alarms-up-to-1-week.patch
 create mode 100644 nixos/modules/kernel-patches/0035-rtc-rs5c372-let-the-alarm-to-be-used-as-wakeup-sourc.patch
 create mode 100644 nixos/modules/kernel-patches/0036-kernel-add-a-config-option-for-keeping-the-kallsyms-.patch
 create mode 100644 nixos/modules/kernel-patches/0037-kernel-when-KALLSYMS-is-disabled-print-module-addres.patch
 create mode 100644 nixos/modules/kernel-patches/0038-usr-sanitize-deps_initramfs-list.patch
 create mode 100644 nixos/modules/kernel-patches/0039-hack-net-wireless-make-the-wl12xx-glue-code-availabl.patch
 create mode 100644 nixos/modules/kernel-patches/0040-generic-platform-mikrotik-build-bits-5.4.patch
 create mode 100644 nixos/modules/kernel-patches/0041-fix-errors-in-unresolved-weak-symbols-on-arm.patch
 create mode 100644 nixos/modules/kernel-patches/0042-arc-add-OWRTDTB-section.patch
 create mode 100644 nixos/modules/kernel-patches/0043-arc-enable-unaligned-access-in-kernel-mode.patch
 create mode 100644 nixos/modules/kernel-patches/0044-mtd-mtdsplit-support.patch
 create mode 100644 nixos/modules/kernel-patches/0045-mtd-spi-nor-write-support-for-minor-aligned-partitio.patch
 create mode 100644 nixos/modules/kernel-patches/0046-add-patch-for-including-unpartitioned-space-in-the-r.patch
 create mode 100644 nixos/modules/kernel-patches/0047-Add-myloader-partition-table-parser.patch
 create mode 100644 nixos/modules/kernel-patches/0048-mtd-bcm47xxpart-check-for-bad-blocks-when-calculatin.patch
 create mode 100644 nixos/modules/kernel-patches/0049-mtd-bcm47xxpart-detect-T_Meter-partition.patch
 create mode 100644 nixos/modules/kernel-patches/0050-kernel-disable-cfi-cmdset-0002-erase-suspend.patch
 create mode 100644 nixos/modules/kernel-patches/0051-Issue-map-read-after-Write-Buffer-Load-command-to-en.patch
 create mode 100644 nixos/modules/kernel-patches/0052-mtd-spinand-add-support-for-ESMT-F50x1G41LB.patch
 create mode 100644 nixos/modules/kernel-patches/0053-mtd-add-EOF-marker-support-to-the-UBI-layer.patch
 create mode 100644 nixos/modules/kernel-patches/0054-mtd-core-add-get_mtd_device_by_node.patch
 create mode 100644 nixos/modules/kernel-patches/0055-dt-bindings-add-bindings-for-mtd-concat-devices.patch
 create mode 100644 nixos/modules/kernel-patches/0056-mtd-mtdconcat-add-dt-driver-for-concat-devices.patch
 create mode 100644 nixos/modules/kernel-patches/0057-fs-add-cdrom-dependency.patch
 create mode 100644 nixos/modules/kernel-patches/0058-fs-add-jffs2-lzma-support-not-activated-by-default-y.patch
 create mode 100644 nixos/modules/kernel-patches/0059-fs-jffs2-EOF-marker.patch
 create mode 100644 nixos/modules/kernel-patches/0060-netfilter-add-support-for-flushing-conntrack-via-pro.patch
 create mode 100644 nixos/modules/kernel-patches/0061-kernel-add-a-new-version-of-my-netfilter-speedup-pat.patch
 create mode 100644 nixos/modules/kernel-patches/0062-netfilter-reduce-match-memory-access.patch
 create mode 100644 nixos/modules/kernel-patches/0063-netfilter-optional-tcp-window-check.patch
 create mode 100644 nixos/modules/kernel-patches/0064-net_sched-codel-do-not-defer-queue-length-update.patch
 create mode 100644 nixos/modules/kernel-patches/0065-net-add-an-optimization-for-dealing-with-raw-sockets.patch
 create mode 100644 nixos/modules/kernel-patches/0066-kernel-add-a-few-patches-for-avoiding-unnecessary-sk.patch
 create mode 100644 nixos/modules/kernel-patches/0067-Add-support-for-MAP-E-FMRs-mesh-mode.patch
 create mode 100644 nixos/modules/kernel-patches/0068-ipv6-allow-rejecting-with-source-address-failed-poli.patch
 create mode 100644 nixos/modules/kernel-patches/0069-net-provide-defines-for-_POLICY_FAILED-until-all-cod.patch
 create mode 100644 nixos/modules/kernel-patches/0070-of_net-add-mac-address-increment-support.patch
 create mode 100644 nixos/modules/kernel-patches/0071-of-of_net-write-back-netdev-MAC-address-to-device-tr.patch
 create mode 100644 nixos/modules/kernel-patches/0072-generic-add-detach-callback-to-struct-phy_driver.patch
 create mode 100644 nixos/modules/kernel-patches/0073-net-dsa-tag_mtk-add-padding-for-tx-packets.patch
 create mode 100644 nixos/modules/kernel-patches/0074-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-p.patch
 create mode 100644 nixos/modules/kernel-patches/0075-ARM-kirkwood-add-missing-linux-if_ether.h-for-ETH_AL.patch
 create mode 100644 nixos/modules/kernel-patches/0076-bcma-get-SoC-device-struct-copy-its-DMA-params-to-th.patch
 create mode 100644 nixos/modules/kernel-patches/0077-gpio-gpio-cascade-add-generic-GPIO-cascade.patch
 create mode 100644 nixos/modules/kernel-patches/0078-debloat-add-kernel-config-option-to-disabling-common.patch
 create mode 100644 nixos/modules/kernel-patches/0079-debloat-disable-common-USB-quirks.patch
 create mode 100644 nixos/modules/kernel-patches/0080-w1-gpio-fix-problem-with-platfom-data-in-w1-gpio.patch
 create mode 100644 nixos/modules/kernel-patches/0081-hwrng-bcm2835-set-quality-to-1000.patch
 create mode 100644 nixos/modules/kernel-patches/0082-PCI-aardvark-Make-main-irq_chip-structure-a-static-d.patch
 create mode 100644 nixos/modules/kernel-patches/0083-init-add-CONFIG_MANGLE_BOOTARGS-and-disable-it-by-de.patch
 create mode 100644 nixos/modules/kernel-patches/0084-ARM-dts-armada-385.dtsi-Add-definitions-for-PCIe-err.patch
 create mode 100644 nixos/modules/kernel-patches/0085-PCI-aardvark-Implement-workaround-for-PCIe-Completio.patch
 create mode 100644 nixos/modules/kernel-patches/0086-ARM-dts-turris-omnia-configure-LED-0-pin-function-to.patch
 create mode 100644 nixos/modules/kernel-patches/0087-ARM-dts-turris-omnia-enable-LED-controller-node.patch
 create mode 100644 nixos/modules/kernel-patches/0088-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch
 create mode 100644 nixos/modules/kernel-patches/0089-leds-turris-omnia-initialize-multi-intensity-to-full.patch
 create mode 100644 nixos/modules/kernel-patches/0090-leds-turris-omnia-change-max-brightness-from-255-to-.patch
 create mode 100644 nixos/modules/kernel-patches/0091-generic-Mangle-bootloader-s-kernel-arguments.patch
 create mode 100644 nixos/modules/kernel-patches/0092-ARM-mvebu-385-ap-Add-partitions.patch
 create mode 100644 nixos/modules/kernel-patches/0093-ARM-dts-armada388-clearfog-emmc-on-clearfog-base.patch
 create mode 100644 nixos/modules/kernel-patches/0094-ARM-dts-armada-xp-linksys-mamba-Increase-kernel-part.patch
 create mode 100644 nixos/modules/kernel-patches/0095-phy-marvell-phy-mvebu-a3700-comphy-Change-2500base-x.patch
 create mode 100644 nixos/modules/kernel-patches/0096-cpuidle-mvebu-indicate-failure-to-enter-deeper-sleep.patch

(limited to 'nixos/modules')

diff --git a/nixos/modules/kernel-patches.nix b/nixos/modules/kernel-patches.nix
index 516508c..d8588e9 100644
--- a/nixos/modules/kernel-patches.nix
+++ b/nixos/modules/kernel-patches.nix
@@ -6,35 +6,293 @@ with lib;
   config = {
     # Kernel patches from Turris OS
     boot.kernelPatches = [{
+      name = "net-dsa-mv88e6xxx-disable-ATU-violation";
+      patch = ./kernel-patches/0001-net-dsa-mv88e6xxx-disable-ATU-violation.patch;
+    }{
+      name = "dt-bindings-Add-slot-power-limit-milliwatt-PCIe-port";
+      patch = ./kernel-patches/0002-dt-bindings-Add-slot-power-limit-milliwatt-PCIe-port.patch;
+    }{
+      name = "PCI-pci-bridge-emul-Set-position-of-PCI-capabilities";
+      patch = ./kernel-patches/0003-PCI-pci-bridge-emul-Set-position-of-PCI-capabilities.patch;
+    }{
+      name = "PCI-mvebu-Use-devm_request_irq-for-registering-inter";
+      patch = ./kernel-patches/0004-PCI-mvebu-Use-devm_request_irq-for-registering-inter.patch;
+    }{
+      name = "PCI-mvebu-Dispose-INTx-irqs-prior-to-removing-INTx-d";
+      patch = ./kernel-patches/0005-PCI-mvebu-Dispose-INTx-irqs-prior-to-removing-INTx-d.patch;
+    }{
+      name = "PCI-Assign-PCI-domain-by-ida_alloc";
+      patch = ./kernel-patches/0006-PCI-Assign-PCI-domain-by-ida_alloc.patch;
+    }{
+      name = "PCI-mvebu-Fix-endianity-when-accessing-pci-emul-brid";
+      patch = ./kernel-patches/0007-PCI-mvebu-Fix-endianity-when-accessing-pci-emul-brid.patch;
+    }{
+      name = "ARM-dts-armada-38x-Fix-assigned-addresses-for-every-";
+      patch = ./kernel-patches/0008-ARM-dts-armada-38x-Fix-assigned-addresses-for-every-.patch;
+    }{
+      name = "dt-bindings-PCI-mvebu-Update-information-about-error";
+      patch = ./kernel-patches/0009-dt-bindings-PCI-mvebu-Update-information-about-error.patch;
+    }{
+      name = "PCI-mvebu-Implement-support-for-interrupts-on-emulat";
+      patch = ./kernel-patches/0010-PCI-mvebu-Implement-support-for-interrupts-on-emulat.patch;
+    }{
+      name = "ARM-dts-armada-38x.dtsi-Add-node-for-MPIC-SoC-Error-";
+      patch = ./kernel-patches/0011-ARM-dts-armada-38x.dtsi-Add-node-for-MPIC-SoC-Error-.patch;
+    }{
+      name = "PCI-pciehp-Enable-DLLSC-interrupt-only-if-supported";
+      patch = ./kernel-patches/0012-PCI-pciehp-Enable-DLLSC-interrupt-only-if-supported.patch;
+    }{
+      name = "PCI-pciehp-Enable-Command-Completed-Interrupt-only-i";
+      patch = ./kernel-patches/0013-PCI-pciehp-Enable-Command-Completed-Interrupt-only-i.patch;
+    }{
+      name = "PCI-mvebu-Add-support-for-PCI_EXP_SLTSTA_DLLSC-via-h";
+      patch = ./kernel-patches/0014-PCI-mvebu-Add-support-for-PCI_EXP_SLTSTA_DLLSC-via-h.patch;
+    }{
+      name = "PCI-mvebu-use-BIT-and-GENMASK-macros-instead-of-hard";
+      patch = ./kernel-patches/0015-PCI-mvebu-use-BIT-and-GENMASK-macros-instead-of-hard.patch;
+    }{
+      name = "PCI-mvebu-For-consistency-add-_OFF-suffix-to-all-reg";
+      patch = ./kernel-patches/0016-PCI-mvebu-For-consistency-add-_OFF-suffix-to-all-reg.patch;
+    }{
+      name = "PCI-aardvark-Add-support-for-PCI-Bridge-Subsystem-Ve";
+      patch = ./kernel-patches/0017-PCI-aardvark-Add-support-for-PCI-Bridge-Subsystem-Ve.patch;
+    }{
+      name = "PCI-aardvark-Dispose-INTx-irqs-prior-to-removing-INT";
+      patch = ./kernel-patches/0018-PCI-aardvark-Dispose-INTx-irqs-prior-to-removing-INT.patch;
+    }{
+      name = "PCI-aardvark-Add-support-for-AER-registers-on-emulat";
+      patch = ./kernel-patches/0019-PCI-aardvark-Add-support-for-AER-registers-on-emulat.patch;
+    }{
+      name = "PCI-aardvark-Dispose-bridge-irq-prior-to-removing-br";
+      patch = ./kernel-patches/0020-PCI-aardvark-Dispose-bridge-irq-prior-to-removing-br.patch;
+    }{
+      name = "PCI-aardvark-Add-support-for-DLLSC-and-hotplug-inter";
+      patch = ./kernel-patches/0021-PCI-aardvark-Add-support-for-DLLSC-and-hotplug-inter.patch;
+    }{
+      name = "PCI-aardvark-Send-Set_Slot_Power_Limit-message";
+      patch = ./kernel-patches/0022-PCI-aardvark-Send-Set_Slot_Power_Limit-message.patch;
+    }{
+      name = "PCI-aardvark-Add-clock-support";
+      patch = ./kernel-patches/0023-PCI-aardvark-Add-clock-support.patch;
+    }{
+      name = "PCI-aardvark-Add-suspend-to-RAM-support";
+      patch = ./kernel-patches/0024-PCI-aardvark-Add-suspend-to-RAM-support.patch;
+    }{
+      name = "PCI-aardvark-Replace-custom-PCIE_CORE_ERR_CAPCTL_-ma";
+      patch = ./kernel-patches/0025-PCI-aardvark-Replace-custom-PCIE_CORE_ERR_CAPCTL_-ma.patch;
+    }{
+      name = "PCI-aardvark-Don-t-write-read-only-bits-explicitly-i";
+      patch = ./kernel-patches/0026-PCI-aardvark-Don-t-write-read-only-bits-explicitly-i.patch;
+    }{
+      name = "compiler.h-only-include-asm-rwonce.h-for-kernel-code";
+      patch = ./kernel-patches/0027-compiler.h-only-include-asm-rwonce.h-for-kernel-code.patch;
+    }{
+      name = "swab-use-stddefs.h-instead-of-compiler.h";
+      patch = ./kernel-patches/0028-swab-use-stddefs.h-instead-of-compiler.h.patch;
+    }{
+      name = "net-sfp-move-quirk-handling-into-sfp.c";
+      patch = ./kernel-patches/0029-net-sfp-move-quirk-handling-into-sfp.c.patch;
+    }{
+      name = "net-sfp-move-Alcatel-Lucent-3FE46541AA-fixup";
+      patch = ./kernel-patches/0030-net-sfp-move-Alcatel-Lucent-3FE46541AA-fixup.patch;
+    }{
+      name = "net-sfp-move-Huawei-MA5671A-fixup";
+      patch = ./kernel-patches/0031-net-sfp-move-Huawei-MA5671A-fixup.patch;
+    }{
+      name = "net-sfp-redo-soft-state-polling";
+      patch = ./kernel-patches/0032-net-sfp-redo-soft-state-polling.patch;
+    }{
+      name = "mm-Fix-alloc_node_mem_map-with-ARCH_PFN_OFFSET-calcu";
+      patch = ./kernel-patches/0033-mm-Fix-alloc_node_mem_map-with-ARCH_PFN_OFFSET-calcu.patch;
+    }{
+      name = "rtc-rs5c372-support-alarms-up-to-1-week";
+      patch = ./kernel-patches/0034-rtc-rs5c372-support-alarms-up-to-1-week.patch;
+    }{
+      name = "rtc-rs5c372-let-the-alarm-to-be-used-as-wakeup-sourc";
+      patch = ./kernel-patches/0035-rtc-rs5c372-let-the-alarm-to-be-used-as-wakeup-sourc.patch;
+    }{
+      name = "kernel-add-a-config-option-for-keeping-the-kallsyms-";
+      patch = ./kernel-patches/0036-kernel-add-a-config-option-for-keeping-the-kallsyms-.patch;
+    }{
+      name = "kernel-when-KALLSYMS-is-disabled-print-module-addres";
+      patch = ./kernel-patches/0037-kernel-when-KALLSYMS-is-disabled-print-module-addres.patch;
+    }{
+      name = "usr-sanitize-deps_initramfs-list";
+      patch = ./kernel-patches/0038-usr-sanitize-deps_initramfs-list.patch;
+    }{
+      name = "hack-net-wireless-make-the-wl12xx-glue-code-availabl";
+      patch = ./kernel-patches/0039-hack-net-wireless-make-the-wl12xx-glue-code-availabl.patch;
+    }{
+      name = "generic-platform-mikrotik-build-bits-5.4";
+      patch = ./kernel-patches/0040-generic-platform-mikrotik-build-bits-5.4.patch;
+    }{
+      name = "fix-errors-in-unresolved-weak-symbols-on-arm";
+      patch = ./kernel-patches/0041-fix-errors-in-unresolved-weak-symbols-on-arm.patch;
+    }{
+      name = "arc-add-OWRTDTB-section";
+      patch = ./kernel-patches/0042-arc-add-OWRTDTB-section.patch;
+    }{
+      name = "arc-enable-unaligned-access-in-kernel-mode";
+      patch = ./kernel-patches/0043-arc-enable-unaligned-access-in-kernel-mode.patch;
+    }{
+      name = "mtd-mtdsplit-support";
+      patch = ./kernel-patches/0044-mtd-mtdsplit-support.patch;
+    }{
+      name = "mtd-spi-nor-write-support-for-minor-aligned-partitio";
+      patch = ./kernel-patches/0045-mtd-spi-nor-write-support-for-minor-aligned-partitio.patch;
+    }{
+      name = "add-patch-for-including-unpartitioned-space-in-the-r";
+      patch = ./kernel-patches/0046-add-patch-for-including-unpartitioned-space-in-the-r.patch;
+    }{
+      name = "Add-myloader-partition-table-parser";
+      patch = ./kernel-patches/0047-Add-myloader-partition-table-parser.patch;
+    }{
+      name = "mtd-bcm47xxpart-check-for-bad-blocks-when-calculatin";
+      patch = ./kernel-patches/0048-mtd-bcm47xxpart-check-for-bad-blocks-when-calculatin.patch;
+    }{
+      name = "mtd-bcm47xxpart-detect-T_Meter-partition";
+      patch = ./kernel-patches/0049-mtd-bcm47xxpart-detect-T_Meter-partition.patch;
+    }{
+      name = "kernel-disable-cfi-cmdset-0002-erase-suspend";
+      patch = ./kernel-patches/0050-kernel-disable-cfi-cmdset-0002-erase-suspend.patch;
+    }{
+      name = "Issue-map-read-after-Write-Buffer-Load-command-to-en";
+      patch = ./kernel-patches/0051-Issue-map-read-after-Write-Buffer-Load-command-to-en.patch;
+    }{
+      name = "mtd-spinand-add-support-for-ESMT-F50x1G41LB";
+      patch = ./kernel-patches/0052-mtd-spinand-add-support-for-ESMT-F50x1G41LB.patch;
+    }{
+      name = "mtd-add-EOF-marker-support-to-the-UBI-layer";
+      patch = ./kernel-patches/0053-mtd-add-EOF-marker-support-to-the-UBI-layer.patch;
+    }{
+      name = "mtd-core-add-get_mtd_device_by_node";
+      patch = ./kernel-patches/0054-mtd-core-add-get_mtd_device_by_node.patch;
+    }{
+      name = "dt-bindings-add-bindings-for-mtd-concat-devices";
+      patch = ./kernel-patches/0055-dt-bindings-add-bindings-for-mtd-concat-devices.patch;
+    }{
+      name = "mtd-mtdconcat-add-dt-driver-for-concat-devices";
+      patch = ./kernel-patches/0056-mtd-mtdconcat-add-dt-driver-for-concat-devices.patch;
+    }{
+      name = "fs-add-cdrom-dependency";
+      patch = ./kernel-patches/0057-fs-add-cdrom-dependency.patch;
+    }{
+      name = "fs-add-jffs2-lzma-support-not-activated-by-default-y";
+      patch = ./kernel-patches/0058-fs-add-jffs2-lzma-support-not-activated-by-default-y.patch;
+    }{
+      name = "fs-jffs2-EOF-marker";
+      patch = ./kernel-patches/0059-fs-jffs2-EOF-marker.patch;
+    }{
+      name = "netfilter-add-support-for-flushing-conntrack-via-pro";
+      patch = ./kernel-patches/0060-netfilter-add-support-for-flushing-conntrack-via-pro.patch;
+    }{
+      name = "kernel-add-a-new-version-of-my-netfilter-speedup-pat";
+      patch = ./kernel-patches/0061-kernel-add-a-new-version-of-my-netfilter-speedup-pat.patch;
+    }{
+      name = "netfilter-reduce-match-memory-access";
+      patch = ./kernel-patches/0062-netfilter-reduce-match-memory-access.patch;
+    }{
+      name = "netfilter-optional-tcp-window-check";
+      patch = ./kernel-patches/0063-netfilter-optional-tcp-window-check.patch;
+    }{
+      name = "net_sched-codel-do-not-defer-queue-length-update";
+      patch = ./kernel-patches/0064-net_sched-codel-do-not-defer-queue-length-update.patch;
+    }{
+      name = "net-add-an-optimization-for-dealing-with-raw-sockets";
+      patch = ./kernel-patches/0065-net-add-an-optimization-for-dealing-with-raw-sockets.patch;
+    }{
+      name = "kernel-add-a-few-patches-for-avoiding-unnecessary-sk";
+      patch = ./kernel-patches/0066-kernel-add-a-few-patches-for-avoiding-unnecessary-sk.patch;
+    }{
+      name = "Add-support-for-MAP-E-FMRs-mesh-mode";
+      patch = ./kernel-patches/0067-Add-support-for-MAP-E-FMRs-mesh-mode.patch;
+    }{
+      name = "ipv6-allow-rejecting-with-source-address-failed-poli";
+      patch = ./kernel-patches/0068-ipv6-allow-rejecting-with-source-address-failed-poli.patch;
+    }{
+      name = "net-provide-defines-for-_POLICY_FAILED-until-all-cod";
+      patch = ./kernel-patches/0069-net-provide-defines-for-_POLICY_FAILED-until-all-cod.patch;
+    }{
+      name = "of_net-add-mac-address-increment-support";
+      patch = ./kernel-patches/0070-of_net-add-mac-address-increment-support.patch;
+    }{
+      name = "of-of_net-write-back-netdev-MAC-address-to-device-tr";
+      patch = ./kernel-patches/0071-of-of_net-write-back-netdev-MAC-address-to-device-tr.patch;
+    }{
+      name = "generic-add-detach-callback-to-struct-phy_driver";
+      patch = ./kernel-patches/0072-generic-add-detach-callback-to-struct-phy_driver.patch;
+    }{
+      name = "net-dsa-tag_mtk-add-padding-for-tx-packets";
+      patch = ./kernel-patches/0073-net-dsa-tag_mtk-add-padding-for-tx-packets.patch;
+    }{
+      name = "net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-p";
+      patch = ./kernel-patches/0074-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-p.patch;
+    }{
+      name = "ARM-kirkwood-add-missing-linux-if_ether";
+      patch = ./kernel-patches/0075-ARM-kirkwood-add-missing-linux-if_ether.h-for-ETH_AL.patch;
+    }{
+      name = "bcma-get-SoC-device-struct-copy-its-DMA-params-to-th";
+      patch = ./kernel-patches/0076-bcma-get-SoC-device-struct-copy-its-DMA-params-to-th.patch;
+    }{
+      name = "gpio-gpio-cascade-add-generic-GPIO-cascade";
+      patch = ./kernel-patches/0077-gpio-gpio-cascade-add-generic-GPIO-cascade.patch;
+    }{
+      name = "debloat-add-kernel-config-option-to-disabling-common";
+      patch = ./kernel-patches/0078-debloat-add-kernel-config-option-to-disabling-common.patch;
+    }{
+      name = "debloat-disable-common-USB-quirks";
+      patch = ./kernel-patches/0079-debloat-disable-common-USB-quirks.patch;
+    }{
+      name = "w1-gpio-fix-problem-with-platfom-data-in-w1-gpio";
+      patch = ./kernel-patches/0080-w1-gpio-fix-problem-with-platfom-data-in-w1-gpio.patch;
+    }{
+      name = "hwrng-bcm2835-set-quality-to-1000";
+      patch = ./kernel-patches/0081-hwrng-bcm2835-set-quality-to-1000.patch;
+    }{
+      name = "PCI-aardvark-Make-main-irq_chip-structure-a-static-d";
+      patch = ./kernel-patches/0082-PCI-aardvark-Make-main-irq_chip-structure-a-static-d.patch;
+    }{
+      name = "init-add-CONFIG_MANGLE_BOOTARGS-and-disable-it-by-de";
+      patch = ./kernel-patches/0083-init-add-CONFIG_MANGLE_BOOTARGS-and-disable-it-by-de.patch;
+    }{
+      name = "ARM-dts-armada-385.dtsi-Add-definitions-for-PCIe-err";
+      patch = ./kernel-patches/0084-ARM-dts-armada-385.dtsi-Add-definitions-for-PCIe-err.patch;
+    }{
+      name = "PCI-aardvark-Implement-workaround-for-PCIe-Completio";
+      patch = ./kernel-patches/0085-PCI-aardvark-Implement-workaround-for-PCIe-Completio.patch;
+    }{
+      name = "ARM-dts-turris-omnia-configure-LED-0-pin-function-to";
+      patch = ./kernel-patches/0086-ARM-dts-turris-omnia-configure-LED-0-pin-function-to.patch;
+    }{
       name = "ARM-dts-turris-omnia-enable-LED-controller-node";
-      patch = ./kernel-patches/0001-ARM-dts-turris-omnia-enable-LED-controller-node.patch;
+      patch = ./kernel-patches/0087-ARM-dts-turris-omnia-enable-LED-controller-node.patch;
     }{
       name = "leds-turris-omnia-support-HW-controlled-mode-via-pri";
-      patch = ./kernel-patches/0002-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch;
+      patch = ./kernel-patches/0088-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch;
     }{
       name = "leds-turris-omnia-initialize-multi-intensity-to-full";
-      patch = ./kernel-patches/0003-leds-turris-omnia-initialize-multi-intensity-to-full.patch;
+      patch = ./kernel-patches/0089-leds-turris-omnia-initialize-multi-intensity-to-full.patch;
     }{
       name = "leds-turris-omnia-change-max-brightness-from-255-to-";
-      patch = ./kernel-patches/0004-leds-turris-omnia-change-max-brightness-from-255-to-.patch;
+      patch = ./kernel-patches/0090-leds-turris-omnia-change-max-brightness-from-255-to-.patch;
     }{
       name = "generic-Mangle-bootloader-s-kernel-arguments";
-      patch = ./kernel-patches/0005-generic-Mangle-bootloader-s-kernel-arguments.patch;
+      patch = ./kernel-patches/0091-generic-Mangle-bootloader-s-kernel-arguments.patch;
     }{
-      name = "cpuidle-mvebu-indicate-failure-to-enter-deeper-sleep";
-      patch = ./kernel-patches/0006-cpuidle-mvebu-indicate-failure-to-enter-deeper-sleep.patch;
+      name = "ARM-mvebu-385-ap-Add-partitions";
+      patch = ./kernel-patches/0092-ARM-mvebu-385-ap-Add-partitions.patch;
     }{
-      name = "pci-mvebu-time-out-reset-on-link-up";
-      patch = ./kernel-patches/0007-pci-mvebu-time-out-reset-on-link-up.patch;
+      name = "ARM-dts-armada388-clearfog-emmc-on-clearfog-base";
+      patch = ./kernel-patches/0093-ARM-dts-armada388-clearfog-emmc-on-clearfog-base.patch;
     }{
-      name = "ARM-dts-mvebu-armada-385-turris-omnia-separate-dts-f";
-      patch = ./kernel-patches/0008-ARM-dts-mvebu-armada-385-turris-omnia-separate-dts-f.patch;
+      name = "ARM-dts-armada-xp-linksys-mamba-Increase-kernel-part";
+      patch = ./kernel-patches/0094-ARM-dts-armada-xp-linksys-mamba-Increase-kernel-part.patch;
     }{
       name = "phy-marvell-phy-mvebu-a3700-comphy-Change-2500base-x";
-      patch = ./kernel-patches/0009-phy-marvell-phy-mvebu-a3700-comphy-Change-2500base-x.patch;
+      patch = ./kernel-patches/0095-phy-marvell-phy-mvebu-a3700-comphy-Change-2500base-x.patch;
     }{
-      name = "Rename-device-tree-for-Omnia-back-as-that-is-what-is";
-      patch = ./kernel-patches/0010-Rename-device-tree-for-Omnia-back-as-that-is-what-is.patch;
+      name = "cpuidle-mvebu-indicate-failure-to-enter-deeper-sleep";
+      patch = ./kernel-patches/0096-cpuidle-mvebu-indicate-failure-to-enter-deeper-sleep.patch;
     }];
   };
 }
diff --git a/nixos/modules/kernel-patches/0001-ARM-dts-turris-omnia-enable-LED-controller-node.patch b/nixos/modules/kernel-patches/0001-ARM-dts-turris-omnia-enable-LED-controller-node.patch
deleted file mode 100644
index a3d421a..0000000
--- a/nixos/modules/kernel-patches/0001-ARM-dts-turris-omnia-enable-LED-controller-node.patch
+++ /dev/null
@@ -1,53 +0,0 @@
-From d33a04c156e91d813fc4dae3ffc9f87715b13446 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
-Date: Mon, 4 Jul 2022 13:36:22 +0200
-Subject: [PATCH 01/10] ARM: dts: turris-omnia: enable LED controller node
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-The LED controller node is disabled because the leds-turris-omnia driver
-does not support setting the LED blinking to be controlled by the MCU.
-
-The patches for that have now been sent [1], so let's enable the node.
-
-[1] https://lore.kernel.org/linux-leds/20220704105955.15474-1-kabel@kernel.org/T/
-
-Signed-off-by: Marek Behún <kabel@kernel.org>
----
- arch/arm/boot/dts/armada-385-turris-omnia.dts | 7 ++-----
- 1 file changed, 2 insertions(+), 5 deletions(-)
-
-diff --git a/arch/arm/boot/dts/armada-385-turris-omnia.dts b/arch/arm/boot/dts/armada-385-turris-omnia.dts
-index f4878df39753..bcced203c612 100644
---- a/arch/arm/boot/dts/armada-385-turris-omnia.dts
-+++ b/arch/arm/boot/dts/armada-385-turris-omnia.dts
-@@ -191,15 +191,13 @@ led-controller@2b {
- 				reg = <0x2b>;
- 				#address-cells = <1>;
- 				#size-cells = <0>;
-+				status = "okay";
- 
- 				/*
- 				 * LEDs are controlled by MCU (STM32F0) at
- 				 * address 0x2b.
- 				 *
--				 * The driver does not support HW control mode
--				 * for the LEDs yet. Disable the LEDs for now.
--				 *
--				 * Also LED functions are not stable yet:
-+				 * LED functions are not stable yet:
- 				 * - there are 3 LEDs connected via MCU to PCIe
- 				 *   ports. One of these ports supports mSATA.
- 				 *   There is no mSATA nor PCIe function.
-@@ -210,7 +208,6 @@ led-controller@2b {
- 				 *   B. Again there is no such function defined.
- 				 *   For now we use LED_FUNCTION_INDICATOR
- 				 */
--				status = "disabled";
- 
- 				multi-led@0 {
- 					reg = <0x0>;
--- 
-2.37.2
-
diff --git a/nixos/modules/kernel-patches/0001-net-dsa-mv88e6xxx-disable-ATU-violation.patch b/nixos/modules/kernel-patches/0001-net-dsa-mv88e6xxx-disable-ATU-violation.patch
new file mode 100644
index 0000000..916899c
--- /dev/null
+++ b/nixos/modules/kernel-patches/0001-net-dsa-mv88e6xxx-disable-ATU-violation.patch
@@ -0,0 +1,26 @@
+From f93fc8ab929406310384688f9d86cf9c7f3ebcc6 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= <cynerd@email.cz>
+Date: Tue, 27 Sep 2022 15:57:12 +0200
+Subject: [PATCH 01/96] net/dsa/mv88e6xxx: disable ATU violation
+
+---
+ drivers/net/dsa/mv88e6xxx/chip.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
+index 0b49d243e00b..74f6db6bb804 100644
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -3425,6 +3425,9 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
+ 	else
+ 		reg = 1 << port;
+ 
++	/* Disable ATU member violation interrupt */
++	reg |= MV88E6XXX_PORT_ASSOC_VECTOR_IGNORE_WRONG;
++
+ 	err = mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_ASSOC_VECTOR,
+ 				   reg);
+ 	if (err)
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0002-dt-bindings-Add-slot-power-limit-milliwatt-PCIe-port.patch b/nixos/modules/kernel-patches/0002-dt-bindings-Add-slot-power-limit-milliwatt-PCIe-port.patch
new file mode 100644
index 0000000..7bd4e76
--- /dev/null
+++ b/nixos/modules/kernel-patches/0002-dt-bindings-Add-slot-power-limit-milliwatt-PCIe-port.patch
@@ -0,0 +1,46 @@
+From 368886a6626e6884d029ba4fe0975e9dc6499750 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Sun, 31 Oct 2021 16:07:05 +0100
+Subject: [PATCH 02/96] dt-bindings: Add 'slot-power-limit-milliwatt' PCIe port
+ property
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This property specifies slot power limit in mW unit. It is a form-factor
+and board specific value and must be initialized by hardware.
+
+Some PCIe controllers delegate this work to software to allow hardware
+flexibility and therefore this property basically specifies what should
+host bridge program into PCIe Slot Capabilities registers.
+
+The property needs to be specified in mW unit instead of the special format
+defined by Slot Capabilities (which encodes scaling factor or different
+unit). Host drivers should convert the value from mW to needed format.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ Documentation/devicetree/bindings/pci/pci.txt | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/Documentation/devicetree/bindings/pci/pci.txt b/Documentation/devicetree/bindings/pci/pci.txt
+index 6a8f2874a24d..b0cc133ed00d 100644
+--- a/Documentation/devicetree/bindings/pci/pci.txt
++++ b/Documentation/devicetree/bindings/pci/pci.txt
+@@ -32,6 +32,12 @@ driver implementation may support the following properties:
+    root port to downstream device and host bridge drivers can do programming
+    which depends on CLKREQ signal existence. For example, programming root port
+    not to advertise ASPM L1 Sub-States support if there is no CLKREQ signal.
++- slot-power-limit-milliwatt:
++   If present, this property specifies slot power limit in milliwatts. Host
++   drivers can parse this property and use it for programming Root Port or host
++   bridge, or for composing and sending PCIe Set_Slot_Power_Limit messages
++   through the Root Port or host bridge when transitioning PCIe link from a
++   non-DL_Up Status to a DL_Up Status.
+ 
+ PCI-PCI Bridge properties
+ -------------------------
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0002-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch b/nixos/modules/kernel-patches/0002-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch
deleted file mode 100644
index 1bd6acc..0000000
--- a/nixos/modules/kernel-patches/0002-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch
+++ /dev/null
@@ -1,125 +0,0 @@
-From 2760691208ad6ce37503007c0f3596532fd813e3 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
-Date: Mon, 4 Jul 2022 12:59:53 +0200
-Subject: [PATCH 02/10] leds: turris-omnia: support HW controlled mode via
- private trigger
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Add support for enabling MCU controlled mode of the Turris Omnia LEDs
-via a LED private trigger called "omnia-mcu".
-
-When in MCU controlled mode, the user can still set LED color, but the
-blinking is done by MCU, which does different things for various LEDs:
-- WAN LED is blinked according to the LED[0] pin of the WAN PHY
-- LAN LEDs are blinked according to the LED[0] output of corresponding
-  port of the LAN switch
-- PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
-  LED pins
-
-For a long time I wanted to actually do this differently: I wanted to
-make the netdev trigger to transparently offload the blinking to the HW
-if user set compatible settings for the netdev trigger.
-There was some work on this, and hopefully we will be able to complete
-it sometime, but since there are various complications, it will probably
-not be soon.
-
-In the meantime let's support HW controlled mode via this private LED
-trigger. If, in the future, we manage to complete the netdev trigger
-offloading, we can still keep this private trigger for backwards
-compatiblity, if needed.
-
-We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
-control until the user first wants to take over it. If a different
-default trigger is specified in device-tree via the
-`linux,default-trigger` property, LED class will overwrite
-cdev->default_trigger, and so the DT property will be respected.
-
-Signed-off-by: Marek Behún <kabel@kernel.org>
----
- drivers/leds/Kconfig             |  1 +
- drivers/leds/leds-turris-omnia.c | 41 ++++++++++++++++++++++++++++++++
- 2 files changed, 42 insertions(+)
-
-diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
-index a49979f41eee..bc148320416d 100644
---- a/drivers/leds/Kconfig
-+++ b/drivers/leds/Kconfig
-@@ -163,6 +163,7 @@ config LEDS_TURRIS_OMNIA
- 	depends on I2C
- 	depends on MACH_ARMADA_38X || COMPILE_TEST
- 	depends on OF
-+	select LEDS_TRIGGERS
- 	help
- 	  This option enables basic support for the LEDs found on the front
- 	  side of CZ.NIC's Turris Omnia router. There are 12 RGB LEDs on the
-diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c
-index 1adfed1c0619..c2dfb22d3065 100644
---- a/drivers/leds/leds-turris-omnia.c
-+++ b/drivers/leds/leds-turris-omnia.c
-@@ -41,6 +41,39 @@ struct omnia_leds {
- 	struct omnia_led leds[];
- };
- 
-+static struct led_hw_trigger_type omnia_hw_trigger_type;
-+
-+static int omnia_hwtrig_activate(struct led_classdev *cdev)
-+{
-+	struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
-+	struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev));
-+
-+	/* put the LED into MCU controlled mode */
-+	return i2c_smbus_write_byte_data(leds->client, CMD_LED_MODE,
-+					 CMD_LED_MODE_LED(led->reg));
-+}
-+
-+static void omnia_hwtrig_deactivate(struct led_classdev *cdev)
-+{
-+	struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
-+	struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev));
-+	int ret;
-+
-+	/* put the LED into software mode */
-+	ret = i2c_smbus_write_byte_data(leds->client, CMD_LED_MODE,
-+					CMD_LED_MODE_LED(led->reg) |
-+					CMD_LED_MODE_USER);
-+	if (ret < 0)
-+		dev_err(cdev->dev, "Cannot put to software mode: %i\n", ret);
-+}
-+
-+static struct led_trigger omnia_hw_trigger = {
-+	.name		= "omnia-mcu",
-+	.activate	= omnia_hwtrig_activate,
-+	.deactivate	= omnia_hwtrig_deactivate,
-+	.trigger_type	= &omnia_hw_trigger_type,
-+};
-+
- static int omnia_led_brightness_set_blocking(struct led_classdev *cdev,
- 					     enum led_brightness brightness)
- {
-@@ -112,6 +145,8 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
- 	cdev = &led->mc_cdev.led_cdev;
- 	cdev->max_brightness = 255;
- 	cdev->brightness_set_blocking = omnia_led_brightness_set_blocking;
-+	cdev->trigger_type = &omnia_hw_trigger_type;
-+	cdev->default_trigger = omnia_hw_trigger.name;
- 
- 	/* put the LED into software mode */
- 	ret = i2c_smbus_write_byte_data(client, CMD_LED_MODE,
-@@ -228,6 +263,12 @@ static int omnia_leds_probe(struct i2c_client *client,
- 
- 	mutex_init(&leds->lock);
- 
-+	ret = devm_led_trigger_register(dev, &omnia_hw_trigger);
-+	if (ret < 0) {
-+		dev_err(dev, "Cannot register private LED trigger: %d\n", ret);
-+		return ret;
-+	}
-+
- 	led = &leds->leds[0];
- 	for_each_available_child_of_node(np, child) {
- 		ret = omnia_led_register(client, led, child);
--- 
-2.37.2
-
diff --git a/nixos/modules/kernel-patches/0003-PCI-pci-bridge-emul-Set-position-of-PCI-capabilities.patch b/nixos/modules/kernel-patches/0003-PCI-pci-bridge-emul-Set-position-of-PCI-capabilities.patch
new file mode 100644
index 0000000..314d47f
--- /dev/null
+++ b/nixos/modules/kernel-patches/0003-PCI-pci-bridge-emul-Set-position-of-PCI-capabilities.patch
@@ -0,0 +1,164 @@
+From 5e75f896d16aa9f4fffa3c8747d948c0f0afa225 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Sun, 3 Jul 2022 12:40:13 +0200
+Subject: [PATCH 03/96] PCI: pci-bridge-emul: Set position of PCI capabilities
+ to real HW value
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+mvebu and aardvark HW have PCIe capabilities on different offset in PCI
+config space. Extend pci-bridge-emul.c code to allow setting custom driver
+custom value where PCIe capabilities starts.
+
+With this change PCIe capabilities of both drivers are reported at the same
+location as where they are reported by U-Boot - in their real HW offset.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/controller/pci-aardvark.c |  1 +
+ drivers/pci/controller/pci-mvebu.c    |  1 +
+ drivers/pci/pci-bridge-emul.c         | 48 +++++++++++++++++----------
+ drivers/pci/pci-bridge-emul.h         |  2 ++
+ 4 files changed, 35 insertions(+), 17 deletions(-)
+
+diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
+index 62db476a8651..3767b25769bc 100644
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -999,6 +999,7 @@ static int advk_sw_pci_bridge_init(struct advk_pcie *pcie)
+ 	bridge->pcie_conf.rootcap = cpu_to_le16(PCI_EXP_RTCAP_CRSVIS);
+ 
+ 	bridge->has_pcie = true;
++	bridge->pcie_start = PCIE_CORE_PCIEXP_CAP;
+ 	bridge->data = pcie;
+ 	bridge->ops = &advk_pci_bridge_emul_ops;
+ 
+diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
+index c1ffdb06c971..1589b0ba5038 100644
+--- a/drivers/pci/controller/pci-mvebu.c
++++ b/drivers/pci/controller/pci-mvebu.c
+@@ -946,6 +946,7 @@ static int mvebu_pci_bridge_emul_init(struct mvebu_pcie_port *port)
+ 	bridge->subsystem_vendor_id = ssdev_id & 0xffff;
+ 	bridge->subsystem_id = ssdev_id >> 16;
+ 	bridge->has_pcie = true;
++	bridge->pcie_start = PCIE_CAP_PCIEXP;
+ 	bridge->data = port;
+ 	bridge->ops = &mvebu_pci_bridge_emul_ops;
+ 
+diff --git a/drivers/pci/pci-bridge-emul.c b/drivers/pci/pci-bridge-emul.c
+index 9c2ca28e3ecf..9334b2dd4764 100644
+--- a/drivers/pci/pci-bridge-emul.c
++++ b/drivers/pci/pci-bridge-emul.c
+@@ -22,11 +22,7 @@
+ 
+ #define PCI_BRIDGE_CONF_END	PCI_STD_HEADER_SIZEOF
+ #define PCI_CAP_SSID_SIZEOF	(PCI_SSVID_DEVICE_ID + 2)
+-#define PCI_CAP_SSID_START	PCI_BRIDGE_CONF_END
+-#define PCI_CAP_SSID_END	(PCI_CAP_SSID_START + PCI_CAP_SSID_SIZEOF)
+ #define PCI_CAP_PCIE_SIZEOF	(PCI_EXP_SLTSTA2 + 2)
+-#define PCI_CAP_PCIE_START	PCI_CAP_SSID_END
+-#define PCI_CAP_PCIE_END	(PCI_CAP_PCIE_START + PCI_CAP_PCIE_SIZEOF)
+ 
+ /**
+  * struct pci_bridge_reg_behavior - register bits behaviors
+@@ -324,7 +320,7 @@ pci_bridge_emul_read_ssid(struct pci_bridge_emul *bridge, int reg, u32 *value)
+ 	switch (reg) {
+ 	case PCI_CAP_LIST_ID:
+ 		*value = PCI_CAP_ID_SSVID |
+-			(bridge->has_pcie ? (PCI_CAP_PCIE_START << 8) : 0);
++			((bridge->pcie_start > bridge->ssid_start) ? (bridge->pcie_start << 8) : 0);
+ 		return PCI_BRIDGE_EMUL_HANDLED;
+ 
+ 	case PCI_SSVID_VENDOR_ID:
+@@ -365,18 +361,33 @@ int pci_bridge_emul_init(struct pci_bridge_emul *bridge,
+ 	if (!bridge->pci_regs_behavior)
+ 		return -ENOMEM;
+ 
+-	if (bridge->subsystem_vendor_id)
+-		bridge->conf.capabilities_pointer = PCI_CAP_SSID_START;
+-	else if (bridge->has_pcie)
+-		bridge->conf.capabilities_pointer = PCI_CAP_PCIE_START;
+-	else
+-		bridge->conf.capabilities_pointer = 0;
++	/* If ssid_start and pcie_start were not specified then choose the lowest possible value. */
++	if (!bridge->ssid_start && !bridge->pcie_start) {
++		if (bridge->subsystem_vendor_id)
++			bridge->ssid_start = PCI_BRIDGE_CONF_END;
++		if (bridge->has_pcie)
++			bridge->pcie_start = bridge->ssid_start + PCI_CAP_SSID_SIZEOF;
++	} else if (!bridge->ssid_start && bridge->subsystem_vendor_id) {
++		if (bridge->pcie_start - PCI_BRIDGE_CONF_END >= PCI_CAP_SSID_SIZEOF)
++			bridge->ssid_start = PCI_BRIDGE_CONF_END;
++		else
++			bridge->ssid_start = bridge->pcie_start + PCI_CAP_PCIE_SIZEOF;
++	} else if (!bridge->pcie_start && bridge->has_pcie) {
++		if (bridge->ssid_start - PCI_BRIDGE_CONF_END >= PCI_CAP_PCIE_SIZEOF)
++			bridge->pcie_start = PCI_BRIDGE_CONF_END;
++		else
++			bridge->pcie_start = bridge->ssid_start + PCI_CAP_SSID_SIZEOF;
++	}
++
++	bridge->conf.capabilities_pointer = min(bridge->ssid_start, bridge->pcie_start);
+ 
+ 	if (bridge->conf.capabilities_pointer)
+ 		bridge->conf.status |= cpu_to_le16(PCI_STATUS_CAP_LIST);
+ 
+ 	if (bridge->has_pcie) {
+ 		bridge->pcie_conf.cap_id = PCI_CAP_ID_EXP;
++		bridge->pcie_conf.next = (bridge->ssid_start > bridge->pcie_start) ?
++					 bridge->ssid_start : 0;
+ 		bridge->pcie_conf.cap |= cpu_to_le16(PCI_EXP_TYPE_ROOT_PORT << 4);
+ 		bridge->pcie_cap_regs_behavior =
+ 			kmemdup(pcie_cap_regs_behavior,
+@@ -459,15 +470,17 @@ int pci_bridge_emul_conf_read(struct pci_bridge_emul *bridge, int where,
+ 		read_op = bridge->ops->read_base;
+ 		cfgspace = (__le32 *) &bridge->conf;
+ 		behavior = bridge->pci_regs_behavior;
+-	} else if (reg >= PCI_CAP_SSID_START && reg < PCI_CAP_SSID_END && bridge->subsystem_vendor_id) {
++	} else if (reg >= bridge->ssid_start && reg < bridge->ssid_start + PCI_CAP_SSID_SIZEOF &&
++		   bridge->subsystem_vendor_id) {
+ 		/* Emulated PCI Bridge Subsystem Vendor ID capability */
+-		reg -= PCI_CAP_SSID_START;
++		reg -= bridge->ssid_start;
+ 		read_op = pci_bridge_emul_read_ssid;
+ 		cfgspace = NULL;
+ 		behavior = NULL;
+-	} else if (reg >= PCI_CAP_PCIE_START && reg < PCI_CAP_PCIE_END && bridge->has_pcie) {
++	} else if (reg >= bridge->pcie_start && reg < bridge->pcie_start + PCI_CAP_PCIE_SIZEOF &&
++		   bridge->has_pcie) {
+ 		/* Our emulated PCIe capability */
+-		reg -= PCI_CAP_PCIE_START;
++		reg -= bridge->pcie_start;
+ 		read_op = bridge->ops->read_pcie;
+ 		cfgspace = (__le32 *) &bridge->pcie_conf;
+ 		behavior = bridge->pcie_cap_regs_behavior;
+@@ -538,9 +551,10 @@ int pci_bridge_emul_conf_write(struct pci_bridge_emul *bridge, int where,
+ 		write_op = bridge->ops->write_base;
+ 		cfgspace = (__le32 *) &bridge->conf;
+ 		behavior = bridge->pci_regs_behavior;
+-	} else if (reg >= PCI_CAP_PCIE_START && reg < PCI_CAP_PCIE_END && bridge->has_pcie) {
++	} else if (reg >= bridge->pcie_start && reg < bridge->pcie_start + PCI_CAP_PCIE_SIZEOF &&
++		   bridge->has_pcie) {
+ 		/* Our emulated PCIe capability */
+-		reg -= PCI_CAP_PCIE_START;
++		reg -= bridge->pcie_start;
+ 		write_op = bridge->ops->write_pcie;
+ 		cfgspace = (__le32 *) &bridge->pcie_conf;
+ 		behavior = bridge->pcie_cap_regs_behavior;
+diff --git a/drivers/pci/pci-bridge-emul.h b/drivers/pci/pci-bridge-emul.h
+index 71392b67471d..2a0e59c7f0d9 100644
+--- a/drivers/pci/pci-bridge-emul.h
++++ b/drivers/pci/pci-bridge-emul.h
+@@ -131,6 +131,8 @@ struct pci_bridge_emul {
+ 	struct pci_bridge_reg_behavior *pci_regs_behavior;
+ 	struct pci_bridge_reg_behavior *pcie_cap_regs_behavior;
+ 	void *data;
++	u8 pcie_start;
++	u8 ssid_start;
+ 	bool has_pcie;
+ 	u16 subsystem_vendor_id;
+ 	u16 subsystem_id;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0003-leds-turris-omnia-initialize-multi-intensity-to-full.patch b/nixos/modules/kernel-patches/0003-leds-turris-omnia-initialize-multi-intensity-to-full.patch
deleted file mode 100644
index 4f9cee4..0000000
--- a/nixos/modules/kernel-patches/0003-leds-turris-omnia-initialize-multi-intensity-to-full.patch
+++ /dev/null
@@ -1,38 +0,0 @@
-From 6ad5109b744e8f10bb14f948b75c38b639f97989 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
-Date: Mon, 4 Jul 2022 12:59:54 +0200
-Subject: [PATCH 03/10] leds: turris-omnia: initialize multi-intensity to full
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-The default color of each LED before driver probe (255, 255, 255).
-Initialize multi_intensity to this value, so that it corresponds to the
-reality.
-
-Signed-off-by: Marek Behún <kabel@kernel.org>
----
- drivers/leds/leds-turris-omnia.c | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c
-index c2dfb22d3065..fae155bd119c 100644
---- a/drivers/leds/leds-turris-omnia.c
-+++ b/drivers/leds/leds-turris-omnia.c
-@@ -131,10 +131,13 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
- 	}
- 
- 	led->subled_info[0].color_index = LED_COLOR_ID_RED;
-+	led->subled_info[0].intensity = 255;
- 	led->subled_info[0].channel = 0;
- 	led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
-+	led->subled_info[1].intensity = 255;
- 	led->subled_info[1].channel = 1;
- 	led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
-+	led->subled_info[2].intensity = 255;
- 	led->subled_info[2].channel = 2;
- 
- 	led->mc_cdev.subled_info = led->subled_info;
--- 
-2.37.2
-
diff --git a/nixos/modules/kernel-patches/0004-PCI-mvebu-Use-devm_request_irq-for-registering-inter.patch b/nixos/modules/kernel-patches/0004-PCI-mvebu-Use-devm_request_irq-for-registering-inter.patch
new file mode 100644
index 0000000..b985bfe
--- /dev/null
+++ b/nixos/modules/kernel-patches/0004-PCI-mvebu-Use-devm_request_irq-for-registering-inter.patch
@@ -0,0 +1,99 @@
+From fdafc999a4a33e028b52fba4c1378766b7722cab Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Tue, 24 May 2022 13:57:37 +0200
+Subject: [PATCH 04/96] PCI: mvebu: Use devm_request_irq() for registering
+ interrupt handler
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Same as in commit a3b69dd0ad62 ("Revert "PCI: aardvark: Rewrite IRQ code to
+chained IRQ handler"") for pci-aardvark driver, use devm_request_irq()
+instead of chained IRQ handler in pci-mvebu.c driver.
+
+This change fixes affinity support and allows to pin interrupts from
+different PCIe controllers to different CPU cores.
+
+Fixes: ec075262648f ("PCI: mvebu: Implement support for legacy INTx interrupts")
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/controller/pci-mvebu.c | 30 +++++++++++++++++-------------
+ 1 file changed, 17 insertions(+), 13 deletions(-)
+
+diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
+index 1589b0ba5038..73d8fb6952e6 100644
+--- a/drivers/pci/controller/pci-mvebu.c
++++ b/drivers/pci/controller/pci-mvebu.c
+@@ -1090,16 +1090,13 @@ static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
+ 	return 0;
+ }
+ 
+-static void mvebu_pcie_irq_handler(struct irq_desc *desc)
++static irqreturn_t mvebu_pcie_irq_handler(int irq, void *arg)
+ {
+-	struct mvebu_pcie_port *port = irq_desc_get_handler_data(desc);
+-	struct irq_chip *chip = irq_desc_get_chip(desc);
++	struct mvebu_pcie_port *port = arg;
+ 	struct device *dev = &port->pcie->pdev->dev;
+ 	u32 cause, unmask, status;
+ 	int i;
+ 
+-	chained_irq_enter(chip, desc);
+-
+ 	cause = mvebu_readl(port, PCIE_INT_CAUSE_OFF);
+ 	unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ 	status = cause & unmask;
+@@ -1113,7 +1110,7 @@ static void mvebu_pcie_irq_handler(struct irq_desc *desc)
+ 			dev_err_ratelimited(dev, "unexpected INT%c IRQ\n", (char)i+'A');
+ 	}
+ 
+-	chained_irq_exit(chip, desc);
++	return status ? IRQ_HANDLED : IRQ_NONE;
+ }
+ 
+ static int mvebu_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
+@@ -1573,9 +1570,20 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
+ 				mvebu_pcie_powerdown(port);
+ 				continue;
+ 			}
+-			irq_set_chained_handler_and_data(irq,
+-							 mvebu_pcie_irq_handler,
+-							 port);
++
++			ret = devm_request_irq(dev, irq, mvebu_pcie_irq_handler,
++					       IRQF_SHARED | IRQF_NO_THREAD,
++					       port->name, port);
++			if (ret) {
++				dev_err(dev, "%s: cannot register interrupt handler: %d\n",
++					port->name, ret);
++				irq_domain_remove(port->intx_irq_domain);
++				pci_bridge_emul_cleanup(&port->bridge);
++				devm_iounmap(dev, port->base);
++				port->base = NULL;
++				mvebu_pcie_powerdown(port);
++				continue;
++			}
+ 		}
+ 
+ 		/*
+@@ -1682,7 +1690,6 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
+ 
+ 	for (i = 0; i < pcie->nports; i++) {
+ 		struct mvebu_pcie_port *port = &pcie->ports[i];
+-		int irq = port->intx_irq;
+ 
+ 		if (!port->base)
+ 			continue;
+@@ -1698,9 +1705,6 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
+ 		/* Clear all interrupt causes. */
+ 		mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
+ 
+-		if (irq > 0)
+-			irq_set_chained_handler_and_data(irq, NULL, NULL);
+-
+ 		/* Remove IRQ domains. */
+ 		if (port->intx_irq_domain)
+ 			irq_domain_remove(port->intx_irq_domain);
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0004-leds-turris-omnia-change-max-brightness-from-255-to-.patch b/nixos/modules/kernel-patches/0004-leds-turris-omnia-change-max-brightness-from-255-to-.patch
deleted file mode 100644
index 9f63b79..0000000
--- a/nixos/modules/kernel-patches/0004-leds-turris-omnia-change-max-brightness-from-255-to-.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-From 74951d542c10fbb181454c31d28075170528a14f Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
-Date: Mon, 4 Jul 2022 12:59:55 +0200
-Subject: [PATCH 04/10] leds: turris-omnia: change max brightness from 255 to 1
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Using binary brightness makes more sense for this controller, because
-internally in the MCU it works that way: the LED has a color, and a
-state whether it is ON or OFF.
-
-The resulting brightness computation with led_mc_calc_color_components()
-will now always result in either (0, 0, 0) or the multi_intensity value.
-
-Signed-off-by: Marek Behún <kabel@kernel.org>
----
- drivers/leds/leds-turris-omnia.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c
-index fae155bd119c..f53bdc3f4cea 100644
---- a/drivers/leds/leds-turris-omnia.c
-+++ b/drivers/leds/leds-turris-omnia.c
-@@ -146,7 +146,7 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
- 	init_data.fwnode = &np->fwnode;
- 
- 	cdev = &led->mc_cdev.led_cdev;
--	cdev->max_brightness = 255;
-+	cdev->max_brightness = 1;
- 	cdev->brightness_set_blocking = omnia_led_brightness_set_blocking;
- 	cdev->trigger_type = &omnia_hw_trigger_type;
- 	cdev->default_trigger = omnia_hw_trigger.name;
--- 
-2.37.2
-
diff --git a/nixos/modules/kernel-patches/0005-PCI-mvebu-Dispose-INTx-irqs-prior-to-removing-INTx-d.patch b/nixos/modules/kernel-patches/0005-PCI-mvebu-Dispose-INTx-irqs-prior-to-removing-INTx-d.patch
new file mode 100644
index 0000000..b5d1651
--- /dev/null
+++ b/nixos/modules/kernel-patches/0005-PCI-mvebu-Dispose-INTx-irqs-prior-to-removing-INTx-d.patch
@@ -0,0 +1,47 @@
+From c9259b56b015aee9c5984cba54912e78d2abf74f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Sat, 9 Jul 2022 16:12:40 +0200
+Subject: [PATCH 05/96] PCI: mvebu: Dispose INTx irqs prior to removing INTx
+ domain
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Documentation for irq_domain_remove() says that all mapping within the
+domain must be disposed prior to domain remove.
+
+Currently INTx irqs are not disposed in pci-mvebu.c device unbind callback
+which cause that kernel crashes after unloading driver and trying to read
+/sys/kernel/debug/irq/irqs/<num> or /proc/interrupts.
+
+Fixes: ec075262648f ("PCI: mvebu: Implement support for legacy INTx interrupts")
+Reported-by: Hajo Noerenberg <hajo-linux-bugzilla@noerenberg.de>
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/controller/pci-mvebu.c | 9 ++++++++-
+ 1 file changed, 8 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
+index 73d8fb6952e6..159167ebb2de 100644
+--- a/drivers/pci/controller/pci-mvebu.c
++++ b/drivers/pci/controller/pci-mvebu.c
+@@ -1706,8 +1706,15 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
+ 		mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
+ 
+ 		/* Remove IRQ domains. */
+-		if (port->intx_irq_domain)
++		if (port->intx_irq_domain) {
++			int virq, j;
++			for (j = 0; j < PCI_NUM_INTX; j++) {
++				virq = irq_find_mapping(port->intx_irq_domain, j);
++				if (virq > 0)
++					irq_dispose_mapping(virq);
++			}
+ 			irq_domain_remove(port->intx_irq_domain);
++		}
+ 
+ 		/* Free config space for emulated root bridge. */
+ 		pci_bridge_emul_cleanup(&port->bridge);
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0005-generic-Mangle-bootloader-s-kernel-arguments.patch b/nixos/modules/kernel-patches/0005-generic-Mangle-bootloader-s-kernel-arguments.patch
deleted file mode 100644
index 4eafb31..0000000
--- a/nixos/modules/kernel-patches/0005-generic-Mangle-bootloader-s-kernel-arguments.patch
+++ /dev/null
@@ -1,217 +0,0 @@
-From 7f478ddf690dfccd8aea406a9697e1448920bf0f Mon Sep 17 00:00:00 2001
-From: Adrian Panella <ianchi74@outlook.com>
-Date: Thu, 9 Mar 2017 09:37:17 +0100
-Subject: [PATCH 05/10] generic: Mangle bootloader's kernel arguments
-
-The command-line arguments provided by the boot loader will be
-appended to a new device tree property: bootloader-args.
-If there is a property "append-rootblock" in DT under /chosen
-and a root= option in bootloaders command line it will be parsed
-and added to DT bootargs with the form: <append-rootblock>XX.
-Only command line ATAG will be processed, the rest of the ATAGs
-sent by bootloader will be ignored.
-This is usefull in dual boot systems, to get the current root partition
-without afecting the rest of the system.
-
-Signed-off-by: Adrian Panella <ianchi74@outlook.com>
-
-This patch has been modified to be mvebu specific. The original patch
-did not pass the bootloader cmdline on if no append-rootblock stanza
-was found, resulting in blank cmdline and failure to boot.
-
-Signed-off-by: Michael Gray <michael.gray@lantisproject.com>
----
- arch/arm/Kconfig                        | 11 ++++
- arch/arm/boot/compressed/atags_to_fdt.c | 85 ++++++++++++++++++++++++-
- init/main.c                             | 16 +++++
- 3 files changed, 111 insertions(+), 1 deletion(-)
-
-diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
-index 7630ba9cb6cc..dae040cc2a4f 100644
---- a/arch/arm/Kconfig
-+++ b/arch/arm/Kconfig
-@@ -1638,6 +1638,17 @@ config ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEND
- 	  The command-line arguments provided by the boot loader will be
- 	  appended to the the device tree bootargs property.
- 
-+config ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE
-+	bool "Append rootblock parsing bootloader's kernel arguments"
-+	help
-+	  The command-line arguments provided by the boot loader will be
-+	  appended to a new device tree property: bootloader-args.
-+	  If there is a property "append-rootblock" in DT under /chosen 
-+	  and a root= option in bootloaders command line it will be parsed 
-+	  and added to DT bootargs with the form: <append-rootblock>XX.
-+	  Only command line ATAG will be processed, the rest of the ATAGs
-+	  sent by bootloader will be ignored.
-+
- endchoice
- 
- config CMDLINE
-diff --git a/arch/arm/boot/compressed/atags_to_fdt.c b/arch/arm/boot/compressed/atags_to_fdt.c
-index 1feb6b0f7a1f..fb8cd4f07e60 100644
---- a/arch/arm/boot/compressed/atags_to_fdt.c
-+++ b/arch/arm/boot/compressed/atags_to_fdt.c
-@@ -5,6 +5,8 @@
- 
- #if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEND)
- #define do_extend_cmdline 1
-+#elif defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
-+#define do_extend_cmdline 1
- #else
- #define do_extend_cmdline 0
- #endif
-@@ -69,6 +71,72 @@ static uint32_t get_cell_size(const void *fdt)
- 	return cell_size;
- }
- 
-+#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
-+
-+static char *append_rootblock(char *dest, const char *str, int len, void *fdt)
-+{
-+	char *ptr, *end;
-+	char *root="root=";
-+	int i, l;
-+	const char *rootblock;
-+
-+	//ARM doesn't have __HAVE_ARCH_STRSTR, so search manually
-+	ptr = str - 1;
-+
-+	do {
-+		//first find an 'r' at the begining or after a space
-+		do {
-+			ptr++;
-+			ptr = strchr(ptr, 'r');
-+			if (!ptr)
-+				goto no_append;
-+
-+		} while (ptr != str && *(ptr-1) != ' ');
-+
-+		//then check for the rest
-+		for(i = 1; i <= 4; i++)
-+			if(*(ptr+i) != *(root+i)) break;
-+
-+	} while (i != 5);
-+
-+	end = strchr(ptr, ' ');
-+	end = end ? (end - 1) : (strchr(ptr, 0) - 1);
-+
-+	//find partition number (assumes format root=/dev/mtdXX | /dev/mtdblockXX | yy:XX )
-+	for( i = 0; end >= ptr && *end >= '0' && *end <= '9'; end--, i++);
-+	ptr = end + 1;
-+
-+	/* if append-rootblock property is set use it to append to command line */
-+	rootblock = getprop(fdt, "/chosen", "append-rootblock", &l);
-+	if (rootblock == NULL)
-+		goto no_append;
-+
-+	if (*dest != ' ') {
-+		*dest = ' ';
-+		dest++;
-+		len++;
-+	}
-+
-+	if (len + l + i <= COMMAND_LINE_SIZE) {
-+		memcpy(dest, rootblock, l);
-+		dest += l - 1;
-+		memcpy(dest, ptr, i);
-+		dest += i;
-+	}
-+
-+	return dest;
-+
-+no_append:
-+	len = strlen(str);
-+	if (len + 1 < COMMAND_LINE_SIZE) {
-+		memcpy(dest, str, len);
-+		dest += len;
-+	}
-+
-+	return dest;
-+}
-+#endif
-+
- static void merge_fdt_bootargs(void *fdt, const char *fdt_cmdline)
- {
- 	char cmdline[COMMAND_LINE_SIZE];
-@@ -88,12 +156,21 @@ static void merge_fdt_bootargs(void *fdt, const char *fdt_cmdline)
- 
- 	/* and append the ATAG_CMDLINE */
- 	if (fdt_cmdline) {
-+
-+#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
-+		//save original bootloader args
-+		//and append ubi.mtd with root partition number to current cmdline
-+		setprop_string(fdt, "/chosen", "bootloader-args", fdt_cmdline);
-+		ptr = append_rootblock(ptr, fdt_cmdline, len, fdt);
-+
-+#else
- 		len = strlen(fdt_cmdline);
- 		if (ptr - cmdline + len + 2 < COMMAND_LINE_SIZE) {
- 			*ptr++ = ' ';
- 			memcpy(ptr, fdt_cmdline, len);
- 			ptr += len;
- 		}
-+#endif
- 	}
- 	*ptr = '\0';
- 
-@@ -168,7 +245,9 @@ int atags_to_fdt(void *atag_list, void *fdt, int total_space)
- 			else
- 				setprop_string(fdt, "/chosen", "bootargs",
- 					       atag->u.cmdline.cmdline);
--		} else if (atag->hdr.tag == ATAG_MEM) {
-+		}
-+#ifndef CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE
-+		else if (atag->hdr.tag == ATAG_MEM) {
- 			if (memcount >= sizeof(mem_reg_property)/4)
- 				continue;
- 			if (!atag->u.mem.size)
-@@ -212,6 +291,10 @@ int atags_to_fdt(void *atag_list, void *fdt, int total_space)
- 		setprop(fdt, "/memory", "reg", mem_reg_property,
- 			4 * memcount * memsize);
- 	}
-+#else
-+
-+	}
-+#endif
- 
- 	return fdt_pack(fdt);
- }
-diff --git a/init/main.c b/init/main.c
-index 1fe7942f5d4a..fa08c129d3e1 100644
---- a/init/main.c
-+++ b/init/main.c
-@@ -113,6 +113,10 @@
- 
- #include <kunit/test.h>
- 
-+#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
-+#include <linux/of.h>
-+#endif
-+
- static int kernel_init(void *);
- 
- extern void init_IRQ(void);
-@@ -961,6 +965,18 @@ asmlinkage __visible void __init __no_sanitize_address start_kernel(void)
- 	page_alloc_init();
- 
- 	pr_notice("Kernel command line: %s\n", saved_command_line);
-+
-+#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
-+	//Show bootloader's original command line for reference
-+	if(of_chosen) {
-+		const char *prop = of_get_property(of_chosen, "bootloader-args", NULL);
-+		if(prop)
-+			pr_notice("Bootloader command line (ignored): %s\n", prop);
-+		else
-+			pr_notice("Bootloader command line not present\n");
-+	}
-+#endif
-+
- 	/* parameters may set static keys */
- 	jump_label_init();
- 	parse_early_param();
--- 
-2.37.2
-
diff --git a/nixos/modules/kernel-patches/0006-PCI-Assign-PCI-domain-by-ida_alloc.patch b/nixos/modules/kernel-patches/0006-PCI-Assign-PCI-domain-by-ida_alloc.patch
new file mode 100644
index 0000000..27911fa
--- /dev/null
+++ b/nixos/modules/kernel-patches/0006-PCI-Assign-PCI-domain-by-ida_alloc.patch
@@ -0,0 +1,215 @@
+From 18001a289af20564c1f17fb5bcc261c02bbb70d2 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Sat, 2 Jul 2022 21:37:51 +0200
+Subject: [PATCH 06/96] PCI: Assign PCI domain by ida_alloc()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Replace assignment of PCI domain from atomic_inc_return() to ida_alloc().
+
+Use two IDAs, one for static domain allocations (those which are defined in
+device tree) and second for dynamic allocations (all other).
+
+During removal of root bus / host bridge release also allocated domain id.
+So released id can be reused again, for example in situation when
+dynamically loading and unloading native PCI host bridge drivers.
+
+This change also allows to mix static device tree assignment and dynamic by
+kernel as all static allocations are reserved in dynamic pool.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/pci.c    | 103 +++++++++++++++++++++++++------------------
+ drivers/pci/probe.c  |   5 +++
+ drivers/pci/remove.c |   6 +++
+ include/linux/pci.h  |   1 +
+ 4 files changed, 72 insertions(+), 43 deletions(-)
+
+diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
+index cfaf40a540a8..34fdcee6634a 100644
+--- a/drivers/pci/pci.c
++++ b/drivers/pci/pci.c
+@@ -6762,60 +6762,70 @@ static void pci_no_domains(void)
+ }
+ 
+ #ifdef CONFIG_PCI_DOMAINS_GENERIC
+-static atomic_t __domain_nr = ATOMIC_INIT(-1);
++static DEFINE_IDA(pci_domain_nr_static_ida);
++static DEFINE_IDA(pci_domain_nr_dynamic_ida);
+ 
+-static int pci_get_new_domain_nr(void)
++static void of_pci_reserve_static_domain_nr(void)
+ {
+-	return atomic_inc_return(&__domain_nr);
++	struct device_node *np;
++	int domain_nr;
++
++	for_each_node_by_type(np, "pci") {
++		domain_nr = of_get_pci_domain_nr(np);
++		if (domain_nr < 0)
++			continue;
++		/*
++		 * Permanently allocate domain_nr in dynamic_ida
++		 * to prevent it from dynamic allocation.
++		 */
++		ida_alloc_range(&pci_domain_nr_dynamic_ida,
++				domain_nr, domain_nr, GFP_KERNEL);
++	}
+ }
+ 
+ static int of_pci_bus_find_domain_nr(struct device *parent)
+ {
+-	static int use_dt_domains = -1;
+-	int domain = -1;
++	static bool static_domains_reserved = false;
++	int domain_nr;
+ 
+-	if (parent)
+-		domain = of_get_pci_domain_nr(parent->of_node);
++	/* On the first call scan device tree for static allocations. */
++	if (!static_domains_reserved) {
++		of_pci_reserve_static_domain_nr();
++		static_domains_reserved = true;
++	}
++
++	if (parent) {
++		/*
++		 * If domain is in DT then allocate it in static IDA.
++		 * This prevent duplicate static allocations in case
++		 * of errors in DT.
++		 */
++		domain_nr = of_get_pci_domain_nr(parent->of_node);
++		if (domain_nr >= 0)
++			return ida_alloc_range(&pci_domain_nr_static_ida,
++					       domain_nr, domain_nr,
++					       GFP_KERNEL);
++	}
+ 
+ 	/*
+-	 * Check DT domain and use_dt_domains values.
+-	 *
+-	 * If DT domain property is valid (domain >= 0) and
+-	 * use_dt_domains != 0, the DT assignment is valid since this means
+-	 * we have not previously allocated a domain number by using
+-	 * pci_get_new_domain_nr(); we should also update use_dt_domains to
+-	 * 1, to indicate that we have just assigned a domain number from
+-	 * DT.
+-	 *
+-	 * If DT domain property value is not valid (ie domain < 0), and we
+-	 * have not previously assigned a domain number from DT
+-	 * (use_dt_domains != 1) we should assign a domain number by
+-	 * using the:
+-	 *
+-	 * pci_get_new_domain_nr()
+-	 *
+-	 * API and update the use_dt_domains value to keep track of method we
+-	 * are using to assign domain numbers (use_dt_domains = 0).
+-	 *
+-	 * All other combinations imply we have a platform that is trying
+-	 * to mix domain numbers obtained from DT and pci_get_new_domain_nr(),
+-	 * which is a recipe for domain mishandling and it is prevented by
+-	 * invalidating the domain value (domain = -1) and printing a
+-	 * corresponding error.
++	 * If domain was not specified in DT then choose free id from dynamic
++	 * allocations. All domain numbers from DT are permanently in dynamic
++	 * allocations to prevent assigning them to other DT nodes without
++	 * static domain.
+ 	 */
+-	if (domain >= 0 && use_dt_domains) {
+-		use_dt_domains = 1;
+-	} else if (domain < 0 && use_dt_domains != 1) {
+-		use_dt_domains = 0;
+-		domain = pci_get_new_domain_nr();
+-	} else {
+-		if (parent)
+-			pr_err("Node %pOF has ", parent->of_node);
+-		pr_err("Inconsistent \"linux,pci-domain\" property in DT\n");
+-		domain = -1;
+-	}
++	return ida_alloc(&pci_domain_nr_dynamic_ida, GFP_KERNEL);
++}
+ 
+-	return domain;
++static void of_pci_bus_release_domain_nr(struct pci_bus *bus, struct device *parent)
++{
++	if (bus->domain_nr < 0)
++		return;
++
++	/* Release domain from ida in which was it allocated. */
++	if (of_get_pci_domain_nr(parent->of_node) == bus->domain_nr)
++		ida_free(&pci_domain_nr_static_ida, bus->domain_nr);
++	else
++		ida_free(&pci_domain_nr_dynamic_ida, bus->domain_nr);
+ }
+ 
+ int pci_bus_find_domain_nr(struct pci_bus *bus, struct device *parent)
+@@ -6823,6 +6833,13 @@ int pci_bus_find_domain_nr(struct pci_bus *bus, struct device *parent)
+ 	return acpi_disabled ? of_pci_bus_find_domain_nr(parent) :
+ 			       acpi_pci_bus_find_domain_nr(bus);
+ }
++
++void pci_bus_release_domain_nr(struct pci_bus *bus, struct device *parent)
++{
++	if (!acpi_disabled)
++		return;
++	of_pci_bus_release_domain_nr(bus, parent);
++}
+ #endif
+ 
+ /**
+diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
+index 17a969942d37..12092d238403 100644
+--- a/drivers/pci/probe.c
++++ b/drivers/pci/probe.c
+@@ -906,6 +906,8 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
+ 		bus->domain_nr = pci_bus_find_domain_nr(bus, parent);
+ 	else
+ 		bus->domain_nr = bridge->domain_nr;
++	if (bus->domain_nr < 0)
++		goto free;
+ #endif
+ 
+ 	b = pci_find_bus(pci_domain_nr(bus), bridge->busnr);
+@@ -1030,6 +1032,9 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
+ 	device_del(&bridge->dev);
+ 
+ free:
++#ifdef CONFIG_PCI_DOMAINS_GENERIC
++	pci_bus_release_domain_nr(bus, parent);
++#endif
+ 	kfree(bus);
+ 	return err;
+ }
+diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c
+index 4c54c75050dc..0145aef1b930 100644
+--- a/drivers/pci/remove.c
++++ b/drivers/pci/remove.c
+@@ -160,6 +160,12 @@ void pci_remove_root_bus(struct pci_bus *bus)
+ 	pci_remove_bus(bus);
+ 	host_bridge->bus = NULL;
+ 
++#ifdef CONFIG_PCI_DOMAINS_GENERIC
++	/* Release domain_nr if it was dynamically allocated */
++	if (host_bridge->domain_nr == PCI_DOMAIN_NR_NOT_SET)
++		pci_bus_release_domain_nr(bus, host_bridge->dev.parent);
++#endif
++
+ 	/* remove the host bridge */
+ 	device_del(&host_bridge->dev);
+ }
+diff --git a/include/linux/pci.h b/include/linux/pci.h
+index 81a57b498f22..6c7f27e62bcc 100644
+--- a/include/linux/pci.h
++++ b/include/linux/pci.h
+@@ -1723,6 +1723,7 @@ static inline int acpi_pci_bus_find_domain_nr(struct pci_bus *bus)
+ { return 0; }
+ #endif
+ int pci_bus_find_domain_nr(struct pci_bus *bus, struct device *parent);
++void pci_bus_release_domain_nr(struct pci_bus *bus, struct device *parent);
+ #endif
+ 
+ /* Some architectures require additional setup to direct VGA traffic */
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0006-cpuidle-mvebu-indicate-failure-to-enter-deeper-sleep.patch b/nixos/modules/kernel-patches/0006-cpuidle-mvebu-indicate-failure-to-enter-deeper-sleep.patch
deleted file mode 100644
index 527a4f7..0000000
--- a/nixos/modules/kernel-patches/0006-cpuidle-mvebu-indicate-failure-to-enter-deeper-sleep.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-From 0ac42f2a36490ee410b7c88720652ded4ec6beff Mon Sep 17 00:00:00 2001
-From: Russell King <rmk+kernel@arm.linux.org.uk>
-Date: Sat, 3 Oct 2015 09:13:05 +0100
-Subject: [PATCH 06/10] cpuidle: mvebu: indicate failure to enter deeper sleep
- states
-
-The cpuidle ->enter method expects the return value to be the sleep
-state we entered.  Returning negative numbers or other codes is not
-permissible since coupled CPU idle was merged.
-
-At least some of the mvebu_v7_cpu_suspend() implementations return the
-value from cpu_suspend(), which returns zero if the CPU vectors back
-into the kernel via cpu_resume() (the success case), or the non-zero
-return value of the suspend actor, or one (failure cases).
-
-We do not want to be returning the failure case value back to CPU idle
-as that indicates that we successfully entered one of the deeper idle
-states.  Always return zero instead, indicating that we slept for the
-shortest amount of time.
-
-Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
----
- drivers/cpuidle/cpuidle-mvebu-v7.c | 6 +++++-
- 1 file changed, 5 insertions(+), 1 deletion(-)
-
-diff --git a/drivers/cpuidle/cpuidle-mvebu-v7.c b/drivers/cpuidle/cpuidle-mvebu-v7.c
-index 01a856971f05..18ded9e7cb34 100644
---- a/drivers/cpuidle/cpuidle-mvebu-v7.c
-+++ b/drivers/cpuidle/cpuidle-mvebu-v7.c
-@@ -39,8 +39,12 @@ static int mvebu_v7_enter_idle(struct cpuidle_device *dev,
- 	ret = mvebu_v7_cpu_suspend(deepidle);
- 	cpu_pm_exit();
- 
-+	/*
-+	 * If we failed to enter the desired state, indicate that we
-+	 * slept lightly.
-+	 */
- 	if (ret)
--		return ret;
-+		return 0;
- 
- 	return index;
- }
--- 
-2.37.2
-
diff --git a/nixos/modules/kernel-patches/0007-PCI-mvebu-Fix-endianity-when-accessing-pci-emul-brid.patch b/nixos/modules/kernel-patches/0007-PCI-mvebu-Fix-endianity-when-accessing-pci-emul-brid.patch
new file mode 100644
index 0000000..566c793
--- /dev/null
+++ b/nixos/modules/kernel-patches/0007-PCI-mvebu-Fix-endianity-when-accessing-pci-emul-brid.patch
@@ -0,0 +1,68 @@
+From b788cf679463d8805bee225400edbd09b7d6bd0e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Fri, 12 Aug 2022 11:09:11 +0200
+Subject: [PATCH 07/96] PCI: mvebu: Fix endianity when accessing pci emul
+ bridge members
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+PCI emul bridge members iolimitupper, iobaseupper, memlimit and membase are
+of type __le16, so correctly access these members via le16_to_cpu() macros.
+
+Fixes: 4ded69473adb ("PCI: mvebu: Propagate errors when updating PCI_IO_BASE and PCI_MEM_BASE registers")
+Reported-by: kernel test robot <lkp@intel.com>
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/controller/pci-mvebu.c | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
+index 159167ebb2de..cacc78863915 100644
+--- a/drivers/pci/controller/pci-mvebu.c
++++ b/drivers/pci/controller/pci-mvebu.c
+@@ -523,7 +523,7 @@ static int mvebu_pcie_handle_iobase_change(struct mvebu_pcie_port *port)
+ 
+ 	/* Are the new iobase/iolimit values invalid? */
+ 	if (conf->iolimit < conf->iobase ||
+-	    conf->iolimitupper < conf->iobaseupper)
++	    le16_to_cpu(conf->iolimitupper) < le16_to_cpu(conf->iobaseupper))
+ 		return mvebu_pcie_set_window(port, port->io_target, port->io_attr,
+ 					     &desired, &port->iowin);
+ 
+@@ -535,10 +535,10 @@ static int mvebu_pcie_handle_iobase_change(struct mvebu_pcie_port *port)
+ 	 * is the CPU address.
+ 	 */
+ 	desired.remap = ((conf->iobase & 0xF0) << 8) |
+-			(conf->iobaseupper << 16);
++			(le16_to_cpu(conf->iobaseupper) << 16);
+ 	desired.base = port->pcie->io.start + desired.remap;
+ 	desired.size = ((0xFFF | ((conf->iolimit & 0xF0) << 8) |
+-			 (conf->iolimitupper << 16)) -
++			 (le16_to_cpu(conf->iolimitupper) << 16)) -
+ 			desired.remap) +
+ 		       1;
+ 
+@@ -552,7 +552,7 @@ static int mvebu_pcie_handle_membase_change(struct mvebu_pcie_port *port)
+ 	struct pci_bridge_emul_conf *conf = &port->bridge.conf;
+ 
+ 	/* Are the new membase/memlimit values invalid? */
+-	if (conf->memlimit < conf->membase)
++	if (le16_to_cpu(conf->memlimit) < le16_to_cpu(conf->membase))
+ 		return mvebu_pcie_set_window(port, port->mem_target, port->mem_attr,
+ 					     &desired, &port->memwin);
+ 
+@@ -562,8 +562,8 @@ static int mvebu_pcie_handle_membase_change(struct mvebu_pcie_port *port)
+ 	 * window to setup, according to the PCI-to-PCI bridge
+ 	 * specifications.
+ 	 */
+-	desired.base = ((conf->membase & 0xFFF0) << 16);
+-	desired.size = (((conf->memlimit & 0xFFF0) << 16) | 0xFFFFF) -
++	desired.base = ((le16_to_cpu(conf->membase) & 0xFFF0) << 16);
++	desired.size = (((le16_to_cpu(conf->memlimit) & 0xFFF0) << 16) | 0xFFFFF) -
+ 		       desired.base + 1;
+ 
+ 	return mvebu_pcie_set_window(port, port->mem_target, port->mem_attr, &desired,
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0007-pci-mvebu-time-out-reset-on-link-up.patch b/nixos/modules/kernel-patches/0007-pci-mvebu-time-out-reset-on-link-up.patch
deleted file mode 100644
index b163995..0000000
--- a/nixos/modules/kernel-patches/0007-pci-mvebu-time-out-reset-on-link-up.patch
+++ /dev/null
@@ -1,65 +0,0 @@
-From 2793db3bb1f859bde77b40fd27da1452a06daf8d Mon Sep 17 00:00:00 2001
-From: Russell King <rmk+kernel@armlinux.org.uk>
-Date: Sat, 9 Jul 2016 10:58:16 +0100
-Subject: [PATCH 07/10] pci: mvebu: time out reset on link up
-
-If the port reports that the link is up while we are resetting, there's
-little point in waiting for the full duration.
-
-Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
----
- drivers/pci/controller/pci-mvebu.c | 20 ++++++++++++++------
- 1 file changed, 14 insertions(+), 6 deletions(-)
-
-diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
-index c1ffdb06c971..b02f8120c24b 100644
---- a/drivers/pci/controller/pci-mvebu.c
-+++ b/drivers/pci/controller/pci-mvebu.c
-@@ -1415,6 +1415,7 @@ static int mvebu_pcie_powerup(struct mvebu_pcie_port *port)
- 
- 	if (port->reset_gpio) {
- 		u32 reset_udelay = PCI_PM_D3COLD_WAIT * 1000;
-+		unsigned int i;
- 
- 		of_property_read_u32(port->dn, "reset-delay-us",
- 				     &reset_udelay);
-@@ -1422,7 +1423,13 @@ static int mvebu_pcie_powerup(struct mvebu_pcie_port *port)
- 		udelay(100);
- 
- 		gpiod_set_value_cansleep(port->reset_gpio, 0);
--		msleep(reset_udelay / 1000);
-+		for (i = 0; i < reset_udelay; i += 1000) {
-+			if (mvebu_pcie_link_up(port))
-+				break;
-+			msleep(1);
-+		}
-+
-+		printk("%s: reset completed in %dus\n", port->name, i);
- 	}
- 
- 	return 0;
-@@ -1539,15 +1546,16 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
- 		if (!child)
- 			continue;
- 
--		ret = mvebu_pcie_powerup(port);
--		if (ret < 0)
--			continue;
--
- 		port->base = mvebu_pcie_map_registers(pdev, child, port);
- 		if (IS_ERR(port->base)) {
- 			dev_err(dev, "%s: cannot map registers\n", port->name);
- 			port->base = NULL;
--			mvebu_pcie_powerdown(port);
-+			continue;
-+		}
-+
-+		ret = mvebu_pcie_powerup(port);
-+		if (ret < 0) {
-+			port->base = NULL;
- 			continue;
- 		}
- 
--- 
-2.37.2
-
diff --git a/nixos/modules/kernel-patches/0008-ARM-dts-armada-38x-Fix-assigned-addresses-for-every-.patch b/nixos/modules/kernel-patches/0008-ARM-dts-armada-38x-Fix-assigned-addresses-for-every-.patch
new file mode 100644
index 0000000..cdab261
--- /dev/null
+++ b/nixos/modules/kernel-patches/0008-ARM-dts-armada-38x-Fix-assigned-addresses-for-every-.patch
@@ -0,0 +1,76 @@
+From f64617ce6467532a4a2ebba14b1655e5a06535bb Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Thu, 18 Aug 2022 00:03:45 +0200
+Subject: [PATCH 08/96] ARM: dts: armada-38x: Fix assigned-addresses for every
+ PCIe Root Port
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+BDF of resource in DT assigned-addresses property of Marvell PCIe Root Port
+(PCI-to-PCI bridge) should match BDF in address part in that DT node name
+as specified resource belongs to Marvell PCIe Root Port itself.
+
+Fixes: 0d3d96ab0059 ("ARM: mvebu: add Device Tree description of the Armada 380/385 SoCs")
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ arch/arm/boot/dts/armada-380.dtsi | 4 ++--
+ arch/arm/boot/dts/armada-385.dtsi | 6 +++---
+ 2 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/arch/arm/boot/dts/armada-380.dtsi b/arch/arm/boot/dts/armada-380.dtsi
+index cff1269f3fbf..7146cc8f082a 100644
+--- a/arch/arm/boot/dts/armada-380.dtsi
++++ b/arch/arm/boot/dts/armada-380.dtsi
+@@ -79,7 +79,7 @@ pcie@1,0 {
+ 			/* x1 port */
+ 			pcie@2,0 {
+ 				device_type = "pci";
+-				assigned-addresses = <0x82000800 0 0x40000 0 0x2000>;
++				assigned-addresses = <0x82001000 0 0x40000 0 0x2000>;
+ 				reg = <0x1000 0 0 0 0>;
+ 				#address-cells = <3>;
+ 				#size-cells = <2>;
+@@ -98,7 +98,7 @@ pcie@2,0 {
+ 			/* x1 port */
+ 			pcie@3,0 {
+ 				device_type = "pci";
+-				assigned-addresses = <0x82000800 0 0x44000 0 0x2000>;
++				assigned-addresses = <0x82001800 0 0x44000 0 0x2000>;
+ 				reg = <0x1800 0 0 0 0>;
+ 				#address-cells = <3>;
+ 				#size-cells = <2>;
+diff --git a/arch/arm/boot/dts/armada-385.dtsi b/arch/arm/boot/dts/armada-385.dtsi
+index 83392b92dae2..be8d607c59b2 100644
+--- a/arch/arm/boot/dts/armada-385.dtsi
++++ b/arch/arm/boot/dts/armada-385.dtsi
+@@ -93,7 +93,7 @@ pcie1_intc: interrupt-controller {
+ 			/* x1 port */
+ 			pcie2: pcie@2,0 {
+ 				device_type = "pci";
+-				assigned-addresses = <0x82000800 0 0x40000 0 0x2000>;
++				assigned-addresses = <0x82001000 0 0x40000 0 0x2000>;
+ 				reg = <0x1000 0 0 0 0>;
+ 				#address-cells = <3>;
+ 				#size-cells = <2>;
+@@ -121,7 +121,7 @@ pcie2_intc: interrupt-controller {
+ 			/* x1 port */
+ 			pcie3: pcie@3,0 {
+ 				device_type = "pci";
+-				assigned-addresses = <0x82000800 0 0x44000 0 0x2000>;
++				assigned-addresses = <0x82001800 0 0x44000 0 0x2000>;
+ 				reg = <0x1800 0 0 0 0>;
+ 				#address-cells = <3>;
+ 				#size-cells = <2>;
+@@ -152,7 +152,7 @@ pcie3_intc: interrupt-controller {
+ 			 */
+ 			pcie4: pcie@4,0 {
+ 				device_type = "pci";
+-				assigned-addresses = <0x82000800 0 0x48000 0 0x2000>;
++				assigned-addresses = <0x82002000 0 0x48000 0 0x2000>;
+ 				reg = <0x2000 0 0 0 0>;
+ 				#address-cells = <3>;
+ 				#size-cells = <2>;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0008-ARM-dts-mvebu-armada-385-turris-omnia-separate-dts-f.patch b/nixos/modules/kernel-patches/0008-ARM-dts-mvebu-armada-385-turris-omnia-separate-dts-f.patch
deleted file mode 100644
index a292738..0000000
--- a/nixos/modules/kernel-patches/0008-ARM-dts-mvebu-armada-385-turris-omnia-separate-dts-f.patch
+++ /dev/null
@@ -1,213 +0,0 @@
-From 03ba17e25dd41d414c00ddde855a36464698d2e1 Mon Sep 17 00:00:00 2001
-From: Tomas Hlavacek <tmshlvck@gmail.com>
-Date: Tue, 5 May 2020 20:40:24 +0200
-Subject: [PATCH 08/10] ARM: dts: mvebu: armada-385-turris-omnia: separate dts
- for SFP and PHY
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-The Turris Omnia board contains dual-personality ethernet NIC eth2 with
-two operation modes: 1) SFP cage and 2) metalic 1000BASE-X PHY.
-The differential pair carrying SGMII/1000BASE-X of eth2 is wired through
-a multiplexor driven by the module-detect signal from the SFP cage.
-The pin status can be read through I2C GPIO expander chip in userspace
-when the sfp driver module is unloaded and / or in U-Boot prior to the
-start of the kernel and the proper DTS file can be selected for the
-(floolowing) boot.
-
-Split DTS for Turris Omnia (that does not have any support for SFP cage)
-into three files:
-  armada-385-turris-omnia.dtsi - common base
-  armada-385-turris-omnia-sfp.dts - DT with the SFP configuration and
-PHY disabled
-  armada-385-turris-omnia-phy.dts - DT with the PHY configuration and
-SFP disabled
-
-Current DSA driver does not allow multiple CPU ports and Turris Omnia
-has two RGMII iterfaces wired between CPU and DSA switch.
-Disable the second CPU port until there is a suitable driver to use it.
-
-Signed-off-by: Tomas Hlavacek <tmshlvck@gmail.com>
-Signed-off-by: Marek Behún <marek.behun@nic.cz>
----
- arch/arm/boot/dts/Makefile                    |  3 +-
- .../boot/dts/armada-385-turris-omnia-phy.dts  | 22 ++++++++++++
- .../boot/dts/armada-385-turris-omnia-sfp.dts  | 23 ++++++++++++
- ...omnia.dts => armada-385-turris-omnia.dtsi} | 35 +++++++++++--------
- 4 files changed, 68 insertions(+), 15 deletions(-)
- create mode 100644 arch/arm/boot/dts/armada-385-turris-omnia-phy.dts
- create mode 100644 arch/arm/boot/dts/armada-385-turris-omnia-sfp.dts
- rename arch/arm/boot/dts/{armada-385-turris-omnia.dts => armada-385-turris-omnia.dtsi} (94%)
-
-diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
-index 27eec8e670ec..f320ee8e2a00 100644
---- a/arch/arm/boot/dts/Makefile
-+++ b/arch/arm/boot/dts/Makefile
-@@ -1495,7 +1495,8 @@ dtb-$(CONFIG_MACH_ARMADA_38X) += \
- 	armada-385-linksys-rango.dtb \
- 	armada-385-linksys-shelby.dtb \
- 	armada-385-synology-ds116.dtb \
--	armada-385-turris-omnia.dtb \
-+	armada-385-turris-omnia-phy.dtb \
-+	armada-385-turris-omnia-sfp.dtb \
- 	armada-388-clearfog.dtb \
- 	armada-388-clearfog-base.dtb \
- 	armada-388-clearfog-pro.dtb \
-diff --git a/arch/arm/boot/dts/armada-385-turris-omnia-phy.dts b/arch/arm/boot/dts/armada-385-turris-omnia-phy.dts
-new file mode 100644
-index 000000000000..706f6a2f8065
---- /dev/null
-+++ b/arch/arm/boot/dts/armada-385-turris-omnia-phy.dts
-@@ -0,0 +1,22 @@
-+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
-+/*
-+ * Device Tree file for the Turris Omnia
-+ *
-+ * Copyright (C) 2016 Uwe Kleine-König <uwe@kleine-koenig.org>
-+ * Copyright (C) 2016-2019 Tomas Hlavacek <tmshlvkc@gmail.com>
-+ *
-+ * Schematic available at https://www.turris.cz/doc/_media/rtrom01-schema.pdf
-+ */
-+
-+/dts-v1/;
-+
-+#include "armada-385-turris-omnia.dtsi"
-+
-+&phy1 {
-+	status = "okay";
-+};
-+
-+&eth2 {
-+	phy-mode = "sgmii";
-+	phy = <&phy1>;
-+};
-diff --git a/arch/arm/boot/dts/armada-385-turris-omnia-sfp.dts b/arch/arm/boot/dts/armada-385-turris-omnia-sfp.dts
-new file mode 100644
-index 000000000000..b9f2b88834be
---- /dev/null
-+++ b/arch/arm/boot/dts/armada-385-turris-omnia-sfp.dts
-@@ -0,0 +1,23 @@
-+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
-+/*
-+ * Device Tree file for the Turris Omnia
-+ *
-+ * Copyright (C) 2016 Uwe Kleine-König <uwe@kleine-koenig.org>
-+ * Copyright (C) 2016-2019 Tomas Hlavacek <tmshlvkc@gmail.com>
-+ *
-+ * Schematic available at https://www.turris.cz/doc/_media/rtrom01-schema.pdf
-+ */
-+
-+/dts-v1/;
-+
-+#include "armada-385-turris-omnia.dtsi"
-+
-+&sfp {
-+	status = "okay";
-+};
-+
-+&eth2 {
-+	phy-mode = "sgmii";
-+	managed = "in-band-status";
-+	sfp = <&sfp>;
-+};
-diff --git a/arch/arm/boot/dts/armada-385-turris-omnia.dts b/arch/arm/boot/dts/armada-385-turris-omnia.dtsi
-similarity index 94%
-rename from arch/arm/boot/dts/armada-385-turris-omnia.dts
-rename to arch/arm/boot/dts/armada-385-turris-omnia.dtsi
-index bcced203c612..58e3d86771db 100644
---- a/arch/arm/boot/dts/armada-385-turris-omnia.dts
-+++ b/arch/arm/boot/dts/armada-385-turris-omnia.dtsi
-@@ -8,8 +8,6 @@
-  * Schematic available at https://www.turris.cz/doc/_media/rtrom01-schema.pdf
-  */
- 
--/dts-v1/;
--
- #include <dt-bindings/gpio/gpio.h>
- #include <dt-bindings/input/input.h>
- #include <dt-bindings/leds/common.h>
-@@ -91,11 +89,11 @@ pcie@3,0 {
- 	sfp: sfp {
- 		compatible = "sff,sfp";
- 		i2c-bus = <&sfp_i2c>;
--		tx-fault-gpios = <&pcawan 0 GPIO_ACTIVE_HIGH>;
--		tx-disable-gpios = <&pcawan 1 GPIO_ACTIVE_HIGH>;
--		rate-select0-gpios = <&pcawan 2 GPIO_ACTIVE_HIGH>;
--		los-gpios = <&pcawan 3 GPIO_ACTIVE_HIGH>;
--		mod-def0-gpios = <&pcawan 4 GPIO_ACTIVE_LOW>;
-+		tx-fault-gpios = <&sfpgpio 0 GPIO_ACTIVE_HIGH>;
-+		tx-disable-gpios = <&sfpgpio 1 GPIO_ACTIVE_HIGH>;
-+		rate-select0-gpios = <&sfpgpio 2 GPIO_ACTIVE_HIGH>;
-+		los-gpios = <&sfpgpio 3 GPIO_ACTIVE_HIGH>;
-+		mod-def0-gpios = <&sfpgpio 4 GPIO_ACTIVE_LOW>;
- 		maximum-power-milliwatt = <3000>;
- 
- 		/*
-@@ -147,7 +145,7 @@ fixed-link {
- 	};
- };
- 
--/* WAN port */
-+/* WAN dual-personality port */
- &eth2 {
- 	/*
- 	 * eth2 is connected via a multiplexor to both the SFP cage and to
-@@ -158,9 +156,8 @@ &eth2 {
- 	 * is present, U-Boot has to enable the sfp node above, remove phy
- 	 * handle and add managed = "in-band-status" property.
- 	 */
-+	phys = <&comphy5 2>;
- 	status = "okay";
--	phy-mode = "sgmii";
--	phy-handle = <&phy1>;
- 	phys = <&comphy5 2>;
- 	sfp = <&sfp>;
- 	buffer-manager = <&bm>;
-@@ -365,7 +362,7 @@ i2c@7 {
- 			#size-cells = <0>;
- 			reg = <7>;
- 
--			pcawan: gpio@71 {
-+			sfpgpio: gpio@71 {
- 				/*
- 				 * GPIO expander for SFP+ signals and
- 				 * and phy irq
-@@ -374,7 +371,7 @@ pcawan: gpio@71 {
- 				reg = <0x71>;
- 
- 				pinctrl-names = "default";
--				pinctrl-0 = <&pcawan_pins>;
-+				pinctrl-0 = <&wanint_pins>;
- 
- 				interrupt-parent = <&gpio1>;
- 				interrupts = <14 IRQ_TYPE_LEVEL_LOW>;
-@@ -454,13 +451,23 @@ fixed-link {
- 				};
- 			};
- 
--			/* port 6 is connected to eth0 */
-+			ports@6 {
-+				reg = <6>;
-+				label = "cpu";
-+				ethernet = <&eth0>;
-+				phy-mode = "rgmii-id";
-+
-+				fixed-link {
-+					speed = <1000>;
-+					full-duplex;
-+				};
-+			};
- 		};
- 	};
- };
- 
- &pinctrl {
--	pcawan_pins: pcawan-pins {
-+	wanint_pins: wanint-pins {
- 		marvell,pins = "mpp46";
- 		marvell,function = "gpio";
- 	};
--- 
-2.37.2
-
diff --git a/nixos/modules/kernel-patches/0009-dt-bindings-PCI-mvebu-Update-information-about-error.patch b/nixos/modules/kernel-patches/0009-dt-bindings-PCI-mvebu-Update-information-about-error.patch
new file mode 100644
index 0000000..fbb3c6a
--- /dev/null
+++ b/nixos/modules/kernel-patches/0009-dt-bindings-PCI-mvebu-Update-information-about-error.patch
@@ -0,0 +1,32 @@
+From d3bdf92b9373c1d8ce917de30afd1ac07b083360 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Tue, 2 Nov 2021 11:06:18 +0100
+Subject: [PATCH 09/96] dt-bindings: PCI: mvebu: Update information about error
+ interrupt
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+mvebu error interrupt is triggered by any non-intx event, which is mainly
+some pcie error.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ Documentation/devicetree/bindings/pci/mvebu-pci.txt | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/Documentation/devicetree/bindings/pci/mvebu-pci.txt b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
+index 6d022a9d36ee..8f0bca42113f 100644
+--- a/Documentation/devicetree/bindings/pci/mvebu-pci.txt
++++ b/Documentation/devicetree/bindings/pci/mvebu-pci.txt
+@@ -83,6 +83,7 @@ and the following optional properties:
+   specified will default to 100ms, as required by the PCIe specification.
+ - interrupt-names: list of interrupt names, supported are:
+    - "intx" - interrupt line triggered by one of the legacy interrupt
++   - "error" - interrupt line triggered by any other event (non-intx)
+ - interrupts or interrupts-extended: List of the interrupt sources which
+   corresponding to the "interrupt-names". If non-empty then also additional
+   'interrupt-controller' subnode must be defined.
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0009-phy-marvell-phy-mvebu-a3700-comphy-Change-2500base-x.patch b/nixos/modules/kernel-patches/0009-phy-marvell-phy-mvebu-a3700-comphy-Change-2500base-x.patch
deleted file mode 100644
index bd094d1..0000000
--- a/nixos/modules/kernel-patches/0009-phy-marvell-phy-mvebu-a3700-comphy-Change-2500base-x.patch
+++ /dev/null
@@ -1,63 +0,0 @@
-From dba4f89292a08c3b1ab1bcdb87a0e9b9eb3af0ad Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
-Date: Wed, 17 Aug 2022 14:35:59 +0200
-Subject: [PATCH 09/10] phy: marvell: phy-mvebu-a3700-comphy: Change 2500base-x
- transmit amplitude
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-Change transmit amplitude to 1025 mV for 2500base-x mode.
-
-This fixes issue wherein if the 8b/10b encoded packet contains a long
-enough alternating sequence of bits (010101... or 101010...), which
-happens if the packet contains a sequence of 'J' or '\xb5' bytes, the
-packet may be lost due to FCS error. The probability of loss grows with
-the number of 'J's with default transmit amplitude setting - with 114
-'J's the probability is about 50%, with 125 'J's almost 100% of packets
-are lost.
-
-Signed-off-by: Marek Behún <kabel@kernel.org>
----
- drivers/phy/marvell/phy-mvebu-a3700-comphy.c | 18 ++++++++++++++++++
- 1 file changed, 18 insertions(+)
-
-diff --git a/drivers/phy/marvell/phy-mvebu-a3700-comphy.c b/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
-index a4d7d9bd100d..4a18f9ca6d25 100644
---- a/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
-+++ b/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
-@@ -68,6 +68,12 @@
- #define SPEED_PLL_MASK			GENMASK(7, 2)
- #define SPEED_PLL_VALUE_16		FIELD_PREP(SPEED_PLL_MASK, 0x10)
- 
-+#define COMPHY_GEN3_SET0		0x0d
-+#define Gx_TX_AMP_MASK			GENMASK(5, 1)
-+#define Gx_TX_AMP_VALUE(x)		FIELD_PREP(Gx_TX_AMP_MASK, x)
-+#define Gx_TX_AMP_ADJ			BIT(6)
-+#define Gx_TX_AMP_1025MV		(Gx_TX_AMP_VALUE(0x12) | Gx_TX_AMP_ADJ)
-+
- #define COMPHY_DIG_LOOPBACK_EN		0x23
- #define SEL_DATA_WIDTH_MASK		GENMASK(11, 10)
- #define DATA_WIDTH_10BIT		FIELD_PREP(SEL_DATA_WIDTH_MASK, 0x0)
-@@ -746,6 +752,18 @@ mvebu_a3700_comphy_ethernet_power_on(struct mvebu_a3700_comphy_lane *lane)
- 		comphy_gbe_phy_init(lane,
- 				    lane->submode != PHY_INTERFACE_MODE_2500BASEX);
- 
-+	/*
-+	 * Fix issue wherein a packet may be lost if it contains a long enough
-+	 * sequence of 'J' or '\xb5' bytes.
-+	 * This only happens with 2500base-x mode. Fix this by changing transmit
-+	 * amplitude to 1025 mV.
-+	 */
-+	if (lane->submode == PHY_INTERFACE_MODE_2500BASEX) {
-+		data = Gx_TX_AMP_1025MV;
-+		mask = Gx_TX_AMP_MASK | Gx_TX_AMP_ADJ;
-+		comphy_lane_reg_set(lane, COMPHY_GEN3_SET0, data, mask);
-+	}
-+
- 	/*
- 	 * 14. Check the PHY Polarity invert bit
- 	 */
--- 
-2.37.2
-
diff --git a/nixos/modules/kernel-patches/0010-PCI-mvebu-Implement-support-for-interrupts-on-emulat.patch b/nixos/modules/kernel-patches/0010-PCI-mvebu-Implement-support-for-interrupts-on-emulat.patch
new file mode 100644
index 0000000..73ead93
--- /dev/null
+++ b/nixos/modules/kernel-patches/0010-PCI-mvebu-Implement-support-for-interrupts-on-emulat.patch
@@ -0,0 +1,437 @@
+From 1f840ec09a1bd8925decbc4917d701f6b93483b8 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Fri, 17 Sep 2021 14:40:17 +0200
+Subject: [PATCH 10/96] PCI: mvebu: Implement support for interrupts on
+ emulated bridge
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This adds support for PME and ERR interrupts reported by emulated bridge
+(for PME and AER kernel drivers) via new Root Port irq chip as these
+interrupts from PCIe Root Ports are handled by mvebu hardware completely
+separately from INTx and MSI interrupts send by real PCIe devices.
+
+With this change, kernel PME and AER drivers start working as they can
+acquire required interrupt lines (provided by mvebu rp virtual irq chip).
+
+Note that for this support, device tree files has to be properly adjusted
+to provide "interrupts" or "interrupts-extended" property with error
+interrupt source and "interrupt-names" property with "error" string.
+
+If device tree files do not provide these properties then driver would work
+as before and would not provide interrupts on emulated bridge, like before.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/controller/pci-mvebu.c | 256 ++++++++++++++++++++++++++---
+ 1 file changed, 237 insertions(+), 19 deletions(-)
+
+diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
+index cacc78863915..56924b0a1969 100644
+--- a/drivers/pci/controller/pci-mvebu.c
++++ b/drivers/pci/controller/pci-mvebu.c
+@@ -56,8 +56,16 @@
+ #define PCIE_CONF_DATA_OFF	0x18fc
+ #define PCIE_INT_CAUSE_OFF	0x1900
+ #define PCIE_INT_UNMASK_OFF	0x1910
++#define  PCIE_INT_DET_COR		BIT(8)
++#define  PCIE_INT_DET_NONFATAL		BIT(9)
++#define  PCIE_INT_DET_FATAL		BIT(10)
++#define  PCIE_INT_ERR_FATAL		BIT(16)
++#define  PCIE_INT_ERR_NONFATAL		BIT(17)
++#define  PCIE_INT_ERR_COR		BIT(18)
+ #define  PCIE_INT_INTX(i)		BIT(24+i)
+ #define  PCIE_INT_PM_PME		BIT(28)
++#define  PCIE_INT_DET_MASK		(PCIE_INT_DET_COR | PCIE_INT_DET_NONFATAL | PCIE_INT_DET_FATAL)
++#define  PCIE_INT_ERR_MASK		(PCIE_INT_ERR_FATAL | PCIE_INT_ERR_NONFATAL | PCIE_INT_ERR_COR)
+ #define  PCIE_INT_ALL_MASK		GENMASK(31, 0)
+ #define PCIE_CTRL_OFF		0x1a00
+ #define  PCIE_CTRL_X1_MODE		0x0001
+@@ -120,9 +128,12 @@ struct mvebu_pcie_port {
+ 	struct resource regs;
+ 	u8 slot_power_limit_value;
+ 	u8 slot_power_limit_scale;
++	struct irq_domain *rp_irq_domain;
+ 	struct irq_domain *intx_irq_domain;
+ 	raw_spinlock_t irq_lock;
++	int error_irq;
+ 	int intx_irq;
++	bool pme_pending;
+ };
+ 
+ static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
+@@ -321,9 +332,19 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
+ 	/* Clear all interrupt causes. */
+ 	mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
+ 
+-	/* Check if "intx" interrupt was specified in DT. */
+-	if (port->intx_irq > 0)
+-		return;
++	/*
++	 * Unmask all error interrupts which are internally generated.
++	 * They cannot be disabled by SERR# Enable bit in PCI Command register,
++	 * see Figure 6-3: Pseudo Logic Diagram for Error Message Controls in
++	 * PCIe base specification.
++	 * Internally generated mvebu interrupts are reported via mvebu summary
++	 * interrupt which requires "error" interrupt to be specified in DT.
++	 */
++	if (port->error_irq > 0) {
++		unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
++		unmask |= PCIE_INT_DET_MASK;
++		mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
++	}
+ 
+ 	/*
+ 	 * Fallback code when "intx" interrupt was not specified in DT:
+@@ -335,10 +356,12 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
+ 	 * performance penalty as every PCIe interrupt handler needs to be
+ 	 * called when some interrupt is triggered.
+ 	 */
+-	unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+-	unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
+-		  PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
+-	mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
++	if (port->intx_irq <= 0) {
++		unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
++		unmask |= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
++			  PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
++		mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
++	}
+ }
+ 
+ static struct mvebu_pcie_port *mvebu_pcie_find_port(struct mvebu_pcie *pcie,
+@@ -598,11 +621,16 @@ mvebu_pci_bridge_emul_base_conf_read(struct pci_bridge_emul *bridge,
+ 	case PCI_INTERRUPT_LINE: {
+ 		/*
+ 		 * From the whole 32bit register we support reading from HW only
+-		 * one bit: PCI_BRIDGE_CTL_BUS_RESET.
++		 * two bits: PCI_BRIDGE_CTL_BUS_RESET and PCI_BRIDGE_CTL_SERR.
+ 		 * Other bits are retrieved only from emulated config buffer.
+ 		 */
+ 		__le32 *cfgspace = (__le32 *)&bridge->conf;
+ 		u32 val = le32_to_cpu(cfgspace[PCI_INTERRUPT_LINE / 4]);
++		if ((mvebu_readl(port, PCIE_INT_UNMASK_OFF) &
++		      PCIE_INT_ERR_MASK) == PCIE_INT_ERR_MASK)
++			val |= PCI_BRIDGE_CTL_SERR << 16;
++		else
++			val &= ~(PCI_BRIDGE_CTL_SERR << 16);
+ 		if (mvebu_readl(port, PCIE_CTRL_OFF) & PCIE_CTRL_MASTER_HOT_RESET)
+ 			val |= PCI_BRIDGE_CTL_BUS_RESET << 16;
+ 		else
+@@ -670,6 +698,11 @@ mvebu_pci_bridge_emul_pcie_conf_read(struct pci_bridge_emul *bridge,
+ 		break;
+ 	}
+ 
++	case PCI_EXP_RTCTL:
++		*value = (mvebu_readl(port, PCIE_INT_UNMASK_OFF) &
++			  PCIE_INT_PM_PME) ? PCI_EXP_RTCTL_PMEIE : 0;
++		break;
++
+ 	case PCI_EXP_RTSTA:
+ 		*value = mvebu_readl(port, PCIE_RC_RTSTA);
+ 		break;
+@@ -775,6 +808,14 @@ mvebu_pci_bridge_emul_base_conf_write(struct pci_bridge_emul *bridge,
+ 		break;
+ 
+ 	case PCI_INTERRUPT_LINE:
++		if ((mask & (PCI_BRIDGE_CTL_SERR << 16)) && port->error_irq > 0) {
++			u32 unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
++			if (new & (PCI_BRIDGE_CTL_SERR << 16))
++				unmask |= PCIE_INT_ERR_MASK;
++			else
++				unmask &= ~PCIE_INT_ERR_MASK;
++			mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
++		}
+ 		if (mask & (PCI_BRIDGE_CTL_BUS_RESET << 16)) {
+ 			u32 ctrl = mvebu_readl(port, PCIE_CTRL_OFF);
+ 			if (new & (PCI_BRIDGE_CTL_BUS_RESET << 16))
+@@ -833,10 +874,25 @@ mvebu_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge,
+ 		 * PME Status bit in Root Status Register (PCIE_RC_RTSTA)
+ 		 * is read-only and can be cleared only by writing 0b to the
+ 		 * Interrupt Cause RW0C register (PCIE_INT_CAUSE_OFF). So
+-		 * clear PME via Interrupt Cause.
++		 * clear PME via Interrupt Cause and also set port->pme_pending
++		 * variable to false value to start processing PME interrupts
++		 * in interrupt handler again.
+ 		 */
+-		if (new & PCI_EXP_RTSTA_PME)
++		if (new & PCI_EXP_RTSTA_PME) {
+ 			mvebu_writel(port, ~PCIE_INT_PM_PME, PCIE_INT_CAUSE_OFF);
++			port->pme_pending = false;
++		}
++		break;
++
++	case PCI_EXP_RTCTL:
++		if ((mask & PCI_EXP_RTCTL_PMEIE) && port->error_irq > 0) {
++			u32 unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
++			if (new & PCI_EXP_RTCTL_PMEIE)
++				unmask |= PCIE_INT_PM_PME;
++			else
++				unmask &= ~PCIE_INT_PM_PME;
++			mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
++		}
+ 		break;
+ 
+ 	case PCI_EXP_DEVCTL2:
+@@ -919,6 +975,14 @@ static int mvebu_pci_bridge_emul_init(struct mvebu_pcie_port *port)
+ 		bridge_flags |= PCI_BRIDGE_EMUL_NO_IO_FORWARD;
+ 	}
+ 
++	/*
++	 * Interrupts on emulated bridge are supported only when "error"
++	 * interrupt was specified in DT. Without it emulated bridge cannot
++	 * emulate interrupts.
++	 */
++	if (port->error_irq > 0)
++		bridge->conf.intpin = PCI_INTERRUPT_INTA;
++
+ 	/*
+ 	 * Older mvebu hardware provides PCIe Capability structure only in
+ 	 * version 1. New hardware provides it in version 2.
+@@ -1065,6 +1129,26 @@ static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
+ 	.xlate = irq_domain_xlate_onecell,
+ };
+ 
++static struct irq_chip rp_irq_chip = {
++	.name = "mvebu-rp",
++};
++
++static int mvebu_pcie_rp_irq_map(struct irq_domain *h,
++				   unsigned int virq, irq_hw_number_t hwirq)
++{
++	struct mvebu_pcie_port *port = h->host_data;
++
++	irq_set_chip_and_handler(virq, &rp_irq_chip, handle_simple_irq);
++	irq_set_chip_data(virq, port);
++
++	return 0;
++}
++
++static const struct irq_domain_ops mvebu_pcie_rp_irq_domain_ops = {
++	.map = mvebu_pcie_rp_irq_map,
++	.xlate = irq_domain_xlate_onecell,
++};
++
+ static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
+ {
+ 	struct device *dev = &port->pcie->pdev->dev;
+@@ -1087,10 +1171,72 @@ static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
+ 		return -ENOMEM;
+ 	}
+ 
++	/*
++	 * When "error" interrupt was not specified in DT then there is no
++	 * support for interrupts on emulated root bridge. So skip following
++	 * initialization.
++	 */
++	if (port->error_irq <= 0)
++		return 0;
++
++	port->rp_irq_domain = irq_domain_add_linear(NULL, 1,
++						      &mvebu_pcie_rp_irq_domain_ops,
++						      port);
++	if (!port->rp_irq_domain) {
++		irq_domain_remove(port->intx_irq_domain);
++		dev_err(dev, "Failed to add Root Port IRQ domain for %s\n", port->name);
++		return -ENOMEM;
++	}
++
+ 	return 0;
+ }
+ 
+-static irqreturn_t mvebu_pcie_irq_handler(int irq, void *arg)
++static irqreturn_t mvebu_pcie_error_irq_handler(int irq, void *arg)
++{
++	struct mvebu_pcie_port *port = arg;
++	struct device *dev = &port->pcie->pdev->dev;
++	u32 cause, unmask, status;
++
++	cause = mvebu_readl(port, PCIE_INT_CAUSE_OFF);
++	unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
++	status = cause & unmask;
++
++	/* "error" interrupt handler does not process INTX interrupts */
++	status &= ~(PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
++		    PCIE_INT_INTX(2) | PCIE_INT_INTX(3));
++
++	/* Process PME interrupt */
++	if ((status & PCIE_INT_PM_PME) && !port->pme_pending) {
++		/*
++		 * Do not clear PME interrupt bit in Cause Register as it
++		 * invalidates also content of Root Status Register. Instead
++		 * set port->pme_pending variable to true to indicate that
++		 * next time PME interrupt should be ignored until variable
++		 * is back to the false value.
++		 */
++		port->pme_pending = true;
++		if (generic_handle_domain_irq(port->rp_irq_domain, 0) == -EINVAL)
++			dev_err_ratelimited(dev, "unhandled PME IRQ\n");
++	}
++
++	/* Process ERR interrupt */
++	if (status & PCIE_INT_ERR_MASK) {
++		mvebu_writel(port, ~PCIE_INT_ERR_MASK, PCIE_INT_CAUSE_OFF);
++		if (generic_handle_domain_irq(port->rp_irq_domain, 0) == -EINVAL)
++			dev_err_ratelimited(dev, "unhandled ERR IRQ\n");
++	}
++
++	/* Process local ERR interrupt */
++	if (status & PCIE_INT_DET_MASK) {
++		mvebu_writel(port, ~PCIE_INT_DET_MASK, PCIE_INT_CAUSE_OFF);
++		if (generic_handle_domain_irq(port->rp_irq_domain, 0) == -EINVAL)
++			dev_err_ratelimited(dev, "unhandled ERR IRQ\n");
++	}
++
++	return status ? IRQ_HANDLED : IRQ_NONE;
++}
++
++static irqreturn_t mvebu_pcie_intx_irq_handler(int irq, void *arg)
+ {
+ 	struct mvebu_pcie_port *port = arg;
+ 	struct device *dev = &port->pcie->pdev->dev;
+@@ -1101,6 +1247,10 @@ static irqreturn_t mvebu_pcie_irq_handler(int irq, void *arg)
+ 	unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+ 	status = cause & unmask;
+ 
++	/* "intx" interrupt handler process only INTX interrupts */
++	status &= PCIE_INT_INTX(0) | PCIE_INT_INTX(1) |
++		  PCIE_INT_INTX(2) | PCIE_INT_INTX(3);
++
+ 	/* Process legacy INTx interrupts */
+ 	for (i = 0; i < PCI_NUM_INTX; i++) {
+ 		if (!(status & PCIE_INT_INTX(i)))
+@@ -1115,9 +1265,29 @@ static irqreturn_t mvebu_pcie_irq_handler(int irq, void *arg)
+ 
+ static int mvebu_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
+ {
+-	/* Interrupt support on mvebu emulated bridges is not implemented yet */
+-	if (dev->bus->number == 0)
+-		return 0; /* Proper return code 0 == NO_IRQ */
++	struct mvebu_pcie_port *port;
++	struct mvebu_pcie *pcie;
++
++	if (dev->bus->number == 0) {
++		/*
++		 * Each emulated root bridge for every mvebu port has its own
++		 * Root Port irq chip and irq domain. Argument pin is the INTx
++		 * pin (1=INTA, 2=INTB, 3=INTC, 4=INTD) and hwirq for function
++		 * irq_create_mapping() is indexed from zero.
++		 */
++		pcie = dev->bus->sysdata;
++		port = mvebu_pcie_find_port(pcie, dev->bus, PCI_DEVFN(slot, 0));
++		if (!port)
++			return 0; /* Proper return code 0 == NO_IRQ */
++		/*
++		 * port->rp_irq_domain is available only when "error" interrupt
++		 * was specified in DT. When is not available then interrupts
++		 * for emulated root bridge are not provided.
++		 */
++		if (port->error_irq <= 0)
++			return 0; /* Proper return code 0 == NO_IRQ */
++		return irq_create_mapping(port->rp_irq_domain, pin - 1);
++	}
+ 
+ 	return of_irq_parse_and_map_pci(dev, slot, pin);
+ }
+@@ -1326,6 +1496,21 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
+ 			 port->name, child);
+ 	}
+ 
++	/*
++	 * Old DT bindings do not contain "error" interrupt
++	 * so do not fail probing driver when interrupt does not exist.
++	 */
++	port->error_irq = of_irq_get_byname(child, "error");
++	if (port->error_irq == -EPROBE_DEFER) {
++		ret = port->error_irq;
++		goto err;
++	}
++	if (port->error_irq <= 0) {
++		dev_warn(dev, "%s: interrupts on Root Port are unsupported, "
++			      "%pOF does not contain error interrupt\n",
++			 port->name, child);
++	}
++
+ 	reset_gpio = of_get_named_gpio_flags(child, "reset-gpios", 0, &flags);
+ 	if (reset_gpio == -EPROBE_DEFER) {
+ 		ret = reset_gpio;
+@@ -1531,7 +1716,6 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
+ 
+ 	for (i = 0; i < pcie->nports; i++) {
+ 		struct mvebu_pcie_port *port = &pcie->ports[i];
+-		int irq = port->intx_irq;
+ 
+ 		child = port->dn;
+ 		if (!child)
+@@ -1559,7 +1743,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
+ 			continue;
+ 		}
+ 
+-		if (irq > 0) {
++		if (port->error_irq > 0 || port->intx_irq > 0) {
+ 			ret = mvebu_pcie_init_irq_domain(port);
+ 			if (ret) {
+ 				dev_err(dev, "%s: cannot init irq domain\n",
+@@ -1570,14 +1754,42 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
+ 				mvebu_pcie_powerdown(port);
+ 				continue;
+ 			}
++		}
++
++		if (port->error_irq > 0) {
++			ret = devm_request_irq(dev, port->error_irq,
++					       mvebu_pcie_error_irq_handler,
++					       IRQF_SHARED | IRQF_NO_THREAD,
++					       port->name, port);
++			if (ret) {
++				dev_err(dev, "%s: cannot register error interrupt handler: %d\n",
++					port->name, ret);
++				if (port->intx_irq_domain)
++					irq_domain_remove(port->intx_irq_domain);
++				if (port->rp_irq_domain)
++					irq_domain_remove(port->rp_irq_domain);
++				pci_bridge_emul_cleanup(&port->bridge);
++				devm_iounmap(dev, port->base);
++				port->base = NULL;
++				mvebu_pcie_powerdown(port);
++				continue;
++			}
++		}
+ 
+-			ret = devm_request_irq(dev, irq, mvebu_pcie_irq_handler,
++		if (port->intx_irq > 0) {
++			ret = devm_request_irq(dev, port->intx_irq,
++					       mvebu_pcie_intx_irq_handler,
+ 					       IRQF_SHARED | IRQF_NO_THREAD,
+ 					       port->name, port);
+ 			if (ret) {
+-				dev_err(dev, "%s: cannot register interrupt handler: %d\n",
++				dev_err(dev, "%s: cannot register intx interrupt handler: %d\n",
+ 					port->name, ret);
+-				irq_domain_remove(port->intx_irq_domain);
++				if (port->error_irq > 0)
++					devm_free_irq(dev, port->error_irq, port);
++				if (port->intx_irq_domain)
++					irq_domain_remove(port->intx_irq_domain);
++				if (port->rp_irq_domain)
++					irq_domain_remove(port->rp_irq_domain);
+ 				pci_bridge_emul_cleanup(&port->bridge);
+ 				devm_iounmap(dev, port->base);
+ 				port->base = NULL;
+@@ -1715,6 +1927,12 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
+ 			}
+ 			irq_domain_remove(port->intx_irq_domain);
+ 		}
++		if (port->rp_irq_domain) {
++			int virq = irq_find_mapping(port->rp_irq_domain, 0);
++			if (virq > 0)
++				irq_dispose_mapping(virq);
++			irq_domain_remove(port->rp_irq_domain);
++		}
+ 
+ 		/* Free config space for emulated root bridge. */
+ 		pci_bridge_emul_cleanup(&port->bridge);
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0010-Rename-device-tree-for-Omnia-back-as-that-is-what-is.patch b/nixos/modules/kernel-patches/0010-Rename-device-tree-for-Omnia-back-as-that-is-what-is.patch
deleted file mode 100644
index befe186..0000000
--- a/nixos/modules/kernel-patches/0010-Rename-device-tree-for-Omnia-back-as-that-is-what-is.patch
+++ /dev/null
@@ -1,32 +0,0 @@
-From 0aa8841f0a0580490003204fb3cacb5043de6876 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= <cynerd@email.cz>
-Date: Tue, 27 Sep 2022 10:19:39 +0200
-Subject: [PATCH 10/10] Rename device tree for Omnia back as that is what is
- expected by U-Boot
-
----
- arch/arm/boot/dts/Makefile                                      | 2 +-
- ...ada-385-turris-omnia-phy.dts => armada-385-turris-omnia.dts} | 0
- 2 files changed, 1 insertion(+), 1 deletion(-)
- rename arch/arm/boot/dts/{armada-385-turris-omnia-phy.dts => armada-385-turris-omnia.dts} (100%)
-
-diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
-index f320ee8e2a00..5d82687e3b88 100644
---- a/arch/arm/boot/dts/Makefile
-+++ b/arch/arm/boot/dts/Makefile
-@@ -1495,7 +1495,7 @@ dtb-$(CONFIG_MACH_ARMADA_38X) += \
- 	armada-385-linksys-rango.dtb \
- 	armada-385-linksys-shelby.dtb \
- 	armada-385-synology-ds116.dtb \
--	armada-385-turris-omnia-phy.dtb \
-+	armada-385-turris-omnia.dtb \
- 	armada-385-turris-omnia-sfp.dtb \
- 	armada-388-clearfog.dtb \
- 	armada-388-clearfog-base.dtb \
-diff --git a/arch/arm/boot/dts/armada-385-turris-omnia-phy.dts b/arch/arm/boot/dts/armada-385-turris-omnia.dts
-similarity index 100%
-rename from arch/arm/boot/dts/armada-385-turris-omnia-phy.dts
-rename to arch/arm/boot/dts/armada-385-turris-omnia.dts
--- 
-2.37.2
-
diff --git a/nixos/modules/kernel-patches/0011-ARM-dts-armada-38x.dtsi-Add-node-for-MPIC-SoC-Error-.patch b/nixos/modules/kernel-patches/0011-ARM-dts-armada-38x.dtsi-Add-node-for-MPIC-SoC-Error-.patch
new file mode 100644
index 0000000..b7b3e7b
--- /dev/null
+++ b/nixos/modules/kernel-patches/0011-ARM-dts-armada-38x.dtsi-Add-node-for-MPIC-SoC-Error-.patch
@@ -0,0 +1,35 @@
+From ae6b14b03c3d42a1eb5c2ba5ae3ab5600c069f60 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Mon, 18 Apr 2022 00:39:52 +0200
+Subject: [PATCH 11/96] ARM: dts: armada-38x.dtsi: Add node for MPIC SoC Error
+ IRQ controller
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+It is child of the MPIC IRQ controller.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ arch/arm/boot/dts/armada-38x.dtsi | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/arch/arm/boot/dts/armada-38x.dtsi b/arch/arm/boot/dts/armada-38x.dtsi
+index df3c8d1d8f64..099f167b65aa 100644
+--- a/arch/arm/boot/dts/armada-38x.dtsi
++++ b/arch/arm/boot/dts/armada-38x.dtsi
+@@ -398,6 +398,11 @@ mpic: interrupt-controller@20a00 {
+ 				interrupt-controller;
+ 				msi-controller;
+ 				interrupts = <GIC_PPI 15 IRQ_TYPE_LEVEL_HIGH>;
++
++				soc_err: interrupt-controller@20 {
++					interrupt-controller;
++					#interrupt-cells = <1>;
++				};
+ 			};
+ 
+ 			timer: timer@20300 {
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0012-PCI-pciehp-Enable-DLLSC-interrupt-only-if-supported.patch b/nixos/modules/kernel-patches/0012-PCI-pciehp-Enable-DLLSC-interrupt-only-if-supported.patch
new file mode 100644
index 0000000..c71098a
--- /dev/null
+++ b/nixos/modules/kernel-patches/0012-PCI-pciehp-Enable-DLLSC-interrupt-only-if-supported.patch
@@ -0,0 +1,139 @@
+From 306447b8079037762c67aabd749ed22e6bb892a4 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Wed, 31 Mar 2021 15:12:50 +0200
+Subject: [PATCH 12/96] PCI: pciehp: Enable DLLSC interrupt only if supported
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Don't enable Data Link Layer State Changed interrupt if it isn't
+supported.
+
+Data Link Layer Link Active Reporting Capable bit in Link Capabilities
+register indicates if Data Link Layer State Changed Enable is supported.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/pci/hotplug/pciehp_hpc.c | 32 ++++++++++++++++++++++++--------
+ drivers/pci/hotplug/pnv_php.c    | 13 +++++++++----
+ 2 files changed, 33 insertions(+), 12 deletions(-)
+
+diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c
+index 040ae076ec0e..ef807c79b1b1 100644
+--- a/drivers/pci/hotplug/pciehp_hpc.c
++++ b/drivers/pci/hotplug/pciehp_hpc.c
+@@ -788,6 +788,7 @@ static int pciehp_poll(void *data)
+ static void pcie_enable_notification(struct controller *ctrl)
+ {
+ 	u16 cmd, mask;
++	u32 link_cap;
+ 
+ 	/*
+ 	 * TBD: Power fault detected software notification support.
+@@ -800,12 +801,17 @@ static void pcie_enable_notification(struct controller *ctrl)
+ 	 * next power fault detected interrupt was notified again.
+ 	 */
+ 
++	pcie_capability_read_dword(ctrl_dev(ctrl), PCI_EXP_LNKCAP, &link_cap);
++
+ 	/*
+-	 * Always enable link events: thus link-up and link-down shall
+-	 * always be treated as hotplug and unplug respectively. Enable
+-	 * presence detect only if Attention Button is not present.
+-	 */
+-	cmd = PCI_EXP_SLTCTL_DLLSCE;
++	 * Enable link events if their support is indicated in Link Capability
++	 * register: thus link-up and link-down shall always be treated as
++	 * hotplug and unplug respectively. Enable presence detect only if
++	 * Attention Button is not present.
++	*/
++	cmd = 0;
++	if (link_cap & PCI_EXP_LNKCAP_DLLLARC)
++		cmd |= PCI_EXP_SLTCTL_DLLSCE;
+ 	if (ATTN_BUTTN(ctrl))
+ 		cmd |= PCI_EXP_SLTCTL_ABPE;
+ 	else
+@@ -845,8 +851,13 @@ void pcie_clear_hotplug_events(struct controller *ctrl)
+ void pcie_enable_interrupt(struct controller *ctrl)
+ {
+ 	u16 mask;
++	u32 link_cap;
+ 
+-	mask = PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_DLLSCE;
++	pcie_capability_read_dword(ctrl_dev(ctrl), PCI_EXP_LNKCAP, &link_cap);
++
++	mask = PCI_EXP_SLTCTL_HPIE;
++	if (link_cap & PCI_EXP_LNKCAP_DLLLARC)
++		mask |= PCI_EXP_SLTCTL_DLLSCE;
+ 	pcie_write_cmd(ctrl, mask, mask);
+ }
+ 
+@@ -904,19 +915,24 @@ int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, bool probe)
+ 	struct controller *ctrl = to_ctrl(hotplug_slot);
+ 	struct pci_dev *pdev = ctrl_dev(ctrl);
+ 	u16 stat_mask = 0, ctrl_mask = 0;
++	u32 link_cap;
+ 	int rc;
+ 
+ 	if (probe)
+ 		return 0;
+ 
++	pcie_capability_read_dword(pdev, PCI_EXP_LNKCAP, &link_cap);
++
+ 	down_write_nested(&ctrl->reset_lock, ctrl->depth);
+ 
+ 	if (!ATTN_BUTTN(ctrl)) {
+ 		ctrl_mask |= PCI_EXP_SLTCTL_PDCE;
+ 		stat_mask |= PCI_EXP_SLTSTA_PDC;
+ 	}
+-	ctrl_mask |= PCI_EXP_SLTCTL_DLLSCE;
+-	stat_mask |= PCI_EXP_SLTSTA_DLLSC;
++	if (link_cap & PCI_EXP_LNKCAP_DLLLARC) {
++		ctrl_mask |= PCI_EXP_SLTCTL_DLLSCE;
++		stat_mask |= PCI_EXP_SLTSTA_DLLSC;
++	}
+ 
+ 	pcie_write_cmd(ctrl, 0, ctrl_mask);
+ 	ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__,
+diff --git a/drivers/pci/hotplug/pnv_php.c b/drivers/pci/hotplug/pnv_php.c
+index 881d420637bf..5c700d3a9009 100644
+--- a/drivers/pci/hotplug/pnv_php.c
++++ b/drivers/pci/hotplug/pnv_php.c
+@@ -840,6 +840,7 @@ static void pnv_php_init_irq(struct pnv_php_slot *php_slot, int irq)
+ {
+ 	struct pci_dev *pdev = php_slot->pdev;
+ 	u32 broken_pdc = 0;
++	u32 link_cap;
+ 	u16 sts, ctrl;
+ 	int ret;
+ 
+@@ -874,17 +875,21 @@ static void pnv_php_init_irq(struct pnv_php_slot *php_slot, int irq)
+ 		return;
+ 	}
+ 
++	pcie_capability_read_dword(pdev, PCI_EXP_LNKCAP, &link_cap);
++
+ 	/* Enable the interrupts */
+ 	pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &ctrl);
+ 	if (php_slot->flags & PNV_PHP_FLAG_BROKEN_PDC) {
+ 		ctrl &= ~PCI_EXP_SLTCTL_PDCE;
+-		ctrl |= (PCI_EXP_SLTCTL_HPIE |
+-			 PCI_EXP_SLTCTL_DLLSCE);
++		ctrl |= PCI_EXP_SLTCTL_HPIE;
+ 	} else {
+ 		ctrl |= (PCI_EXP_SLTCTL_HPIE |
+-			 PCI_EXP_SLTCTL_PDCE |
+-			 PCI_EXP_SLTCTL_DLLSCE);
++				PCI_EXP_SLTCTL_PDCE);
+ 	}
++	if (link_cap & PCI_EXP_LNKCAP_DLLLARC)
++		ctrl |= PCI_EXP_SLTCTL_DLLSCE;
++	else
++		ctrl &= ~PCI_EXP_SLTCTL_DLLSCE;
+ 	pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, ctrl);
+ 
+ 	/* The interrupt is initialized successfully when @irq is valid */
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0013-PCI-pciehp-Enable-Command-Completed-Interrupt-only-i.patch b/nixos/modules/kernel-patches/0013-PCI-pciehp-Enable-Command-Completed-Interrupt-only-i.patch
new file mode 100644
index 0000000..f34c666
--- /dev/null
+++ b/nixos/modules/kernel-patches/0013-PCI-pciehp-Enable-Command-Completed-Interrupt-only-i.patch
@@ -0,0 +1,38 @@
+From 7cc3ad181a36378397c4cdb68d034b7c719c17ab Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Wed, 31 Mar 2021 15:14:29 +0200
+Subject: [PATCH 13/96] PCI: pciehp: Enable Command Completed Interrupt only if
+ supported
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The No Command Completed Support bit in the Slot Capabilities register
+indicates whether Command Completed Interrupt Enable is unsupported.
+
+Enable this interrupt only in the case it is supported.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/pci/hotplug/pciehp_hpc.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c
+index ef807c79b1b1..a5199b312e74 100644
+--- a/drivers/pci/hotplug/pciehp_hpc.c
++++ b/drivers/pci/hotplug/pciehp_hpc.c
+@@ -817,7 +817,9 @@ static void pcie_enable_notification(struct controller *ctrl)
+ 	else
+ 		cmd |= PCI_EXP_SLTCTL_PDCE;
+ 	if (!pciehp_poll_mode)
+-		cmd |= PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_CCIE;
++		cmd |= PCI_EXP_SLTCTL_HPIE;
++	if (!pciehp_poll_mode && !NO_CMD_CMPL(ctrl))
++		cmd |= PCI_EXP_SLTCTL_CCIE;
+ 
+ 	mask = (PCI_EXP_SLTCTL_PDCE | PCI_EXP_SLTCTL_ABPE |
+ 		PCI_EXP_SLTCTL_PFDE |
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0014-PCI-mvebu-Add-support-for-PCI_EXP_SLTSTA_DLLSC-via-h.patch b/nixos/modules/kernel-patches/0014-PCI-mvebu-Add-support-for-PCI_EXP_SLTSTA_DLLSC-via-h.patch
new file mode 100644
index 0000000..febba42
--- /dev/null
+++ b/nixos/modules/kernel-patches/0014-PCI-mvebu-Add-support-for-PCI_EXP_SLTSTA_DLLSC-via-h.patch
@@ -0,0 +1,297 @@
+From 1acf23b5be75ad9981eafb94044e507a53cb29c8 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Fri, 17 Sep 2021 14:53:11 +0200
+Subject: [PATCH 14/96] PCI: mvebu: Add support for PCI_EXP_SLTSTA_DLLSC via
+ hot plug interrupt
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+If link up/down state is changed in mvebu_pcie_link_up() then trigger
+hot plug interrupt with DLLSC state change.
+
+Also triggers hot plug interrupt when mvebu triggers Link Failure interrupt
+which indicates that link was changed from active state or when mvebu
+triggers TxReq No Link interrupt which indicates that link is down while
+trying to transmit PCIe transaction.
+
+And this hot plug interrupt also when explicit Link Disable or PCIe Host
+Reset is issued as mvebu does not trigger Link Failure when dropping to
+Detect via Hot Reset or Link Disable.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/controller/Kconfig     |   3 +
+ drivers/pci/controller/pci-mvebu.c | 147 ++++++++++++++++++++++++++++-
+ 2 files changed, 149 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
+index b8d96d38064d..e4635712dbce 100644
+--- a/drivers/pci/controller/Kconfig
++++ b/drivers/pci/controller/Kconfig
+@@ -10,6 +10,9 @@ config PCI_MVEBU
+ 	depends on ARM
+ 	depends on OF
+ 	select PCI_BRIDGE_EMUL
++	select PCIEPORTBUS
++	select HOTPLUG_PCI
++	select HOTPLUG_PCI_PCIE
+ 	help
+ 	 Add support for Marvell EBU PCIe controller. This PCIe controller
+ 	 is used on 32-bit Marvell ARM SoCs: Dove, Kirkwood, Armada 370,
+diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
+index 56924b0a1969..d832c5135d5c 100644
+--- a/drivers/pci/controller/pci-mvebu.c
++++ b/drivers/pci/controller/pci-mvebu.c
+@@ -56,12 +56,14 @@
+ #define PCIE_CONF_DATA_OFF	0x18fc
+ #define PCIE_INT_CAUSE_OFF	0x1900
+ #define PCIE_INT_UNMASK_OFF	0x1910
++#define  PCIE_INT_TXREQ_NOLINK		BIT(0)
+ #define  PCIE_INT_DET_COR		BIT(8)
+ #define  PCIE_INT_DET_NONFATAL		BIT(9)
+ #define  PCIE_INT_DET_FATAL		BIT(10)
+ #define  PCIE_INT_ERR_FATAL		BIT(16)
+ #define  PCIE_INT_ERR_NONFATAL		BIT(17)
+ #define  PCIE_INT_ERR_COR		BIT(18)
++#define  PCIE_INT_LINK_FAIL		BIT(23)
+ #define  PCIE_INT_INTX(i)		BIT(24+i)
+ #define  PCIE_INT_PM_PME		BIT(28)
+ #define  PCIE_INT_DET_MASK		(PCIE_INT_DET_COR | PCIE_INT_DET_NONFATAL | PCIE_INT_DET_FATAL)
+@@ -134,6 +136,8 @@ struct mvebu_pcie_port {
+ 	int error_irq;
+ 	int intx_irq;
+ 	bool pme_pending;
++	struct timer_list link_irq_timer;
++	bool link_was_up;
+ };
+ 
+ static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
+@@ -153,7 +157,26 @@ static inline bool mvebu_has_ioport(struct mvebu_pcie_port *port)
+ 
+ static bool mvebu_pcie_link_up(struct mvebu_pcie_port *port)
+ {
+-	return !(mvebu_readl(port, PCIE_STAT_OFF) & PCIE_STAT_LINK_DOWN);
++	bool link_is_up;
++	u16 slotsta;
++
++	link_is_up = !(mvebu_readl(port, PCIE_STAT_OFF) & PCIE_STAT_LINK_DOWN);
++
++	if (link_is_up != port->link_was_up) {
++		port->link_was_up = link_is_up;
++		/*
++		 * Link IRQ timer/handler is available only when "error"
++		 * interrupt was specified in DT.
++		 */
++		if (port->error_irq > 0) {
++			slotsta = le16_to_cpu(port->bridge.pcie_conf.slotsta);
++			port->bridge.pcie_conf.slotsta =
++				cpu_to_le16(slotsta | PCI_EXP_SLTSTA_DLLSC);
++			mod_timer(&port->link_irq_timer, jiffies + 1);
++		}
++	}
++
++	return link_is_up;
+ }
+ 
+ static u8 mvebu_pcie_get_local_bus_nr(struct mvebu_pcie_port *port)
+@@ -346,6 +369,19 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
+ 		mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
+ 	}
+ 
++	/*
++	 * Unmask No Link and Link Failure interrupts to process Link Down
++	 * events. These events are reported as Data Link Layer State Changed
++	 * notification via Hot Plug Interrupt. Other parts of Link change
++	 * events are available only when "error" interrupt was specified in DT.
++	 * So enable these interrupts under same conditions.
++	 */
++	if (port->error_irq > 0) {
++		unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
++		unmask |= PCIE_INT_TXREQ_NOLINK | PCIE_INT_LINK_FAIL;
++		mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
++	}
++
+ 	/*
+ 	 * Fallback code when "intx" interrupt was not specified in DT:
+ 	 * Unmask all legacy INTx interrupts as driver does not provide a way
+@@ -692,6 +728,14 @@ mvebu_pci_bridge_emul_pcie_conf_read(struct pci_bridge_emul *bridge,
+ 			val |= slotctl & PCI_EXP_SLTCTL_ASPL_DISABLE;
+ 		else if (!(mvebu_readl(port, PCIE_SSPL_OFF) & PCIE_SSPL_ENABLE))
+ 			val |= PCI_EXP_SLTCTL_ASPL_DISABLE;
++		/*
++		 * HPIE and DLLSCE bits are stored only in emulated config
++		 * space buffer and are supported only when or "error" interrupt
++		 * was specified in DT.
++		 */
++		if (port->error_irq > 0)
++			val |= slotctl & (PCI_EXP_SLTCTL_HPIE |
++					  PCI_EXP_SLTCTL_DLLSCE);
+ 		/* This callback is 32-bit and in high bits is slot status. */
+ 		val |= slotsta << 16;
+ 		*value = val;
+@@ -823,6 +867,25 @@ mvebu_pci_bridge_emul_base_conf_write(struct pci_bridge_emul *bridge,
+ 			else
+ 				ctrl &= ~PCIE_CTRL_MASTER_HOT_RESET;
+ 			mvebu_writel(port, ctrl, PCIE_CTRL_OFF);
++			/*
++			 * When dropping to Detect via Hot Reset, Disable Link
++			 * or Loopback states, the Link Failure interrupt is not
++			 * asserted. So when setting Secondary Bus Reset / Hot
++			 * Reset bit, call link IRQ timer/handler manually.
++			 */
++			if ((ctrl & PCIE_CTRL_MASTER_HOT_RESET) && port->link_was_up) {
++				port->link_was_up = false;
++				/*
++				 * Link IRQ timer/handler is available only when
++				 * "error" interrupt was specified in DT.
++				 */
++				if (port->error_irq > 0) {
++					u16 slotsta = le16_to_cpu(port->bridge.pcie_conf.slotsta);
++					port->bridge.pcie_conf.slotsta =
++						cpu_to_le16(slotsta | PCI_EXP_SLTSTA_DLLSC);
++					mod_timer(&port->link_irq_timer, jiffies + 1);
++				}
++			}
+ 		}
+ 		break;
+ 
+@@ -851,6 +914,25 @@ mvebu_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge,
+ 		new &= ~PCI_EXP_LNKCTL_CLKREQ_EN;
+ 
+ 		mvebu_writel(port, new, PCIE_CAP_PCIEXP + PCI_EXP_LNKCTL);
++		/*
++		 * When dropping to Detect via Hot Reset, Disable Link
++		 * or Loopback states, the Link Failure interrupt is not
++		 * asserted. So when setting Link Disable bit, call link
++		 * IRQ timer/handler manually.
++		 */
++		if ((new & PCI_EXP_LNKCTL_LD) && port->link_was_up) {
++			port->link_was_up = false;
++			/*
++			 * Link IRQ timer/handler is available only when
++			 * "error" interrupt was specified in DT.
++			 */
++			if (port->error_irq > 0) {
++				u16 slotsta = le16_to_cpu(port->bridge.pcie_conf.slotsta);
++				port->bridge.pcie_conf.slotsta =
++					cpu_to_le16(slotsta | PCI_EXP_SLTSTA_DLLSC);
++				mod_timer(&port->link_irq_timer, jiffies + 1);
++			}
++		}
+ 		break;
+ 
+ 	case PCI_EXP_SLTCTL:
+@@ -991,6 +1073,15 @@ static int mvebu_pci_bridge_emul_init(struct mvebu_pcie_port *port)
+ 	bridge->pcie_conf.cap = cpu_to_le16(pcie_cap_ver | PCI_EXP_FLAGS_SLOT);
+ 
+ 	/*
++	 * When "error" interrupt was specified in DT then driver is able to
++	 * deliver Data Link Layer State Change interrupt. So in this case mark
++	 * bridge as Hot Plug Capable as this is the way how to enable
++	 * delivering of Data Link Layer State Change interrupts.
++	 *
++	 * No Command Completed Support is set because bridge does not support
++	 * Command Completed Interrupt. Every command is executed immediately
++	 * without any delay.
++	 *
+ 	 * Set Presence Detect State bit permanently as there is no support for
+ 	 * unplugging PCIe card from the slot. Assume that PCIe card is always
+ 	 * connected in slot.
+@@ -1002,6 +1093,8 @@ static int mvebu_pci_bridge_emul_init(struct mvebu_pcie_port *port)
+ 	 * Also set correct slot power limit.
+ 	 */
+ 	bridge->pcie_conf.slotcap = cpu_to_le32(
++		PCI_EXP_SLTCAP_NCCS |
++		(port->error_irq > 0 ? PCI_EXP_SLTCAP_HPC : 0) |
+ 		FIELD_PREP(PCI_EXP_SLTCAP_SPLV, port->slot_power_limit_value) |
+ 		FIELD_PREP(PCI_EXP_SLTCAP_SPLS, port->slot_power_limit_scale) |
+ 		FIELD_PREP(PCI_EXP_SLTCAP_PSN, port->port+1));
+@@ -1191,11 +1284,29 @@ static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
+ 	return 0;
+ }
+ 
++static void mvebu_pcie_link_irq_handler(struct timer_list *timer)
++{
++	struct mvebu_pcie_port *port = from_timer(port, timer, link_irq_timer);
++	struct device *dev = &port->pcie->pdev->dev;
++	u16 slotctl;
++
++	dev_info(dev, "%s: link %s\n", port->name, port->link_was_up ? "up" : "down");
++
++	slotctl = le16_to_cpu(port->bridge.pcie_conf.slotctl);
++	if (!(slotctl & PCI_EXP_SLTCTL_DLLSCE) ||
++	    !(slotctl & PCI_EXP_SLTCTL_HPIE))
++		return;
++
++	if (generic_handle_domain_irq(port->rp_irq_domain, 0) == -EINVAL)
++		dev_err_ratelimited(dev, "unhandled HP IRQ\n");
++}
++
+ static irqreturn_t mvebu_pcie_error_irq_handler(int irq, void *arg)
+ {
+ 	struct mvebu_pcie_port *port = arg;
+ 	struct device *dev = &port->pcie->pdev->dev;
+ 	u32 cause, unmask, status;
++	u16 slotsta;
+ 
+ 	cause = mvebu_readl(port, PCIE_INT_CAUSE_OFF);
+ 	unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
+@@ -1233,6 +1344,25 @@ static irqreturn_t mvebu_pcie_error_irq_handler(int irq, void *arg)
+ 			dev_err_ratelimited(dev, "unhandled ERR IRQ\n");
+ 	}
+ 
++	/* Process No Link and Link Failure interrupts as HP IRQ */
++	if (status & (PCIE_INT_TXREQ_NOLINK | PCIE_INT_LINK_FAIL)) {
++		mvebu_writel(port,
++			     ~(PCIE_INT_TXREQ_NOLINK | PCIE_INT_LINK_FAIL),
++			     PCIE_INT_CAUSE_OFF);
++		if (port->link_was_up) {
++			port->link_was_up = false;
++			slotsta = le16_to_cpu(port->bridge.pcie_conf.slotsta);
++			port->bridge.pcie_conf.slotsta =
++				cpu_to_le16(slotsta | PCI_EXP_SLTSTA_DLLSC);
++			/*
++			 * Deactivate timer and call mvebu_pcie_link_irq_handler()
++			 * function directly as we are in the interrupt context.
++			 */
++			del_timer_sync(&port->link_irq_timer);
++			mvebu_pcie_link_irq_handler(&port->link_irq_timer);
++		}
++	}
++
+ 	return status ? IRQ_HANDLED : IRQ_NONE;
+ }
+ 
+@@ -1798,6 +1928,18 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
+ 			}
+ 		}
+ 
++		/*
++		 * Function mvebu_pcie_link_irq_handler() calls function
++		 * generic_handle_irq() and it expects local IRQs to be disabled
++		 * as normally generic_handle_irq() is called from the interrupt
++		 * context. So use TIMER_IRQSAFE flag for this link_irq_timer.
++		 * Available only if "or "error" interrupt was specified.
++		 */
++		if (port->error_irq > 0)
++			timer_setup(&port->link_irq_timer,
++				    mvebu_pcie_link_irq_handler,
++				    TIMER_IRQSAFE);
++
+ 		/*
+ 		 * PCIe topology exported by mvebu hw is quite complicated. In
+ 		 * reality has something like N fully independent host bridges
+@@ -1934,6 +2076,9 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
+ 			irq_domain_remove(port->rp_irq_domain);
+ 		}
+ 
++		if (port->error_irq > 0)
++			del_timer_sync(&port->link_irq_timer);
++
+ 		/* Free config space for emulated root bridge. */
+ 		pci_bridge_emul_cleanup(&port->bridge);
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0015-PCI-mvebu-use-BIT-and-GENMASK-macros-instead-of-hard.patch b/nixos/modules/kernel-patches/0015-PCI-mvebu-use-BIT-and-GENMASK-macros-instead-of-hard.patch
new file mode 100644
index 0000000..1e75168
--- /dev/null
+++ b/nixos/modules/kernel-patches/0015-PCI-mvebu-use-BIT-and-GENMASK-macros-instead-of-hard.patch
@@ -0,0 +1,47 @@
+From 4b442a1a2a4a00ae7f776bffe53e17421621c57b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Fri, 17 Sep 2021 14:54:29 +0200
+Subject: [PATCH 15/96] PCI: mvebu: use BIT() and GENMASK() macros instead of
+ hardcoded hex values
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/controller/pci-mvebu.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
+index d832c5135d5c..90a32db42a87 100644
+--- a/drivers/pci/controller/pci-mvebu.c
++++ b/drivers/pci/controller/pci-mvebu.c
+@@ -44,7 +44,7 @@
+ #define PCIE_WIN5_BASE_OFF	0x1884
+ #define PCIE_WIN5_REMAP_OFF	0x188c
+ #define PCIE_CONF_ADDR_OFF	0x18f8
+-#define  PCIE_CONF_ADDR_EN		0x80000000
++#define  PCIE_CONF_ADDR_EN		BIT(31)
+ #define  PCIE_CONF_REG(r)		((((r) & 0xf00) << 16) | ((r) & 0xfc))
+ #define  PCIE_CONF_BUS(b)		(((b) & 0xff) << 16)
+ #define  PCIE_CONF_DEV(d)		(((d) & 0x1f) << 11)
+@@ -70,13 +70,13 @@
+ #define  PCIE_INT_ERR_MASK		(PCIE_INT_ERR_FATAL | PCIE_INT_ERR_NONFATAL | PCIE_INT_ERR_COR)
+ #define  PCIE_INT_ALL_MASK		GENMASK(31, 0)
+ #define PCIE_CTRL_OFF		0x1a00
+-#define  PCIE_CTRL_X1_MODE		0x0001
++#define  PCIE_CTRL_X1_MODE		BIT(0)
+ #define  PCIE_CTRL_RC_MODE		BIT(1)
+ #define  PCIE_CTRL_MASTER_HOT_RESET	BIT(24)
+ #define PCIE_STAT_OFF		0x1a04
+-#define  PCIE_STAT_BUS                  0xff00
+-#define  PCIE_STAT_DEV                  0x1f0000
+ #define  PCIE_STAT_LINK_DOWN		BIT(0)
++#define  PCIE_STAT_BUS			GENMASK(15, 8)
++#define  PCIE_STAT_DEV			GENMASK(20, 16)
+ #define PCIE_SSPL_OFF		0x1a0c
+ #define  PCIE_SSPL_VALUE_SHIFT		0
+ #define  PCIE_SSPL_VALUE_MASK		GENMASK(7, 0)
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0016-PCI-mvebu-For-consistency-add-_OFF-suffix-to-all-reg.patch b/nixos/modules/kernel-patches/0016-PCI-mvebu-For-consistency-add-_OFF-suffix-to-all-reg.patch
new file mode 100644
index 0000000..e112cdd
--- /dev/null
+++ b/nixos/modules/kernel-patches/0016-PCI-mvebu-For-consistency-add-_OFF-suffix-to-all-reg.patch
@@ -0,0 +1,167 @@
+From e8bd26a6ae1f60ed750b7210383f68f0dd091339 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Fri, 17 Sep 2021 14:55:03 +0200
+Subject: [PATCH 16/96] PCI: mvebu: For consistency add _OFF suffix to all
+ registers
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/controller/pci-mvebu.c | 40 +++++++++++++++---------------
+ 1 file changed, 20 insertions(+), 20 deletions(-)
+
+diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
+index 90a32db42a87..d5b630e27882 100644
+--- a/drivers/pci/controller/pci-mvebu.c
++++ b/drivers/pci/controller/pci-mvebu.c
+@@ -34,7 +34,7 @@
+ #define PCIE_BAR_LO_OFF(n)	(0x0010 + ((n) << 3))
+ #define PCIE_BAR_HI_OFF(n)	(0x0014 + ((n) << 3))
+ #define PCIE_SSDEV_ID_OFF	0x002c
+-#define PCIE_CAP_PCIEXP		0x0060
++#define PCIE_CAP_PCIEXP_OFF	0x0060
+ #define PCIE_CAP_PCIERR_OFF	0x0100
+ #define PCIE_BAR_CTRL_OFF(n)	(0x1804 + (((n) - 1) * 4))
+ #define PCIE_WIN04_CTRL_OFF(n)	(0x1820 + ((n) << 4))
+@@ -83,8 +83,8 @@
+ #define  PCIE_SSPL_SCALE_SHIFT		8
+ #define  PCIE_SSPL_SCALE_MASK		GENMASK(9, 8)
+ #define  PCIE_SSPL_ENABLE		BIT(16)
+-#define PCIE_RC_RTSTA		0x1a14
+-#define PCIE_DEBUG_CTRL         0x1a60
++#define PCIE_RC_RTSTA_OFF	0x1a14
++#define PCIE_DEBUG_CTRL_OFF	0x1a60
+ #define  PCIE_DEBUG_SOFT_RESET		BIT(20)
+ 
+ struct mvebu_pcie_port;
+@@ -296,10 +296,10 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
+ 	 * be set to number of SerDes PCIe lanes (1 or 4). If this register is
+ 	 * not set correctly then link with endpoint card is not established.
+ 	 */
+-	lnkcap = mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP);
++	lnkcap = mvebu_readl(port, PCIE_CAP_PCIEXP_OFF + PCI_EXP_LNKCAP);
+ 	lnkcap &= ~PCI_EXP_LNKCAP_MLW;
+ 	lnkcap |= (port->is_x4 ? 4 : 1) << 4;
+-	mvebu_writel(port, lnkcap, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP);
++	mvebu_writel(port, lnkcap, PCIE_CAP_PCIEXP_OFF + PCI_EXP_LNKCAP);
+ 
+ 	/* Disable Root Bridge I/O space, memory space and bus mastering. */
+ 	cmd = mvebu_readl(port, PCIE_CMD_OFF);
+@@ -690,11 +690,11 @@ mvebu_pci_bridge_emul_pcie_conf_read(struct pci_bridge_emul *bridge,
+ 
+ 	switch (reg) {
+ 	case PCI_EXP_DEVCAP:
+-		*value = mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_DEVCAP);
++		*value = mvebu_readl(port, PCIE_CAP_PCIEXP_OFF + PCI_EXP_DEVCAP);
+ 		break;
+ 
+ 	case PCI_EXP_DEVCTL:
+-		*value = mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_DEVCTL);
++		*value = mvebu_readl(port, PCIE_CAP_PCIEXP_OFF + PCI_EXP_DEVCTL);
+ 		break;
+ 
+ 	case PCI_EXP_LNKCAP:
+@@ -704,13 +704,13 @@ mvebu_pci_bridge_emul_pcie_conf_read(struct pci_bridge_emul *bridge,
+ 		 * Additionally enable Data Link Layer Link Active Reporting
+ 		 * Capable bit as DL_Active indication is provided too.
+ 		 */
+-		*value = (mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP) &
++		*value = (mvebu_readl(port, PCIE_CAP_PCIEXP_OFF + PCI_EXP_LNKCAP) &
+ 			  ~PCI_EXP_LNKCAP_CLKPM) | PCI_EXP_LNKCAP_DLLLARC;
+ 		break;
+ 
+ 	case PCI_EXP_LNKCTL:
+ 		/* DL_Active indication is provided via PCIE_STAT_OFF */
+-		*value = mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_LNKCTL) |
++		*value = mvebu_readl(port, PCIE_CAP_PCIEXP_OFF + PCI_EXP_LNKCTL) |
+ 			 (mvebu_pcie_link_up(port) ?
+ 			  (PCI_EXP_LNKSTA_DLLLA << 16) : 0);
+ 		break;
+@@ -748,19 +748,19 @@ mvebu_pci_bridge_emul_pcie_conf_read(struct pci_bridge_emul *bridge,
+ 		break;
+ 
+ 	case PCI_EXP_RTSTA:
+-		*value = mvebu_readl(port, PCIE_RC_RTSTA);
++		*value = mvebu_readl(port, PCIE_RC_RTSTA_OFF);
+ 		break;
+ 
+ 	case PCI_EXP_DEVCAP2:
+-		*value = mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_DEVCAP2);
++		*value = mvebu_readl(port, PCIE_CAP_PCIEXP_OFF + PCI_EXP_DEVCAP2);
+ 		break;
+ 
+ 	case PCI_EXP_DEVCTL2:
+-		*value = mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_DEVCTL2);
++		*value = mvebu_readl(port, PCIE_CAP_PCIEXP_OFF + PCI_EXP_DEVCTL2);
+ 		break;
+ 
+ 	case PCI_EXP_LNKCTL2:
+-		*value = mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_LNKCTL2);
++		*value = mvebu_readl(port, PCIE_CAP_PCIEXP_OFF + PCI_EXP_LNKCTL2);
+ 		break;
+ 
+ 	default:
+@@ -902,7 +902,7 @@ mvebu_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge,
+ 
+ 	switch (reg) {
+ 	case PCI_EXP_DEVCTL:
+-		mvebu_writel(port, new, PCIE_CAP_PCIEXP + PCI_EXP_DEVCTL);
++		mvebu_writel(port, new, PCIE_CAP_PCIEXP_OFF + PCI_EXP_DEVCTL);
+ 		break;
+ 
+ 	case PCI_EXP_LNKCTL:
+@@ -913,7 +913,7 @@ mvebu_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge,
+ 		 */
+ 		new &= ~PCI_EXP_LNKCTL_CLKREQ_EN;
+ 
+-		mvebu_writel(port, new, PCIE_CAP_PCIEXP + PCI_EXP_LNKCTL);
++		mvebu_writel(port, new, PCIE_CAP_PCIEXP_OFF + PCI_EXP_LNKCTL);
+ 		/*
+ 		 * When dropping to Detect via Hot Reset, Disable Link
+ 		 * or Loopback states, the Link Failure interrupt is not
+@@ -953,7 +953,7 @@ mvebu_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge,
+ 
+ 	case PCI_EXP_RTSTA:
+ 		/*
+-		 * PME Status bit in Root Status Register (PCIE_RC_RTSTA)
++		 * PME Status bit in Root Status Register (PCIE_RC_RTSTA_OFF)
+ 		 * is read-only and can be cleared only by writing 0b to the
+ 		 * Interrupt Cause RW0C register (PCIE_INT_CAUSE_OFF). So
+ 		 * clear PME via Interrupt Cause and also set port->pme_pending
+@@ -978,11 +978,11 @@ mvebu_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge,
+ 		break;
+ 
+ 	case PCI_EXP_DEVCTL2:
+-		mvebu_writel(port, new, PCIE_CAP_PCIEXP + PCI_EXP_DEVCTL2);
++		mvebu_writel(port, new, PCIE_CAP_PCIEXP_OFF + PCI_EXP_DEVCTL2);
+ 		break;
+ 
+ 	case PCI_EXP_LNKCTL2:
+-		mvebu_writel(port, new, PCIE_CAP_PCIEXP + PCI_EXP_LNKCTL2);
++		mvebu_writel(port, new, PCIE_CAP_PCIEXP_OFF + PCI_EXP_LNKCTL2);
+ 		break;
+ 
+ 	default:
+@@ -1042,7 +1042,7 @@ static int mvebu_pci_bridge_emul_init(struct mvebu_pcie_port *port)
+ 	u32 dev_id = mvebu_readl(port, PCIE_DEV_ID_OFF);
+ 	u32 dev_rev = mvebu_readl(port, PCIE_DEV_REV_OFF);
+ 	u32 ssdev_id = mvebu_readl(port, PCIE_SSDEV_ID_OFF);
+-	u32 pcie_cap = mvebu_readl(port, PCIE_CAP_PCIEXP);
++	u32 pcie_cap = mvebu_readl(port, PCIE_CAP_PCIEXP_OFF);
+ 	u8 pcie_cap_ver = ((pcie_cap >> 16) & PCI_EXP_FLAGS_VERS);
+ 
+ 	bridge->conf.vendor = cpu_to_le16(dev_id & 0xffff);
+@@ -1103,7 +1103,7 @@ static int mvebu_pci_bridge_emul_init(struct mvebu_pcie_port *port)
+ 	bridge->subsystem_vendor_id = ssdev_id & 0xffff;
+ 	bridge->subsystem_id = ssdev_id >> 16;
+ 	bridge->has_pcie = true;
+-	bridge->pcie_start = PCIE_CAP_PCIEXP;
++	bridge->pcie_start = PCIE_CAP_PCIEXP_OFF;
+ 	bridge->data = port;
+ 	bridge->ops = &mvebu_pci_bridge_emul_ops;
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0017-PCI-aardvark-Add-support-for-PCI-Bridge-Subsystem-Ve.patch b/nixos/modules/kernel-patches/0017-PCI-aardvark-Add-support-for-PCI-Bridge-Subsystem-Ve.patch
new file mode 100644
index 0000000..083ed79
--- /dev/null
+++ b/nixos/modules/kernel-patches/0017-PCI-aardvark-Add-support-for-PCI-Bridge-Subsystem-Ve.patch
@@ -0,0 +1,45 @@
+From 3a116a12e6e1e76971315d233007941673adb8c8 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Tue, 19 Oct 2021 13:54:19 +0200
+Subject: [PATCH 17/96] PCI: aardvark: Add support for PCI Bridge Subsystem
+ Vendor ID on emulated bridge
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Register with Subsystem Device/Vendor ID is at offset 0x2c. Export is via
+emulated bridge.
+
+After this change Subsystem ID is visible in lspci output at line:
+
+  Capabilities: [40] Subsystem
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/controller/pci-aardvark.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
+index 3767b25769bc..dd1b29af0470 100644
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -33,6 +33,7 @@
+ #define PCIE_CORE_DEV_ID_REG					0x0
+ #define PCIE_CORE_CMD_STATUS_REG				0x4
+ #define PCIE_CORE_DEV_REV_REG					0x8
++#define PCIE_CORE_SSDEV_ID_REG					0x2c
+ #define PCIE_CORE_PCIEXP_CAP					0xc0
+ #define PCIE_CORE_ERR_CAPCTL_REG				0x118
+ #define     PCIE_CORE_ERR_CAPCTL_ECRC_CHK_TX			BIT(5)
+@@ -998,6 +999,8 @@ static int advk_sw_pci_bridge_init(struct advk_pcie *pcie)
+ 	/* Indicates supports for Completion Retry Status */
+ 	bridge->pcie_conf.rootcap = cpu_to_le16(PCI_EXP_RTCAP_CRSVIS);
+ 
++	bridge->subsystem_vendor_id = advk_readl(pcie, PCIE_CORE_SSDEV_ID_REG) & 0xffff;
++	bridge->subsystem_id = advk_readl(pcie, PCIE_CORE_SSDEV_ID_REG) >> 16;
+ 	bridge->has_pcie = true;
+ 	bridge->pcie_start = PCIE_CORE_PCIEXP_CAP;
+ 	bridge->data = pcie;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0018-PCI-aardvark-Dispose-INTx-irqs-prior-to-removing-INT.patch b/nixos/modules/kernel-patches/0018-PCI-aardvark-Dispose-INTx-irqs-prior-to-removing-INT.patch
new file mode 100644
index 0000000..4b4f856
--- /dev/null
+++ b/nixos/modules/kernel-patches/0018-PCI-aardvark-Dispose-INTx-irqs-prior-to-removing-INT.patch
@@ -0,0 +1,44 @@
+From 3c6148dc2653a7026a1e8295b1eccb98fdd1c3ac Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Sat, 9 Jul 2022 16:55:54 +0200
+Subject: [PATCH 18/96] PCI: aardvark: Dispose INTx irqs prior to removing INTx
+ domain
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Documentation for irq_domain_remove() says that all mapping within the
+domain must be disposed prior to domain remove.
+
+Currently INTx irqs are not disposed in pci-aardvark.c device unbind callback
+which cause that kernel crashes after unloading driver and trying to read
+/sys/kernel/debug/irq/irqs/<num> or /proc/interrupts.
+
+Fixes: 526a76991b7b ("PCI: aardvark: Implement driver 'remove' function and allow to build it as module")
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/controller/pci-aardvark.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
+index dd1b29af0470..a97502b2aef1 100644
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -1452,6 +1452,14 @@ static int advk_pcie_init_irq_domain(struct advk_pcie *pcie)
+ 
+ static void advk_pcie_remove_irq_domain(struct advk_pcie *pcie)
+ {
++	int virq, i;
++
++	for (i = 0; i < PCI_NUM_INTX; i++) {
++		virq = irq_find_mapping(pcie->irq_domain, i);
++		if (virq > 0)
++			irq_dispose_mapping(virq);
++	}
++
+ 	irq_domain_remove(pcie->irq_domain);
+ }
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0019-PCI-aardvark-Add-support-for-AER-registers-on-emulat.patch b/nixos/modules/kernel-patches/0019-PCI-aardvark-Add-support-for-AER-registers-on-emulat.patch
new file mode 100644
index 0000000..6a28e7f
--- /dev/null
+++ b/nixos/modules/kernel-patches/0019-PCI-aardvark-Add-support-for-AER-registers-on-emulat.patch
@@ -0,0 +1,139 @@
+From cbe67d3f1b2565b4d94ec08cbc2b144165cb9117 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Tue, 24 May 2022 15:28:26 +0200
+Subject: [PATCH 19/96] PCI: aardvark: Add support for AER registers on
+ emulated bridge
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Aardvark controller supports Advanced Error Reporting configuration
+registers.
+
+Export these registers on the emulated root bridge via new .read_ext() and
+.write_ext() methods.
+
+Note that in the Advanced Error Reporting Capability header the offset to
+the next Extended Capability header is set, but it is not documented in
+Armada 3700 Functional Specification. Since this change adds support only
+for Advanced Error Reporting, explicitly clear PCI_EXT_CAP_NEXT bits in AER
+capability header.
+
+Now the pcieport driver correctly detects AER support and allows PCIe AER
+driver to start receiving ERR interrupts. Kernel log now says:
+
+  pcieport 0000:00:00.0: AER: enabled with IRQ 52
+
+Link: https://lore.kernel.org/r/20220524132827.8837-2-kabel@kernel.org
+Signed-off-by: Pali Rohár <pali@kernel.org>
+Signed-off-by: Marek Behún <kabel@kernel.org>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+---
+ drivers/pci/controller/pci-aardvark.c | 79 +++++++++++++++++++++++++++
+ 1 file changed, 79 insertions(+)
+
+diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
+index a97502b2aef1..4921d9cf80d2 100644
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -35,6 +35,7 @@
+ #define PCIE_CORE_DEV_REV_REG					0x8
+ #define PCIE_CORE_SSDEV_ID_REG					0x2c
+ #define PCIE_CORE_PCIEXP_CAP					0xc0
++#define PCIE_CORE_PCIERR_CAP					0x100
+ #define PCIE_CORE_ERR_CAPCTL_REG				0x118
+ #define     PCIE_CORE_ERR_CAPCTL_ECRC_CHK_TX			BIT(5)
+ #define     PCIE_CORE_ERR_CAPCTL_ECRC_CHK_TX_EN			BIT(6)
+@@ -943,11 +944,89 @@ advk_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge,
+ 	}
+ }
+ 
++static pci_bridge_emul_read_status_t
++advk_pci_bridge_emul_ext_conf_read(struct pci_bridge_emul *bridge,
++				   int reg, u32 *value)
++{
++	struct advk_pcie *pcie = bridge->data;
++
++	switch (reg) {
++	case 0:
++		*value = advk_readl(pcie, PCIE_CORE_PCIERR_CAP + reg);
++
++		/*
++		 * PCI_EXT_CAP_NEXT bits are set to offset 0x150, but Armada
++		 * 3700 Functional Specification does not document registers
++		 * at those addresses.
++		 *
++		 * Thus we clear PCI_EXT_CAP_NEXT bits to make Advanced Error
++		 * Reporting Capability header the last Extended Capability.
++		 * If we obtain documentation for those registers in the
++		 * future, this can be changed.
++		 */
++		*value &= 0x000fffff;
++		return PCI_BRIDGE_EMUL_HANDLED;
++
++	case PCI_ERR_UNCOR_STATUS:
++	case PCI_ERR_UNCOR_MASK:
++	case PCI_ERR_UNCOR_SEVER:
++	case PCI_ERR_COR_STATUS:
++	case PCI_ERR_COR_MASK:
++	case PCI_ERR_CAP:
++	case PCI_ERR_HEADER_LOG + 0:
++	case PCI_ERR_HEADER_LOG + 4:
++	case PCI_ERR_HEADER_LOG + 8:
++	case PCI_ERR_HEADER_LOG + 12:
++	case PCI_ERR_ROOT_COMMAND:
++	case PCI_ERR_ROOT_STATUS:
++	case PCI_ERR_ROOT_ERR_SRC:
++		*value = advk_readl(pcie, PCIE_CORE_PCIERR_CAP + reg);
++		return PCI_BRIDGE_EMUL_HANDLED;
++
++	default:
++		return PCI_BRIDGE_EMUL_NOT_HANDLED;
++	}
++}
++
++static void
++advk_pci_bridge_emul_ext_conf_write(struct pci_bridge_emul *bridge,
++				    int reg, u32 old, u32 new, u32 mask)
++{
++	struct advk_pcie *pcie = bridge->data;
++
++	switch (reg) {
++	/* These are W1C registers, so clear other bits */
++	case PCI_ERR_UNCOR_STATUS:
++	case PCI_ERR_COR_STATUS:
++	case PCI_ERR_ROOT_STATUS:
++		new &= mask;
++		fallthrough;
++
++	case PCI_ERR_UNCOR_MASK:
++	case PCI_ERR_UNCOR_SEVER:
++	case PCI_ERR_COR_MASK:
++	case PCI_ERR_CAP:
++	case PCI_ERR_HEADER_LOG + 0:
++	case PCI_ERR_HEADER_LOG + 4:
++	case PCI_ERR_HEADER_LOG + 8:
++	case PCI_ERR_HEADER_LOG + 12:
++	case PCI_ERR_ROOT_COMMAND:
++	case PCI_ERR_ROOT_ERR_SRC:
++		advk_writel(pcie, new, PCIE_CORE_PCIERR_CAP + reg);
++		break;
++
++	default:
++		break;
++	}
++}
++
+ static const struct pci_bridge_emul_ops advk_pci_bridge_emul_ops = {
+ 	.read_base = advk_pci_bridge_emul_base_conf_read,
+ 	.write_base = advk_pci_bridge_emul_base_conf_write,
+ 	.read_pcie = advk_pci_bridge_emul_pcie_conf_read,
+ 	.write_pcie = advk_pci_bridge_emul_pcie_conf_write,
++	.read_ext = advk_pci_bridge_emul_ext_conf_read,
++	.write_ext = advk_pci_bridge_emul_ext_conf_write,
+ };
+ 
+ /*
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0020-PCI-aardvark-Dispose-bridge-irq-prior-to-removing-br.patch b/nixos/modules/kernel-patches/0020-PCI-aardvark-Dispose-bridge-irq-prior-to-removing-br.patch
new file mode 100644
index 0000000..3b65ce5
--- /dev/null
+++ b/nixos/modules/kernel-patches/0020-PCI-aardvark-Dispose-bridge-irq-prior-to-removing-br.patch
@@ -0,0 +1,41 @@
+From 36711e04b081f70286acb2911447550ac9f14d27 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Wed, 24 Aug 2022 15:59:49 +0200
+Subject: [PATCH 20/96] PCI: aardvark: Dispose bridge irq prior to removing
+ bridge domain
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Documentation for irq_domain_remove() says that all mapping within the
+domain must be disposed prior to domain remove.
+
+Currently bridge irq is not disposed in pci-aardvark.c device unbind callback
+which cause that kernel crashes after unloading driver and trying to read
+/sys/kernel/debug/irq/irqs/<num> or /proc/interrupts.
+
+Fixes: 815bc3136867 ("PCI: aardvark: Use separate INTA interrupt for emulated root bridge")
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/controller/pci-aardvark.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
+index 4921d9cf80d2..668b052e3e2a 100644
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -1577,6 +1577,11 @@ static int advk_pcie_init_rp_irq_domain(struct advk_pcie *pcie)
+ 
+ static void advk_pcie_remove_rp_irq_domain(struct advk_pcie *pcie)
+ {
++	int virq;
++
++	virq = irq_find_mapping(pcie->rp_irq_domain, 0);
++	if (virq > 0)
++		irq_dispose_mapping(virq);
+ 	irq_domain_remove(pcie->rp_irq_domain);
+ }
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0021-PCI-aardvark-Add-support-for-DLLSC-and-hotplug-inter.patch b/nixos/modules/kernel-patches/0021-PCI-aardvark-Add-support-for-DLLSC-and-hotplug-inter.patch
new file mode 100644
index 0000000..5debe96
--- /dev/null
+++ b/nixos/modules/kernel-patches/0021-PCI-aardvark-Add-support-for-DLLSC-and-hotplug-inter.patch
@@ -0,0 +1,268 @@
+From e9d12baaf2ca8f10303816dd3114f6fb54d9481f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Wed, 31 Aug 2022 15:55:46 +0200
+Subject: [PATCH 21/96] PCI: aardvark: Add support for DLLSC and hotplug
+ interrupt
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add support for Data Link Layer State Change in the emulated slot
+registers and hotplug interrupt via the emulated root bridge.
+
+This is mainly useful for when an error causes link down event. With
+this change, drivers can try recovery.
+
+Link down state change can be implemented because Aardvark supports Link
+Down event interrupt. Use it for signaling that Data Link Layer Link is
+not active anymore via Hot-Plug Interrupt on emulated root bridge.
+
+Link up interrupt is not available on Aardvark, but we check for whether
+link is up in the advk_pcie_link_up() function. By triggering Hot-Plug
+Interrupt from this function we achieve Link up event, so long as the
+function is called (which it is after probe and when rescanning).
+Although it is not ideal, it is better than nothing.
+
+Since advk_pcie_link_up() is not called from interrupt handler, we
+cannot call generic_handle_domain_irq() from it directly. Instead create
+a TIMER_IRQSAFE timer and trigger it from advk_pcie_link_up().
+
+(We haven't been able to find any documentation for a Link Up interrupt
+ on Aardvark, but it is possible there is one, in some undocumented
+ register. If we manage to find this information, this can be
+ rewritten.)
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/pci/controller/Kconfig        |   3 +
+ drivers/pci/controller/pci-aardvark.c | 101 ++++++++++++++++++++++++--
+ 2 files changed, 99 insertions(+), 5 deletions(-)
+
+diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
+index e4635712dbce..d4baecbcf574 100644
+--- a/drivers/pci/controller/Kconfig
++++ b/drivers/pci/controller/Kconfig
+@@ -24,6 +24,9 @@ config PCI_AARDVARK
+ 	depends on OF
+ 	depends on PCI_MSI_IRQ_DOMAIN
+ 	select PCI_BRIDGE_EMUL
++	select PCIEPORTBUS
++	select HOTPLUG_PCI
++	select HOTPLUG_PCI_PCIE
+ 	help
+ 	 Add support for Aardvark 64bit PCIe Host Controller. This
+ 	 controller is part of the South Bridge of the Marvel Armada
+diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
+index 668b052e3e2a..b2f143191313 100644
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -25,6 +25,7 @@
+ #include <linux/of_address.h>
+ #include <linux/of_gpio.h>
+ #include <linux/of_pci.h>
++#include <linux/timer.h>
+ 
+ #include "../pci.h"
+ #include "../pci-bridge-emul.h"
+@@ -101,6 +102,7 @@
+ #define PCIE_MSG_PM_PME_MASK			BIT(7)
+ #define PCIE_ISR0_MASK_REG			(CONTROL_BASE_ADDR + 0x44)
+ #define     PCIE_ISR0_MSI_INT_PENDING		BIT(24)
++#define     PCIE_ISR0_LINK_DOWN			BIT(1)
+ #define     PCIE_ISR0_CORR_ERR			BIT(11)
+ #define     PCIE_ISR0_NFAT_ERR			BIT(12)
+ #define     PCIE_ISR0_FAT_ERR			BIT(13)
+@@ -285,6 +287,8 @@ struct advk_pcie {
+ 	DECLARE_BITMAP(msi_used, MSI_IRQ_NUM);
+ 	struct mutex msi_used_lock;
+ 	int link_gen;
++	bool link_was_up;
++	struct timer_list link_irq_timer;
+ 	struct pci_bridge_emul bridge;
+ 	struct gpio_desc *reset_gpio;
+ 	struct phy *phy;
+@@ -314,7 +318,24 @@ static inline bool advk_pcie_link_up(struct advk_pcie *pcie)
+ {
+ 	/* check if LTSSM is in normal operation - some L* state */
+ 	u8 ltssm_state = advk_pcie_ltssm_state(pcie);
+-	return ltssm_state >= LTSSM_L0 && ltssm_state < LTSSM_DISABLED;
++	bool link_is_up;
++	u16 slotsta;
++
++	link_is_up = ltssm_state >= LTSSM_L0 && ltssm_state < LTSSM_DISABLED;
++
++	if (link_is_up && !pcie->link_was_up) {
++		dev_info(&pcie->pdev->dev, "link up\n");
++
++		pcie->link_was_up = true;
++
++		slotsta = le16_to_cpu(pcie->bridge.pcie_conf.slotsta);
++		slotsta |= PCI_EXP_SLTSTA_DLLSC;
++		pcie->bridge.pcie_conf.slotsta = cpu_to_le16(slotsta);
++
++		mod_timer(&pcie->link_irq_timer, jiffies + 1);
++	}
++
++	return link_is_up;
+ }
+ 
+ static inline bool advk_pcie_link_active(struct advk_pcie *pcie)
+@@ -443,8 +464,6 @@ static void advk_pcie_train_link(struct advk_pcie *pcie)
+ 	ret = advk_pcie_wait_for_link(pcie);
+ 	if (ret < 0)
+ 		dev_err(dev, "link never came up\n");
+-	else
+-		dev_info(dev, "link up\n");
+ }
+ 
+ /*
+@@ -593,6 +612,11 @@ static void advk_pcie_setup_hw(struct advk_pcie *pcie)
+ 	reg &= ~PCIE_ISR0_MSI_INT_PENDING;
+ 	advk_writel(pcie, reg, PCIE_ISR0_MASK_REG);
+ 
++	/* Unmask Link Down interrupt */
++	reg = advk_readl(pcie, PCIE_ISR0_MASK_REG);
++	reg &= ~PCIE_ISR0_LINK_DOWN;
++	advk_writel(pcie, reg, PCIE_ISR0_MASK_REG);
++
+ 	/* Unmask PME interrupt for processing of PME requester */
+ 	reg = advk_readl(pcie, PCIE_ISR0_MASK_REG);
+ 	reg &= ~PCIE_MSG_PM_PME_MASK;
+@@ -919,6 +943,14 @@ advk_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge,
+ 			advk_pcie_wait_for_retrain(pcie);
+ 		break;
+ 
++	case PCI_EXP_SLTCTL: {
++		u16 slotctl = le16_to_cpu(bridge->pcie_conf.slotctl);
++		/* Only emulation of HPIE and DLLSCE bits is provided */
++		slotctl &= PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_DLLSCE;
++		bridge->pcie_conf.slotctl = cpu_to_le16(slotctl);
++		break;
++	}
++
+ 	case PCI_EXP_RTCTL: {
+ 		u16 rootctl = le16_to_cpu(bridge->pcie_conf.rootctl);
+ 		/* Only emulation of PMEIE and CRSSVE bits is provided */
+@@ -1036,6 +1068,7 @@ static const struct pci_bridge_emul_ops advk_pci_bridge_emul_ops = {
+ static int advk_sw_pci_bridge_init(struct advk_pcie *pcie)
+ {
+ 	struct pci_bridge_emul *bridge = &pcie->bridge;
++	u32 slotcap;
+ 
+ 	bridge->conf.vendor =
+ 		cpu_to_le16(advk_readl(pcie, PCIE_CORE_DEV_ID_REG) & 0xffff);
+@@ -1062,6 +1095,13 @@ static int advk_sw_pci_bridge_init(struct advk_pcie *pcie)
+ 	bridge->pcie_conf.cap = cpu_to_le16(2 | PCI_EXP_FLAGS_SLOT);
+ 
+ 	/*
++	 * Mark bridge as Hot Plug Capable since this is the way how to enable
++	 * delivering of Data Link Layer State Change interrupts.
++	 *
++	 * Set No Command Completed Support because bridge does not support
++	 * Command Completed Interrupt. Every command is executed immediately
++	 * without any delay.
++	 *
+ 	 * Set Presence Detect State bit permanently since there is no support
+ 	 * for unplugging the card nor detecting whether it is plugged. (If a
+ 	 * platform exists in the future that supports it, via a GPIO for
+@@ -1071,8 +1111,9 @@ static int advk_sw_pci_bridge_init(struct advk_pcie *pcie)
+ 	 * value is reserved for ports within the same silicon as Root Port
+ 	 * which is not our case.
+ 	 */
+-	bridge->pcie_conf.slotcap = cpu_to_le32(FIELD_PREP(PCI_EXP_SLTCAP_PSN,
+-							   1));
++	slotcap = PCI_EXP_SLTCAP_NCCS | PCI_EXP_SLTCAP_HPC |
++		  FIELD_PREP(PCI_EXP_SLTCAP_PSN, 1);
++	bridge->pcie_conf.slotcap = cpu_to_le32(slotcap);
+ 	bridge->pcie_conf.slotsta = cpu_to_le16(PCI_EXP_SLTSTA_PDS);
+ 
+ 	/* Indicates supports for Completion Retry Status */
+@@ -1585,6 +1626,24 @@ static void advk_pcie_remove_rp_irq_domain(struct advk_pcie *pcie)
+ 	irq_domain_remove(pcie->rp_irq_domain);
+ }
+ 
++static void advk_pcie_link_irq_handler(struct timer_list *timer)
++{
++	struct advk_pcie *pcie = from_timer(pcie, timer, link_irq_timer);
++	u16 slotctl;
++
++	slotctl = le16_to_cpu(pcie->bridge.pcie_conf.slotctl);
++	if (!(slotctl & PCI_EXP_SLTCTL_DLLSCE) ||
++	    !(slotctl & PCI_EXP_SLTCTL_HPIE))
++		return;
++
++	/*
++	 * Aardvark HW returns zero for PCI_EXP_FLAGS_IRQ, so use PCIe
++	 * interrupt 0
++	 */
++	if (generic_handle_domain_irq(pcie->rp_irq_domain, 0) == -EINVAL)
++		dev_err_ratelimited(&pcie->pdev->dev, "unhandled HP IRQ\n");
++}
++
+ static void advk_pcie_handle_pme(struct advk_pcie *pcie)
+ {
+ 	u32 requester = advk_readl(pcie, PCIE_MSG_LOG_REG) >> 16;
+@@ -1636,6 +1695,7 @@ static void advk_pcie_handle_int(struct advk_pcie *pcie)
+ {
+ 	u32 isr0_val, isr0_mask, isr0_status;
+ 	u32 isr1_val, isr1_mask, isr1_status;
++	u16 slotsta;
+ 	int i;
+ 
+ 	isr0_val = advk_readl(pcie, PCIE_ISR0_REG);
+@@ -1662,6 +1722,26 @@ static void advk_pcie_handle_int(struct advk_pcie *pcie)
+ 			dev_err_ratelimited(&pcie->pdev->dev, "unhandled ERR IRQ\n");
+ 	}
+ 
++	/* Process Link Down interrupt as HP IRQ */
++	if (isr0_status & PCIE_ISR0_LINK_DOWN) {
++		advk_writel(pcie, PCIE_ISR0_LINK_DOWN, PCIE_ISR0_REG);
++
++		dev_info(&pcie->pdev->dev, "link down\n");
++
++		pcie->link_was_up = false;
++
++		slotsta = le16_to_cpu(pcie->bridge.pcie_conf.slotsta);
++		slotsta |= PCI_EXP_SLTSTA_DLLSC;
++		pcie->bridge.pcie_conf.slotsta = cpu_to_le16(slotsta);
++
++		/*
++		 * Deactivate timer and call advk_pcie_link_irq_handler()
++		 * function directly as we are in the interrupt context.
++		 */
++		del_timer_sync(&pcie->link_irq_timer);
++		advk_pcie_link_irq_handler(&pcie->link_irq_timer);
++	}
++
+ 	/* Process MSI interrupts */
+ 	if (isr0_status & PCIE_ISR0_MSI_INT_PENDING)
+ 		advk_pcie_handle_msi(pcie);
+@@ -1898,6 +1978,14 @@ static int advk_pcie_probe(struct platform_device *pdev)
+ 	if (ret)
+ 		return ret;
+ 
++	/*
++	 * generic_handle_domain_irq() expects local IRQs to be disabled since
++	 * normally it is called from interrupt context, so use TIMER_IRQSAFE
++	 * flag for this link_irq_timer.
++	 */
++	timer_setup(&pcie->link_irq_timer, advk_pcie_link_irq_handler,
++		    TIMER_IRQSAFE);
++
+ 	advk_pcie_setup_hw(pcie);
+ 
+ 	ret = advk_sw_pci_bridge_init(pcie);
+@@ -1986,6 +2074,9 @@ static int advk_pcie_remove(struct platform_device *pdev)
+ 	advk_pcie_remove_msi_irq_domain(pcie);
+ 	advk_pcie_remove_irq_domain(pcie);
+ 
++	/* Deactivate link event timer */
++	del_timer_sync(&pcie->link_irq_timer);
++
+ 	/* Free config space for emulated root bridge */
+ 	pci_bridge_emul_cleanup(&pcie->bridge);
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0022-PCI-aardvark-Send-Set_Slot_Power_Limit-message.patch b/nixos/modules/kernel-patches/0022-PCI-aardvark-Send-Set_Slot_Power_Limit-message.patch
new file mode 100644
index 0000000..2c64e5b
--- /dev/null
+++ b/nixos/modules/kernel-patches/0022-PCI-aardvark-Send-Set_Slot_Power_Limit-message.patch
@@ -0,0 +1,148 @@
+From 6f717ca06ec6ba55ec97851763dbd4e26fe7ea0f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Wed, 31 Aug 2022 15:57:01 +0200
+Subject: [PATCH 22/96] PCI: aardvark: Send Set_Slot_Power_Limit message
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Emulate Slot PowerLimit Scale and Value bits in the Slot Capabilities
+register of the emulated bridge and if slot power limit value is
+defined, send that Set_Slot_Power_Limit message via Message Generation
+Control Register in Link Up handler on link up event.
+
+Slot power limit value is read from device-tree property
+'slot-power-limit-milliwatt'. If this property is not specified, we
+treat it as "Slot Capabilities register has not yet been initialized".
+
+According to PCIe Base specification 3.0, when transitioning from a
+non-DL_Up Status to a DL_Up Status, the Port must initiate the
+transmission of a Set_Slot_Power_Limit Message to the other component
+on the Link to convey the value programmed in the Slot Power Limit
+Scale and Value fields of the Slot Capabilities register. This
+transmission is optional if the Slot Capabilities register has not
+yet been initialized.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/pci/controller/pci-aardvark.c | 51 ++++++++++++++++++++++++---
+ 1 file changed, 47 insertions(+), 4 deletions(-)
+
+diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
+index b2f143191313..656e4ea95e2e 100644
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -214,6 +214,11 @@ enum {
+ };
+ 
+ #define VENDOR_ID_REG				(LMI_BASE_ADDR + 0x44)
++#define PME_MSG_GEN_CTRL			(LMI_BASE_ADDR + 0x220)
++#define     SEND_SET_SLOT_POWER_LIMIT		BIT(13)
++#define     SEND_PME_TURN_OFF			BIT(14)
++#define     SLOT_POWER_LIMIT_DATA_SHIFT		16
++#define     SLOT_POWER_LIMIT_DATA_MASK		GENMASK(25, 16)
+ 
+ /* PCIe core controller registers */
+ #define CTRL_CORE_BASE_ADDR			0x18000
+@@ -286,6 +291,8 @@ struct advk_pcie {
+ 	raw_spinlock_t msi_irq_lock;
+ 	DECLARE_BITMAP(msi_used, MSI_IRQ_NUM);
+ 	struct mutex msi_used_lock;
++	u8 slot_power_limit_value;
++	u8 slot_power_limit_scale;
+ 	int link_gen;
+ 	bool link_was_up;
+ 	struct timer_list link_irq_timer;
+@@ -318,8 +325,9 @@ static inline bool advk_pcie_link_up(struct advk_pcie *pcie)
+ {
+ 	/* check if LTSSM is in normal operation - some L* state */
+ 	u8 ltssm_state = advk_pcie_ltssm_state(pcie);
++	u16 slotsta, slotctl;
++	u32 slotpwr, val;
+ 	bool link_is_up;
+-	u16 slotsta;
+ 
+ 	link_is_up = ltssm_state >= LTSSM_L0 && ltssm_state < LTSSM_DISABLED;
+ 
+@@ -333,6 +341,27 @@ static inline bool advk_pcie_link_up(struct advk_pcie *pcie)
+ 		pcie->bridge.pcie_conf.slotsta = cpu_to_le16(slotsta);
+ 
+ 		mod_timer(&pcie->link_irq_timer, jiffies + 1);
++
++		/*
++		 * According to PCIe Base specification 3.0, when transitioning
++		 * from a non-DL_Up Status to a DL_Up Status, the Port must
++		 * initiate the transmission of a Set_Slot_Power_Limit Message
++		 * to the other component on the Link to convey the value
++		 * programmed in the Slot Power Limit Scale and Value fields of
++		 * the Slot Capabilities register. This transmission is optional
++		 * if the Slot Capabilities register has not yet been
++		 * initialized.
++		 */
++		slotctl = le16_to_cpu(pcie->bridge.pcie_conf.slotctl);
++		slotpwr = FIELD_GET(PCI_EXP_SLTCAP_SPLV | PCI_EXP_SLTCAP_SPLS,
++				    le32_to_cpu(pcie->bridge.pcie_conf.slotcap));
++		if (!(slotctl & PCI_EXP_SLTCTL_ASPL_DISABLE) && slotpwr) {
++			val = advk_readl(pcie, PME_MSG_GEN_CTRL);
++			val &= ~SLOT_POWER_LIMIT_DATA_MASK;
++			val |= slotpwr << SLOT_POWER_LIMIT_DATA_SHIFT;
++			val |= SEND_SET_SLOT_POWER_LIMIT;
++			advk_writel(pcie, val, PME_MSG_GEN_CTRL);
++		}
+ 	}
+ 
+ 	return link_is_up;
+@@ -945,8 +974,9 @@ advk_pci_bridge_emul_pcie_conf_write(struct pci_bridge_emul *bridge,
+ 
+ 	case PCI_EXP_SLTCTL: {
+ 		u16 slotctl = le16_to_cpu(bridge->pcie_conf.slotctl);
+-		/* Only emulation of HPIE and DLLSCE bits is provided */
+-		slotctl &= PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_DLLSCE;
++		/* Only emulation of HPIE, DLLSCE and ASPLD bits is provided */
++		slotctl &= PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_DLLSCE |
++			   PCI_EXP_SLTCTL_ASPL_DISABLE;
+ 		bridge->pcie_conf.slotctl = cpu_to_le16(slotctl);
+ 		break;
+ 	}
+@@ -1110,9 +1140,13 @@ static int advk_sw_pci_bridge_init(struct advk_pcie *pcie)
+ 	 * Set physical slot number to 1 since there is only one port and zero
+ 	 * value is reserved for ports within the same silicon as Root Port
+ 	 * which is not our case.
++	 *
++	 * Set slot power limit.
+ 	 */
+ 	slotcap = PCI_EXP_SLTCAP_NCCS | PCI_EXP_SLTCAP_HPC |
+-		  FIELD_PREP(PCI_EXP_SLTCAP_PSN, 1);
++		  FIELD_PREP(PCI_EXP_SLTCAP_PSN, 1) |
++		  FIELD_PREP(PCI_EXP_SLTCAP_SPLV, pcie->slot_power_limit_value) |
++		  FIELD_PREP(PCI_EXP_SLTCAP_SPLS, pcie->slot_power_limit_scale);
+ 	bridge->pcie_conf.slotcap = cpu_to_le32(slotcap);
+ 	bridge->pcie_conf.slotsta = cpu_to_le16(PCI_EXP_SLTSTA_PDS);
+ 
+@@ -1854,6 +1888,7 @@ static int advk_pcie_probe(struct platform_device *pdev)
+ 	struct advk_pcie *pcie;
+ 	struct pci_host_bridge *bridge;
+ 	struct resource_entry *entry;
++	u32 slot_power_limit;
+ 	int ret, irq;
+ 
+ 	bridge = devm_pci_alloc_host_bridge(dev, sizeof(struct advk_pcie));
+@@ -1974,6 +2009,14 @@ static int advk_pcie_probe(struct platform_device *pdev)
+ 	else
+ 		pcie->link_gen = ret;
+ 
++	slot_power_limit = of_pci_get_slot_power_limit(dev->of_node,
++						       &pcie->slot_power_limit_value,
++						       &pcie->slot_power_limit_scale);
++	if (slot_power_limit)
++		dev_info(dev, "Slot Power Limit: %u.%uW\n",
++			 slot_power_limit / 1000,
++			 (slot_power_limit / 100) % 10);
++
+ 	ret = advk_pcie_setup_phy(pcie);
+ 	if (ret)
+ 		return ret;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0023-PCI-aardvark-Add-clock-support.patch b/nixos/modules/kernel-patches/0023-PCI-aardvark-Add-clock-support.patch
new file mode 100644
index 0000000..779d803
--- /dev/null
+++ b/nixos/modules/kernel-patches/0023-PCI-aardvark-Add-clock-support.patch
@@ -0,0 +1,93 @@
+From 0d829d8007898c85cecdb750ca6892cadbab21e5 Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Wed, 31 Aug 2022 15:59:39 +0200
+Subject: [PATCH 23/96] PCI: aardvark: Add clock support
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The IP relies on a gated clock. When we will add S2RAM support, this
+clock will need to be resumed before any PCIe registers are
+accessed. Add support for this clock.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Pali Rohár <pali@kernel.org>
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/pci/controller/pci-aardvark.c | 32 +++++++++++++++++++++++++++
+ 1 file changed, 32 insertions(+)
+
+diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
+index 656e4ea95e2e..84c1f3165472 100644
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -9,6 +9,7 @@
+  */
+ 
+ #include <linux/bitfield.h>
++#include <linux/clk.h>
+ #include <linux/delay.h>
+ #include <linux/gpio/consumer.h>
+ #include <linux/interrupt.h>
+@@ -298,6 +299,7 @@ struct advk_pcie {
+ 	struct timer_list link_irq_timer;
+ 	struct pci_bridge_emul bridge;
+ 	struct gpio_desc *reset_gpio;
++	struct clk *clk;
+ 	struct phy *phy;
+ };
+ 
+@@ -1826,6 +1828,29 @@ static int advk_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
+ 		return of_irq_parse_and_map_pci(dev, slot, pin);
+ }
+ 
++static int advk_pcie_setup_clk(struct advk_pcie *pcie)
++{
++	struct device *dev = &pcie->pdev->dev;
++	int ret;
++
++	pcie->clk = devm_clk_get(dev, NULL);
++	if (IS_ERR(pcie->clk) && (PTR_ERR(pcie->clk) == -EPROBE_DEFER))
++		return PTR_ERR(pcie->clk);
++
++	/* Old bindings miss the clock handle */
++	if (IS_ERR(pcie->clk)) {
++		dev_warn(dev, "Clock unavailable (%ld)\n", PTR_ERR(pcie->clk));
++		pcie->clk = NULL;
++		return 0;
++	}
++
++	ret = clk_prepare_enable(pcie->clk);
++	if (ret)
++		dev_err(dev, "Clock initialization failed (%d)\n", ret);
++
++	return ret;
++}
++
+ static void advk_pcie_disable_phy(struct advk_pcie *pcie)
+ {
+ 	phy_power_off(pcie->phy);
+@@ -2017,6 +2042,10 @@ static int advk_pcie_probe(struct platform_device *pdev)
+ 			 slot_power_limit / 1000,
+ 			 (slot_power_limit / 100) % 10);
+ 
++	ret = advk_pcie_setup_clk(pcie);
++	if (ret)
++		return ret;
++
+ 	ret = advk_pcie_setup_phy(pcie);
+ 	if (ret)
+ 		return ret;
+@@ -2139,6 +2168,9 @@ static int advk_pcie_remove(struct platform_device *pdev)
+ 	/* Disable phy */
+ 	advk_pcie_disable_phy(pcie);
+ 
++	/* Disable clock */
++	clk_disable_unprepare(pcie->clk);
++
+ 	return 0;
+ }
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0024-PCI-aardvark-Add-suspend-to-RAM-support.patch b/nixos/modules/kernel-patches/0024-PCI-aardvark-Add-suspend-to-RAM-support.patch
new file mode 100644
index 0000000..d03de3c
--- /dev/null
+++ b/nixos/modules/kernel-patches/0024-PCI-aardvark-Add-suspend-to-RAM-support.patch
@@ -0,0 +1,74 @@
+From ff0d91bd6cfdd3c27d75e631a6aaeb5b4eee4f1f Mon Sep 17 00:00:00 2001
+From: Miquel Raynal <miquel.raynal@bootlin.com>
+Date: Wed, 31 Aug 2022 16:07:27 +0200
+Subject: [PATCH 24/96] PCI: aardvark: Add suspend to RAM support
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add suspend and resume callbacks. We need to use the NOIRQ variants to
+ensure the controller's IRQ handlers are not run during suspend() /
+resume(), which could cause races.
+
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Pali Rohár <pali@kernel.org>
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/pci/controller/pci-aardvark.c | 34 +++++++++++++++++++++++++++
+ 1 file changed, 34 insertions(+)
+
+diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
+index 84c1f3165472..a042bfe70d69 100644
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -1907,6 +1907,39 @@ static int advk_pcie_setup_phy(struct advk_pcie *pcie)
+ 	return ret;
+ }
+ 
++static int advk_pcie_suspend(struct device *dev)
++{
++	struct advk_pcie *pcie = dev_get_drvdata(dev);
++
++	advk_pcie_disable_phy(pcie);
++
++	clk_disable_unprepare(pcie->clk);
++
++	return 0;
++}
++
++static int advk_pcie_resume(struct device *dev)
++{
++	struct advk_pcie *pcie = dev_get_drvdata(dev);
++	int ret;
++
++	ret = clk_prepare_enable(pcie->clk);
++	if (ret)
++		return ret;
++
++	ret = advk_pcie_enable_phy(pcie);
++	if (ret)
++		return ret;
++
++	advk_pcie_setup_hw(pcie);
++
++	return 0;
++}
++
++static const struct dev_pm_ops advk_pcie_dev_pm_ops = {
++	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(advk_pcie_suspend, advk_pcie_resume)
++};
++
+ static int advk_pcie_probe(struct platform_device *pdev)
+ {
+ 	struct device *dev = &pdev->dev;
+@@ -2184,6 +2217,7 @@ static struct platform_driver advk_pcie_driver = {
+ 	.driver = {
+ 		.name = "advk-pcie",
+ 		.of_match_table = advk_pcie_of_match_table,
++		.pm = &advk_pcie_dev_pm_ops,
+ 	},
+ 	.probe = advk_pcie_probe,
+ 	.remove = advk_pcie_remove,
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0025-PCI-aardvark-Replace-custom-PCIE_CORE_ERR_CAPCTL_-ma.patch b/nixos/modules/kernel-patches/0025-PCI-aardvark-Replace-custom-PCIE_CORE_ERR_CAPCTL_-ma.patch
new file mode 100644
index 0000000..8fee40f
--- /dev/null
+++ b/nixos/modules/kernel-patches/0025-PCI-aardvark-Replace-custom-PCIE_CORE_ERR_CAPCTL_-ma.patch
@@ -0,0 +1,51 @@
+From a293a3635908a9af07f9ec5e3e01ceb936058710 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Thu, 1 Sep 2022 11:32:28 +0200
+Subject: [PATCH 25/96] PCI: aardvark: Replace custom PCIE_CORE_ERR_CAPCTL_*
+ macros by linux/pci_regs.h macros
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Kernel already has these macros defined under different names.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/pci/controller/pci-aardvark.c | 13 +++----------
+ 1 file changed, 3 insertions(+), 10 deletions(-)
+
+diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
+index a042bfe70d69..635f236232bb 100644
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -38,11 +38,6 @@
+ #define PCIE_CORE_SSDEV_ID_REG					0x2c
+ #define PCIE_CORE_PCIEXP_CAP					0xc0
+ #define PCIE_CORE_PCIERR_CAP					0x100
+-#define PCIE_CORE_ERR_CAPCTL_REG				0x118
+-#define     PCIE_CORE_ERR_CAPCTL_ECRC_CHK_TX			BIT(5)
+-#define     PCIE_CORE_ERR_CAPCTL_ECRC_CHK_TX_EN			BIT(6)
+-#define     PCIE_CORE_ERR_CAPCTL_ECRC_CHCK			BIT(7)
+-#define     PCIE_CORE_ERR_CAPCTL_ECRC_CHCK_RCV			BIT(8)
+ /* PIO registers base address and register offsets */
+ #define PIO_BASE_ADDR				0x4000
+ #define PIO_CTRL				(PIO_BASE_ADDR + 0x0)
+@@ -590,11 +585,9 @@ static void advk_pcie_setup_hw(struct advk_pcie *pcie)
+ 	advk_writel(pcie, reg, PCIE_CORE_CMD_STATUS_REG);
+ 
+ 	/* Set Advanced Error Capabilities and Control PF0 register */
+-	reg = PCIE_CORE_ERR_CAPCTL_ECRC_CHK_TX |
+-		PCIE_CORE_ERR_CAPCTL_ECRC_CHK_TX_EN |
+-		PCIE_CORE_ERR_CAPCTL_ECRC_CHCK |
+-		PCIE_CORE_ERR_CAPCTL_ECRC_CHCK_RCV;
+-	advk_writel(pcie, reg, PCIE_CORE_ERR_CAPCTL_REG);
++	reg = PCI_ERR_CAP_ECRC_GENC | PCI_ERR_CAP_ECRC_GENE |
++	      PCI_ERR_CAP_ECRC_CHKC | PCI_ERR_CAP_ECRC_CHKE;
++	advk_writel(pcie, reg, PCIE_CORE_PCIERR_CAP + PCI_ERR_CAP);
+ 
+ 	/* Set PCIe Device Control register */
+ 	reg = advk_readl(pcie, PCIE_CORE_PCIEXP_CAP + PCI_EXP_DEVCTL);
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0026-PCI-aardvark-Don-t-write-read-only-bits-explicitly-i.patch b/nixos/modules/kernel-patches/0026-PCI-aardvark-Don-t-write-read-only-bits-explicitly-i.patch
new file mode 100644
index 0000000..0a248e4
--- /dev/null
+++ b/nixos/modules/kernel-patches/0026-PCI-aardvark-Don-t-write-read-only-bits-explicitly-i.patch
@@ -0,0 +1,39 @@
+From 3d3bb1ce67148f05ae3f42d9642bae8011919800 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Thu, 18 Aug 2022 15:51:38 +0200
+Subject: [PATCH 26/96] PCI: aardvark: Don't write read-only bits explicitly in
+ PCI_ERR_CAP register
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The bits PCI_ERR_CAP_ECRC_GENC and PCI_ERR_CAP_ECRC_CHKC are read only,
+reporting the capability of ECRC. Don't write them explicitly, instead
+read the register (where they are set), and add the bits that enable
+these features.
+
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/pci/controller/pci-aardvark.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
+index 635f236232bb..93e2e0234df6 100644
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -584,9 +584,9 @@ static void advk_pcie_setup_hw(struct advk_pcie *pcie)
+ 	reg &= ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
+ 	advk_writel(pcie, reg, PCIE_CORE_CMD_STATUS_REG);
+ 
+-	/* Set Advanced Error Capabilities and Control PF0 register */
+-	reg = PCI_ERR_CAP_ECRC_GENC | PCI_ERR_CAP_ECRC_GENE |
+-	      PCI_ERR_CAP_ECRC_CHKC | PCI_ERR_CAP_ECRC_CHKE;
++	/* Enable generation and checking of ECRC on Root Bridge */
++	reg = advk_readl(pcie, PCIE_CORE_PCIERR_CAP + PCI_ERR_CAP);
++	reg |= PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE;
+ 	advk_writel(pcie, reg, PCIE_CORE_PCIERR_CAP + PCI_ERR_CAP);
+ 
+ 	/* Set PCIe Device Control register */
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0027-compiler.h-only-include-asm-rwonce.h-for-kernel-code.patch b/nixos/modules/kernel-patches/0027-compiler.h-only-include-asm-rwonce.h-for-kernel-code.patch
new file mode 100644
index 0000000..75579b1
--- /dev/null
+++ b/nixos/modules/kernel-patches/0027-compiler.h-only-include-asm-rwonce.h-for-kernel-code.patch
@@ -0,0 +1,37 @@
+From fffecea36f26cf21d4a7cf38d0697fe841d6a84f Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 22 Oct 2020 22:00:03 +0200
+Subject: [PATCH 27/96] compiler.h: only include asm/rwonce.h for kernel code
+
+This header file is not in uapi, which makes any user space code that includes
+linux/compiler.h to fail with the error 'asm/rwonce.h: No such file or directory'
+
+Fixes: e506ea451254 ("compiler.h: Split {READ,WRITE}_ONCE definitions out into rwonce.h")
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ include/linux/compiler.h | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/include/linux/compiler.h b/include/linux/compiler.h
+index 01ce94b58b42..a93693c632af 100644
+--- a/include/linux/compiler.h
++++ b/include/linux/compiler.h
+@@ -213,6 +213,8 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
+ #define function_nocfi(x) (x)
+ #endif
+ 
++#include <asm/rwonce.h>
++
+ #endif /* __KERNEL__ */
+ 
+ /*
+@@ -245,6 +247,4 @@ static inline void *offset_to_ptr(const int *off)
+  */
+ #define prevent_tail_call_optimization()	mb()
+ 
+-#include <asm/rwonce.h>
+-
+ #endif /* __LINUX_COMPILER_H */
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0028-swab-use-stddefs.h-instead-of-compiler.h.patch b/nixos/modules/kernel-patches/0028-swab-use-stddefs.h-instead-of-compiler.h.patch
new file mode 100644
index 0000000..2b49bb8
--- /dev/null
+++ b/nixos/modules/kernel-patches/0028-swab-use-stddefs.h-instead-of-compiler.h.patch
@@ -0,0 +1,26 @@
+From 36da7f38eabfcc0802786eb691cf8231cd4e496a Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 11:36:00 +0200
+Subject: [PATCH 28/96] swab: use stddefs.h instead of compiler.h
+
+Fix an issue with kernel headers that broke perf.
+---
+ include/uapi/linux/swab.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/include/uapi/linux/swab.h b/include/uapi/linux/swab.h
+index 7272f85d6d6a..3736f2fe1541 100644
+--- a/include/uapi/linux/swab.h
++++ b/include/uapi/linux/swab.h
+@@ -3,7 +3,7 @@
+ #define _UAPI_LINUX_SWAB_H
+ 
+ #include <linux/types.h>
+-#include <linux/compiler.h>
++#include <linux/stddef.h>
+ #include <asm/bitsperlong.h>
+ #include <asm/swab.h>
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0029-net-sfp-move-quirk-handling-into-sfp.c.patch b/nixos/modules/kernel-patches/0029-net-sfp-move-quirk-handling-into-sfp.c.patch
new file mode 100644
index 0000000..2c3d27e
--- /dev/null
+++ b/nixos/modules/kernel-patches/0029-net-sfp-move-quirk-handling-into-sfp.c.patch
@@ -0,0 +1,299 @@
+From 36f747eca3edd3260b4f2fd2cd899af3cc2d9892 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 26 Aug 2022 08:43:30 +0100
+Subject: [PATCH 29/96] net: sfp: move quirk handling into sfp.c
+
+We need to handle more quirks than just those which affect the link
+modes of the module. Move the quirk lookup into sfp.c, and pass the
+quirk to sfp-bus.c
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+---
+ drivers/net/phy/sfp-bus.c | 98 ++-------------------------------------
+ drivers/net/phy/sfp.c     | 94 ++++++++++++++++++++++++++++++++++++-
+ drivers/net/phy/sfp.h     |  9 +++-
+ 3 files changed, 104 insertions(+), 97 deletions(-)
+
+diff --git a/drivers/net/phy/sfp-bus.c b/drivers/net/phy/sfp-bus.c
+index 15aa5ac1ff49..82216c7bb470 100644
+--- a/drivers/net/phy/sfp-bus.c
++++ b/drivers/net/phy/sfp-bus.c
+@@ -10,12 +10,6 @@
+ 
+ #include "sfp.h"
+ 
+-struct sfp_quirk {
+-	const char *vendor;
+-	const char *part;
+-	void (*modes)(const struct sfp_eeprom_id *id, unsigned long *modes);
+-};
+-
+ /**
+  * struct sfp_bus - internal representation of a sfp bus
+  */
+@@ -38,93 +32,6 @@ struct sfp_bus {
+ 	bool started;
+ };
+ 
+-static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
+-				unsigned long *modes)
+-{
+-	phylink_set(modes, 2500baseX_Full);
+-}
+-
+-static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id,
+-				      unsigned long *modes)
+-{
+-	/* Ubiquiti U-Fiber Instant module claims that support all transceiver
+-	 * types including 10G Ethernet which is not truth. So clear all claimed
+-	 * modes and set only one mode which module supports: 1000baseX_Full.
+-	 */
+-	phylink_zero(modes);
+-	phylink_set(modes, 1000baseX_Full);
+-}
+-
+-static const struct sfp_quirk sfp_quirks[] = {
+-	{
+-		// Alcatel Lucent G-010S-P can operate at 2500base-X, but
+-		// incorrectly report 2500MBd NRZ in their EEPROM
+-		.vendor = "ALCATELLUCENT",
+-		.part = "G010SP",
+-		.modes = sfp_quirk_2500basex,
+-	}, {
+-		// Alcatel Lucent G-010S-A can operate at 2500base-X, but
+-		// report 3.2GBd NRZ in their EEPROM
+-		.vendor = "ALCATELLUCENT",
+-		.part = "3FE46541AA",
+-		.modes = sfp_quirk_2500basex,
+-	}, {
+-		// Huawei MA5671A can operate at 2500base-X, but report 1.2GBd
+-		// NRZ in their EEPROM
+-		.vendor = "HUAWEI",
+-		.part = "MA5671A",
+-		.modes = sfp_quirk_2500basex,
+-	}, {
+-		// Lantech 8330-262D-E can operate at 2500base-X, but
+-		// incorrectly report 2500MBd NRZ in their EEPROM
+-		.vendor = "Lantech",
+-		.part = "8330-262D-E",
+-		.modes = sfp_quirk_2500basex,
+-	}, {
+-		.vendor = "UBNT",
+-		.part = "UF-INSTANT",
+-		.modes = sfp_quirk_ubnt_uf_instant,
+-	},
+-};
+-
+-static size_t sfp_strlen(const char *str, size_t maxlen)
+-{
+-	size_t size, i;
+-
+-	/* Trailing characters should be filled with space chars */
+-	for (i = 0, size = 0; i < maxlen; i++)
+-		if (str[i] != ' ')
+-			size = i + 1;
+-
+-	return size;
+-}
+-
+-static bool sfp_match(const char *qs, const char *str, size_t len)
+-{
+-	if (!qs)
+-		return true;
+-	if (strlen(qs) != len)
+-		return false;
+-	return !strncmp(qs, str, len);
+-}
+-
+-static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id)
+-{
+-	const struct sfp_quirk *q;
+-	unsigned int i;
+-	size_t vs, ps;
+-
+-	vs = sfp_strlen(id->base.vendor_name, ARRAY_SIZE(id->base.vendor_name));
+-	ps = sfp_strlen(id->base.vendor_pn, ARRAY_SIZE(id->base.vendor_pn));
+-
+-	for (i = 0, q = sfp_quirks; i < ARRAY_SIZE(sfp_quirks); i++, q++)
+-		if (sfp_match(q->vendor, id->base.vendor_name, vs) &&
+-		    sfp_match(q->part, id->base.vendor_pn, ps))
+-			return q;
+-
+-	return NULL;
+-}
+-
+ /**
+  * sfp_parse_port() - Parse the EEPROM base ID, setting the port type
+  * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+@@ -786,12 +693,13 @@ void sfp_link_down(struct sfp_bus *bus)
+ }
+ EXPORT_SYMBOL_GPL(sfp_link_down);
+ 
+-int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id)
++int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
++		      const struct sfp_quirk *quirk)
+ {
+ 	const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+ 	int ret = 0;
+ 
+-	bus->sfp_quirk = sfp_lookup_quirk(id);
++	bus->sfp_quirk = quirk;
+ 
+ 	if (ops && ops->module_insert)
+ 		ret = ops->module_insert(bus->upstream, id);
+diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
+index e7b0e12cc75b..8515685d4581 100644
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -252,6 +252,8 @@ struct sfp {
+ 	unsigned int module_t_start_up;
+ 	bool tx_fault_ignore;
+ 
++	const struct sfp_quirk *quirk;
++
+ #if IS_ENABLED(CONFIG_HWMON)
+ 	struct sfp_diag diag;
+ 	struct delayed_work hwmon_probe;
+@@ -308,6 +310,93 @@ static const struct of_device_id sfp_of_match[] = {
+ };
+ MODULE_DEVICE_TABLE(of, sfp_of_match);
+ 
++static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
++				unsigned long *modes)
++{
++	linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT, modes);
++}
++
++static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id,
++				      unsigned long *modes)
++{
++	/* Ubiquiti U-Fiber Instant module claims that support all transceiver
++	 * types including 10G Ethernet which is not truth. So clear all claimed
++	 * modes and set only one mode which module supports: 1000baseX_Full.
++	 */
++	linkmode_zero(modes);
++	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, modes);
++}
++
++static const struct sfp_quirk sfp_quirks[] = {
++	{
++		// Alcatel Lucent G-010S-P can operate at 2500base-X, but
++		// incorrectly report 2500MBd NRZ in their EEPROM
++		.vendor = "ALCATELLUCENT",
++		.part = "G010SP",
++		.modes = sfp_quirk_2500basex,
++	}, {
++		// Alcatel Lucent G-010S-A can operate at 2500base-X, but
++		// report 3.2GBd NRZ in their EEPROM
++		.vendor = "ALCATELLUCENT",
++		.part = "3FE46541AA",
++		.modes = sfp_quirk_2500basex,
++	}, {
++		// Huawei MA5671A can operate at 2500base-X, but report 1.2GBd
++		// NRZ in their EEPROM
++		.vendor = "HUAWEI",
++		.part = "MA5671A",
++		.modes = sfp_quirk_2500basex,
++	}, {
++		// Lantech 8330-262D-E can operate at 2500base-X, but
++		// incorrectly report 2500MBd NRZ in their EEPROM
++		.vendor = "Lantech",
++		.part = "8330-262D-E",
++		.modes = sfp_quirk_2500basex,
++	}, {
++		.vendor = "UBNT",
++		.part = "UF-INSTANT",
++		.modes = sfp_quirk_ubnt_uf_instant,
++	},
++};
++
++static size_t sfp_strlen(const char *str, size_t maxlen)
++{
++	size_t size, i;
++
++	/* Trailing characters should be filled with space chars */
++	for (i = 0, size = 0; i < maxlen; i++)
++		if (str[i] != ' ')
++			size = i + 1;
++
++	return size;
++}
++
++static bool sfp_match(const char *qs, const char *str, size_t len)
++{
++	if (!qs)
++		return true;
++	if (strlen(qs) != len)
++		return false;
++	return !strncmp(qs, str, len);
++}
++
++static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id)
++{
++	const struct sfp_quirk *q;
++	unsigned int i;
++	size_t vs, ps;
++
++	vs = sfp_strlen(id->base.vendor_name, ARRAY_SIZE(id->base.vendor_name));
++	ps = sfp_strlen(id->base.vendor_pn, ARRAY_SIZE(id->base.vendor_pn));
++
++	for (i = 0, q = sfp_quirks; i < ARRAY_SIZE(sfp_quirks); i++, q++)
++		if (sfp_match(q->vendor, id->base.vendor_name, vs) &&
++		    sfp_match(q->part, id->base.vendor_pn, ps))
++			return q;
++
++	return NULL;
++}
++
+ static unsigned long poll_jiffies;
+ 
+ static unsigned int sfp_gpio_get_state(struct sfp *sfp)
+@@ -1963,6 +2052,8 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
+ 	else
+ 		sfp->tx_fault_ignore = false;
+ 
++	sfp->quirk = sfp_lookup_quirk(&id);
++
+ 	return 0;
+ }
+ 
+@@ -2075,7 +2166,8 @@ static void sfp_sm_module(struct sfp *sfp, unsigned int event)
+ 			break;
+ 
+ 		/* Report the module insertion to the upstream device */
+-		err = sfp_module_insert(sfp->sfp_bus, &sfp->id);
++		err = sfp_module_insert(sfp->sfp_bus, &sfp->id,
++					sfp->quirk);
+ 		if (err < 0) {
+ 			sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0);
+ 			break;
+diff --git a/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h
+index 27226535c72b..03f1d47fe6ca 100644
+--- a/drivers/net/phy/sfp.h
++++ b/drivers/net/phy/sfp.h
+@@ -6,6 +6,12 @@
+ 
+ struct sfp;
+ 
++struct sfp_quirk {
++	const char *vendor;
++	const char *part;
++	void (*modes)(const struct sfp_eeprom_id *id, unsigned long *modes);
++};
++
+ struct sfp_socket_ops {
+ 	void (*attach)(struct sfp *sfp);
+ 	void (*detach)(struct sfp *sfp);
+@@ -23,7 +29,8 @@ int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev);
+ void sfp_remove_phy(struct sfp_bus *bus);
+ void sfp_link_up(struct sfp_bus *bus);
+ void sfp_link_down(struct sfp_bus *bus);
+-int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id);
++int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
++		      const struct sfp_quirk *quirk);
+ void sfp_module_remove(struct sfp_bus *bus);
+ int sfp_module_start(struct sfp_bus *bus);
+ void sfp_module_stop(struct sfp_bus *bus);
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0030-net-sfp-move-Alcatel-Lucent-3FE46541AA-fixup.patch b/nixos/modules/kernel-patches/0030-net-sfp-move-Alcatel-Lucent-3FE46541AA-fixup.patch
new file mode 100644
index 0000000..846e443
--- /dev/null
+++ b/nixos/modules/kernel-patches/0030-net-sfp-move-Alcatel-Lucent-3FE46541AA-fixup.patch
@@ -0,0 +1,75 @@
+From dc751e277ce69c792de8627a24833c9668828e21 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 26 Aug 2022 08:43:35 +0100
+Subject: [PATCH 30/96] net: sfp: move Alcatel Lucent 3FE46541AA fixup
+
+Add a new fixup mechanism to the SFP quirks, and use it for this
+module.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+---
+ drivers/net/phy/sfp.c | 14 +++++++++-----
+ drivers/net/phy/sfp.h |  1 +
+ 2 files changed, 10 insertions(+), 5 deletions(-)
+
+diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
+index 8515685d4581..37b29e1463bf 100644
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -310,6 +310,11 @@ static const struct of_device_id sfp_of_match[] = {
+ };
+ MODULE_DEVICE_TABLE(of, sfp_of_match);
+ 
++static void sfp_fixup_long_startup(struct sfp *sfp)
++{
++	sfp->module_t_start_up = T_START_UP_BAD_GPON;
++}
++
+ static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
+ 				unsigned long *modes)
+ {
+@@ -340,6 +345,7 @@ static const struct sfp_quirk sfp_quirks[] = {
+ 		.vendor = "ALCATELLUCENT",
+ 		.part = "3FE46541AA",
+ 		.modes = sfp_quirk_2500basex,
++		.fixup = sfp_fixup_long_startup,
+ 	}, {
+ 		// Huawei MA5671A can operate at 2500base-X, but report 1.2GBd
+ 		// NRZ in their EEPROM
+@@ -2040,11 +2046,7 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
+ 	if (ret < 0)
+ 		return ret;
+ 
+-	if (!memcmp(id.base.vendor_name, "ALCATELLUCENT   ", 16) &&
+-	    !memcmp(id.base.vendor_pn, "3FE46541AA      ", 16))
+-		sfp->module_t_start_up = T_START_UP_BAD_GPON;
+-	else
+-		sfp->module_t_start_up = T_START_UP;
++	sfp->module_t_start_up = T_START_UP;
+ 
+ 	if (!memcmp(id.base.vendor_name, "HUAWEI          ", 16) &&
+ 	    !memcmp(id.base.vendor_pn, "MA5671A         ", 16))
+@@ -2053,6 +2055,8 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
+ 		sfp->tx_fault_ignore = false;
+ 
+ 	sfp->quirk = sfp_lookup_quirk(&id);
++	if (sfp->quirk && sfp->quirk->fixup)
++		sfp->quirk->fixup(sfp);
+ 
+ 	return 0;
+ }
+diff --git a/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h
+index 03f1d47fe6ca..7ad06deae76c 100644
+--- a/drivers/net/phy/sfp.h
++++ b/drivers/net/phy/sfp.h
+@@ -10,6 +10,7 @@ struct sfp_quirk {
+ 	const char *vendor;
+ 	const char *part;
+ 	void (*modes)(const struct sfp_eeprom_id *id, unsigned long *modes);
++	void (*fixup)(struct sfp *sfp);
+ };
+ 
+ struct sfp_socket_ops {
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0031-net-sfp-move-Huawei-MA5671A-fixup.patch b/nixos/modules/kernel-patches/0031-net-sfp-move-Huawei-MA5671A-fixup.patch
new file mode 100644
index 0000000..3d54ea0
--- /dev/null
+++ b/nixos/modules/kernel-patches/0031-net-sfp-move-Huawei-MA5671A-fixup.patch
@@ -0,0 +1,52 @@
+From cdd0eac0bbbb07f5df4813d47ba7871d8836d750 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 26 Aug 2022 08:43:40 +0100
+Subject: [PATCH 31/96] net: sfp: move Huawei MA5671A fixup
+
+Move this module over to the new fixup mechanism.
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+---
+ drivers/net/phy/sfp.c | 12 +++++++-----
+ 1 file changed, 7 insertions(+), 5 deletions(-)
+
+diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
+index 37b29e1463bf..404ea1ba457a 100644
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -315,6 +315,11 @@ static void sfp_fixup_long_startup(struct sfp *sfp)
+ 	sfp->module_t_start_up = T_START_UP_BAD_GPON;
+ }
+ 
++static void sfp_fixup_ignore_tx_fault(struct sfp *sfp)
++{
++	sfp->tx_fault_ignore = true;
++}
++
+ static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
+ 				unsigned long *modes)
+ {
+@@ -352,6 +357,7 @@ static const struct sfp_quirk sfp_quirks[] = {
+ 		.vendor = "HUAWEI",
+ 		.part = "MA5671A",
+ 		.modes = sfp_quirk_2500basex,
++		.fixup = sfp_fixup_ignore_tx_fault,
+ 	}, {
+ 		// Lantech 8330-262D-E can operate at 2500base-X, but
+ 		// incorrectly report 2500MBd NRZ in their EEPROM
+@@ -2048,11 +2054,7 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
+ 
+ 	sfp->module_t_start_up = T_START_UP;
+ 
+-	if (!memcmp(id.base.vendor_name, "HUAWEI          ", 16) &&
+-	    !memcmp(id.base.vendor_pn, "MA5671A         ", 16))
+-		sfp->tx_fault_ignore = true;
+-	else
+-		sfp->tx_fault_ignore = false;
++	sfp->tx_fault_ignore = false;
+ 
+ 	sfp->quirk = sfp_lookup_quirk(&id);
+ 	if (sfp->quirk && sfp->quirk->fixup)
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0032-net-sfp-redo-soft-state-polling.patch b/nixos/modules/kernel-patches/0032-net-sfp-redo-soft-state-polling.patch
new file mode 100644
index 0000000..0c7a4b1
--- /dev/null
+++ b/nixos/modules/kernel-patches/0032-net-sfp-redo-soft-state-polling.patch
@@ -0,0 +1,84 @@
+From 5bec210c1c4677ecef5962138c75cb514a079661 Mon Sep 17 00:00:00 2001
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
+Date: Fri, 26 Aug 2022 08:48:20 +0100
+Subject: [PATCH 32/96] net: sfp: redo soft state polling
+
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
+---
+ drivers/net/phy/sfp.c | 35 ++++++++++++++++++++++++-----------
+ 1 file changed, 24 insertions(+), 11 deletions(-)
+
+diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
+index 404ea1ba457a..b213e9c2e456 100644
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -234,6 +234,7 @@ struct sfp {
+ 	bool need_poll;
+ 
+ 	struct mutex st_mutex;			/* Protects state */
++	unsigned int state_ignore_hw_mask;
+ 	unsigned int state_soft_mask;
+ 	unsigned int state;
+ 	struct delayed_work poll;
+@@ -600,17 +601,18 @@ static void sfp_soft_set_state(struct sfp *sfp, unsigned int state)
+ static void sfp_soft_start_poll(struct sfp *sfp)
+ {
+ 	const struct sfp_eeprom_id *id = &sfp->id;
++	unsigned int mask = 0;
+ 
+ 	sfp->state_soft_mask = 0;
+-	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_DISABLE &&
+-	    !sfp->gpio[GPIO_TX_DISABLE])
+-		sfp->state_soft_mask |= SFP_F_TX_DISABLE;
+-	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_FAULT &&
+-	    !sfp->gpio[GPIO_TX_FAULT])
+-		sfp->state_soft_mask |= SFP_F_TX_FAULT;
+-	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RX_LOS &&
+-	    !sfp->gpio[GPIO_LOS])
+-		sfp->state_soft_mask |= SFP_F_LOS;
++	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_DISABLE)
++		mask |= SFP_F_TX_DISABLE;
++	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_FAULT)
++		mask |= SFP_F_TX_FAULT;
++	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RX_LOS)
++		mask |= SFP_F_LOS;
++
++	// Poll the soft state for hardware pins we want to ignore
++	sfp->state_soft_mask = sfp->state_ignore_hw_mask & mask;
+ 
+ 	if (sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT) &&
+ 	    !sfp->need_poll)
+@@ -624,10 +626,12 @@ static void sfp_soft_stop_poll(struct sfp *sfp)
+ 
+ static unsigned int sfp_get_state(struct sfp *sfp)
+ {
++	unsigned int soft = sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT);
+ 	unsigned int state = sfp->get_state(sfp);
+ 
+-	if (state & SFP_F_PRESENT &&
+-	    sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT))
++	state &= ~sfp->state_ignore_hw_mask;
++
++	if (state & SFP_F_PRESENT && soft)
+ 		state |= sfp_soft_get_state(sfp);
+ 
+ 	return state;
+@@ -2052,6 +2056,15 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
+ 	if (ret < 0)
+ 		return ret;
+ 
++	/* Initialise state bits to ignore from hardware */
++	sfp->state_ignore_hw_mask = 0;
++	if (!sfp->gpio[GPIO_TX_DISABLE])
++		sfp->state_ignore_hw_mask |= SFP_F_TX_DISABLE;
++	if (!sfp->gpio[GPIO_TX_FAULT])
++		sfp->state_ignore_hw_mask |= SFP_F_TX_FAULT;
++	if (!sfp->gpio[GPIO_LOS])
++		sfp->state_ignore_hw_mask |= SFP_F_LOS;
++
+ 	sfp->module_t_start_up = T_START_UP;
+ 
+ 	sfp->tx_fault_ignore = false;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0033-mm-Fix-alloc_node_mem_map-with-ARCH_PFN_OFFSET-calcu.patch b/nixos/modules/kernel-patches/0033-mm-Fix-alloc_node_mem_map-with-ARCH_PFN_OFFSET-calcu.patch
new file mode 100644
index 0000000..55aa1d6
--- /dev/null
+++ b/nixos/modules/kernel-patches/0033-mm-Fix-alloc_node_mem_map-with-ARCH_PFN_OFFSET-calcu.patch
@@ -0,0 +1,92 @@
+From 4818a6d22f2ca39d148608e365209978a2be8c24 Mon Sep 17 00:00:00 2001
+From: Tobias Wolf <dev-NTEO@vplace.de>
+Date: Tue, 27 Sep 2022 16:21:21 +0200
+Subject: [PATCH 33/96] mm: Fix alloc_node_mem_map with ARCH_PFN_OFFSET
+ calculation
+
+An rt288x (ralink) based router (Belkin F5D8235 v1) does not boot with any
+kernel beyond version 4.3 resulting in:
+
+BUG: Bad page state in process swapper  pfn:086ac
+
+bisect resulted in:
+
+a1c34a3bf00af2cede839879502e12dc68491ad5 is the first bad commit
+commit a1c34a3bf00af2cede839879502e12dc68491ad5
+Author: Laura Abbott <laura@labbott.name>
+Date:   Thu Nov 5 18:48:46 2015 -0800
+
+    mm: Don't offset memmap for flatmem
+
+    Srinivas Kandagatla reported bad page messages when trying to remove the
+    bottom 2MB on an ARM based IFC6410 board
+
+      BUG: Bad page state in process swapper  pfn:fffa8
+      page:ef7fb500 count:0 mapcount:0 mapping:  (null) index:0x0
+      flags: 0x96640253(locked|error|dirty|active|arch_1|reclaim|mlocked)
+      page dumped because: PAGE_FLAGS_CHECK_AT_FREE flag(s) set
+      bad because of flags:
+      flags: 0x200041(locked|active|mlocked)
+      Modules linked in:
+      CPU: 0 PID: 0 Comm: swapper Not tainted 3.19.0-rc3-00007-g412f9ba-dirty
+#816
+      Hardware name: Qualcomm (Flattened Device Tree)
+        unwind_backtrace
+        show_stack
+        dump_stack
+        bad_page
+        free_pages_prepare
+        free_hot_cold_page
+        __free_pages
+        free_highmem_page
+        mem_init
+        start_kernel
+      Disabling lock debugging due to kernel taint
+    [...]
+:040000 040000 2de013c372345fd471cd58f0553c9b38b0ef1cc4
+0a8156f848733dfa21e16c196dfb6c0a76290709 M      mm
+
+This fix for ARM does not account ARCH_PFN_OFFSET for mem_map as later used by
+page_to_pfn anymore.
+
+The following output was generated with two hacked in printk statements:
+
+printk("before %p vs. %p or %p\n", mem_map, mem_map - offset, mem_map -
+(pgdat->node_start_pfn - ARCH_PFN_OFFSET));
+		if (page_to_pfn(mem_map) != pgdat->node_start_pfn)
+			mem_map -= offset + (pgdat->node_start_pfn - ARCH_PFN_OFFSET);
+printk("after %p\n", mem_map);
+
+Output:
+
+[    0.000000] before 8861b280 vs. 8861b280 or 8851b280
+[    0.000000] after 8851b280
+
+As seen in the first line mem_map with subtraction of offset does not equal the
+mem_map after subtraction of ARCH_PFN_OFFSET.
+
+After adding the offset of ARCH_PFN_OFFSET as well to mem_map as the
+previously calculated offset is zero for the named platform it is able to boot
+4.4 and 4.9-rc7 again.
+
+Signed-off-by: Tobias Wolf <dev-NTEO@vplace.de>
+---
+ mm/page_alloc.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/mm/page_alloc.c b/mm/page_alloc.c
+index cdf0e7d707c3..f3a69976b0b2 100644
+--- a/mm/page_alloc.c
++++ b/mm/page_alloc.c
+@@ -7698,7 +7698,7 @@ static void __init alloc_node_mem_map(struct pglist_data *pgdat)
+ 	if (pgdat == NODE_DATA(0)) {
+ 		mem_map = NODE_DATA(0)->node_mem_map;
+ 		if (page_to_pfn(mem_map) != pgdat->node_start_pfn)
+-			mem_map -= offset;
++			mem_map -= offset + (pgdat->node_start_pfn - ARCH_PFN_OFFSET);
+ 	}
+ #endif
+ }
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0034-rtc-rs5c372-support-alarms-up-to-1-week.patch b/nixos/modules/kernel-patches/0034-rtc-rs5c372-support-alarms-up-to-1-week.patch
new file mode 100644
index 0000000..7ad61a4
--- /dev/null
+++ b/nixos/modules/kernel-patches/0034-rtc-rs5c372-support-alarms-up-to-1-week.patch
@@ -0,0 +1,104 @@
+From debf651897e47681bb72bfcefd6b308264ae4985 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Cabanelas?= <dgcbueu@gmail.com>
+Date: Tue, 27 Sep 2022 16:21:26 +0200
+Subject: [PATCH 34/96] rtc: rs5c372: support alarms up to 1 week
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The Ricoh R2221x, R2223x, RS5C372, RV5C387A chips can handle 1 week
+alarms.
+
+Read the "wday" alarm register and convert it to a date to support up 1
+week in our driver.
+
+Signed-off-by: Daniel González Cabanelas <dgcbueu@gmail.com>
+---
+ drivers/rtc/rtc-rs5c372.c | 48 ++++++++++++++++++++++++++++++++++-----
+ 1 file changed, 42 insertions(+), 6 deletions(-)
+
+diff --git a/drivers/rtc/rtc-rs5c372.c b/drivers/rtc/rtc-rs5c372.c
+index cb15983383f5..ab4b5209870d 100644
+--- a/drivers/rtc/rtc-rs5c372.c
++++ b/drivers/rtc/rtc-rs5c372.c
+@@ -399,7 +399,9 @@ static int rs5c_read_alarm(struct device *dev, struct rtc_wkalrm *t)
+ {
+ 	struct i2c_client	*client = to_i2c_client(dev);
+ 	struct rs5c372		*rs5c = i2c_get_clientdata(client);
+-	int			status;
++	int			status, wday_offs;
++	struct rtc_time 	rtc;
++	unsigned long 		alarm_secs;
+ 
+ 	status = rs5c_get_regs(rs5c);
+ 	if (status < 0)
+@@ -409,6 +411,30 @@ static int rs5c_read_alarm(struct device *dev, struct rtc_wkalrm *t)
+ 	t->time.tm_sec = 0;
+ 	t->time.tm_min = bcd2bin(rs5c->regs[RS5C_REG_ALARM_A_MIN] & 0x7f);
+ 	t->time.tm_hour = rs5c_reg2hr(rs5c, rs5c->regs[RS5C_REG_ALARM_A_HOURS]);
++	t->time.tm_wday = ffs(rs5c->regs[RS5C_REG_ALARM_A_WDAY] & 0x7f) - 1;
++
++	/* determine the day, month and year based on alarm wday, taking as a
++	 * reference the current time from the rtc
++	 */
++	status = rs5c372_rtc_read_time(dev, &rtc);
++	if (status < 0)
++		return status;
++
++	wday_offs = t->time.tm_wday - rtc.tm_wday;
++	alarm_secs = mktime64(rtc.tm_year + 1900,
++			      rtc.tm_mon + 1,
++			      rtc.tm_mday + wday_offs,
++			      t->time.tm_hour,
++			      t->time.tm_min,
++			      t->time.tm_sec);
++
++	if (wday_offs < 0 || (wday_offs == 0 &&
++			      (t->time.tm_hour < rtc.tm_hour ||
++			       (t->time.tm_hour == rtc.tm_hour &&
++				t->time.tm_min <= rtc.tm_min))))
++		alarm_secs += 7 * 86400;
++
++	rtc_time64_to_tm(alarm_secs, &t->time);
+ 
+ 	/* ... and status */
+ 	t->enabled = !!(rs5c->regs[RS5C_REG_CTRL1] & RS5C_CTRL1_AALE);
+@@ -423,12 +449,20 @@ static int rs5c_set_alarm(struct device *dev, struct rtc_wkalrm *t)
+ 	struct rs5c372		*rs5c = i2c_get_clientdata(client);
+ 	int			status, addr, i;
+ 	unsigned char		buf[3];
++	struct rtc_time 	rtc_tm;
++	unsigned long 		rtc_secs, alarm_secs;
+ 
+-	/* only handle up to 24 hours in the future, like RTC_ALM_SET */
+-	if (t->time.tm_mday != -1
+-			|| t->time.tm_mon != -1
+-			|| t->time.tm_year != -1)
++	/* chip only can handle alarms up to one week in the future*/
++	status = rs5c372_rtc_read_time(dev, &rtc_tm);
++	if (status)
++		return status;
++	rtc_secs = rtc_tm_to_time64(&rtc_tm);
++	alarm_secs = rtc_tm_to_time64(&t->time);
++	if (alarm_secs >= rtc_secs + 7 * 86400) {
++		dev_err(dev, "%s: alarm maximum is one week in the future (%d)\n",
++			__func__, status);
+ 		return -EINVAL;
++	}
+ 
+ 	/* REVISIT: round up tm_sec */
+ 
+@@ -449,7 +483,9 @@ static int rs5c_set_alarm(struct device *dev, struct rtc_wkalrm *t)
+ 	/* set alarm */
+ 	buf[0] = bin2bcd(t->time.tm_min);
+ 	buf[1] = rs5c_hr2reg(rs5c, t->time.tm_hour);
+-	buf[2] = 0x7f;	/* any/all days */
++	/* each bit is the day of the week, 0x7f means all days */
++	buf[2] = (t->time.tm_wday >= 0 && t->time.tm_wday < 7) ?
++		  BIT(t->time.tm_wday) : 0x7f;
+ 
+ 	for (i = 0; i < sizeof(buf); i++) {
+ 		addr = RS5C_ADDR(RS5C_REG_ALARM_A_MIN + i);
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0035-rtc-rs5c372-let-the-alarm-to-be-used-as-wakeup-sourc.patch b/nixos/modules/kernel-patches/0035-rtc-rs5c372-let-the-alarm-to-be-used-as-wakeup-sourc.patch
new file mode 100644
index 0000000..ec3186a
--- /dev/null
+++ b/nixos/modules/kernel-patches/0035-rtc-rs5c372-let-the-alarm-to-be-used-as-wakeup-sourc.patch
@@ -0,0 +1,80 @@
+From 406867ec540eff2b484512063e8f9ade22daac07 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Cabanelas?= <dgcbueu@gmail.com>
+Date: Tue, 27 Sep 2022 16:21:26 +0200
+Subject: [PATCH 35/96] rtc: rs5c372: let the alarm to be used as wakeup source
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Currently there is no use for the interrupts on the rs5c372 RTC and the
+wakealarm isn't enabled. There are some devices like NASes which use this
+RTC to wake up from the power off state when the INTR pin is activated by
+the alarm clock.
+
+Enable the alarm and let to be used as a wakeup source.
+
+Tested on a Buffalo LS421DE NAS.
+
+Signed-off-by: Daniel González Cabanelas <dgcbueu@gmail.com>
+---
+ drivers/rtc/rtc-rs5c372.c | 16 ++++++++++++++++
+ 1 file changed, 16 insertions(+)
+
+diff --git a/drivers/rtc/rtc-rs5c372.c b/drivers/rtc/rtc-rs5c372.c
+index ab4b5209870d..5a1db45998c2 100644
+--- a/drivers/rtc/rtc-rs5c372.c
++++ b/drivers/rtc/rtc-rs5c372.c
+@@ -833,6 +833,7 @@ static int rs5c372_probe(struct i2c_client *client,
+ 	int err = 0;
+ 	int smbus_mode = 0;
+ 	struct rs5c372 *rs5c372;
++	bool rs5c372_can_wakeup_device = false;
+ 
+ 	dev_dbg(&client->dev, "%s\n", __func__);
+ 
+@@ -868,6 +869,12 @@ static int rs5c372_probe(struct i2c_client *client,
+ 	else
+ 		rs5c372->type = id->driver_data;
+ 
++#ifdef CONFIG_OF
++	if(of_property_read_bool(client->dev.of_node,
++					      "wakeup-source"))
++		rs5c372_can_wakeup_device = true;
++#endif
++
+ 	/* we read registers 0x0f then 0x00-0x0f; skip the first one */
+ 	rs5c372->regs = &rs5c372->buf[1];
+ 	rs5c372->smbus = smbus_mode;
+@@ -901,6 +908,8 @@ static int rs5c372_probe(struct i2c_client *client,
+ 		goto exit;
+ 	}
+ 
++	rs5c372->has_irq = 1;
++
+ 	/* if the oscillator lost power and no other software (like
+ 	 * the bootloader) set it up, do it here.
+ 	 *
+@@ -927,6 +936,10 @@ static int rs5c372_probe(struct i2c_client *client,
+ 			);
+ 
+ 	/* REVISIT use client->irq to register alarm irq ... */
++	if (rs5c372_can_wakeup_device) {
++		device_init_wakeup(&client->dev, true);
++	}
++
+ 	rs5c372->rtc = devm_rtc_device_register(&client->dev,
+ 					rs5c372_driver.driver.name,
+ 					&rs5c372_rtc_ops, THIS_MODULE);
+@@ -940,6 +953,9 @@ static int rs5c372_probe(struct i2c_client *client,
+ 	if (err)
+ 		goto exit;
+ 
++	/* the rs5c372 alarm only supports a minute accuracy */
++	rs5c372->rtc->uie_unsupported = 1;
++
+ 	return 0;
+ 
+ exit:
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0036-kernel-add-a-config-option-for-keeping-the-kallsyms-.patch b/nixos/modules/kernel-patches/0036-kernel-add-a-config-option-for-keeping-the-kallsyms-.patch
new file mode 100644
index 0000000..4597baa
--- /dev/null
+++ b/nixos/modules/kernel-patches/0036-kernel-add-a-config-option-for-keeping-the-kallsyms-.patch
@@ -0,0 +1,133 @@
+From 0ef054c3e7d7423d0f18edd7a29f62a9c6a811b4 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 27 Sep 2022 16:21:26 +0200
+Subject: [PATCH 36/96] kernel: add a config option for keeping the kallsyms
+ table uncompressed, saving ~9kb kernel size after lzma on ar71xx
+
+[john@phrozen.org: added to my upstream queue 30.12.2016]
+lede-commit: e0e3509b5ce2ccf93d4d67ea907613f5f7ec2eed
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ init/Kconfig            | 11 +++++++++++
+ kernel/kallsyms.c       |  8 ++++++++
+ scripts/kallsyms.c      | 12 ++++++++++++
+ scripts/link-vmlinux.sh |  4 ++++
+ 4 files changed, 35 insertions(+)
+
+diff --git a/init/Kconfig b/init/Kconfig
+index c7900e8975f1..4d5f1ee66139 100644
+--- a/init/Kconfig
++++ b/init/Kconfig
+@@ -1475,6 +1475,17 @@ config SYSCTL_ARCH_UNALIGN_ALLOW
+ 	  the unaligned access emulation.
+ 	  see arch/parisc/kernel/unaligned.c for reference
+ 
++config KALLSYMS_UNCOMPRESSED
++	bool "Keep kallsyms uncompressed"
++	depends on KALLSYMS
++	help
++		Normally kallsyms contains compressed symbols (using a token table),
++		reducing the uncompressed kernel image size. Keeping the symbol table
++		uncompressed significantly improves the size of this part in compressed
++		kernel images.
++
++		Say N unless you need compressed kernel images to be small.
++
+ config HAVE_PCSPKR_PLATFORM
+ 	bool
+ 
+diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
+index fbdf8d3279ac..f30ecfedd293 100644
+--- a/kernel/kallsyms.c
++++ b/kernel/kallsyms.c
+@@ -81,6 +81,11 @@ static unsigned int kallsyms_expand_symbol(unsigned int off,
+ 	 * For every byte on the compressed symbol data, copy the table
+ 	 * entry for that byte.
+ 	 */
++#ifdef CONFIG_KALLSYMS_UNCOMPRESSED
++	memcpy(result, data + 1, len - 1);
++	result += len - 1;
++	len = 0;
++#endif
+ 	while (len) {
+ 		tptr = &kallsyms_token_table[kallsyms_token_index[*data]];
+ 		data++;
+@@ -113,6 +118,9 @@ static unsigned int kallsyms_expand_symbol(unsigned int off,
+  */
+ static char kallsyms_get_symbol_type(unsigned int off)
+ {
++#ifdef CONFIG_KALLSYMS_UNCOMPRESSED
++	return kallsyms_names[off + 1];
++#endif
+ 	/*
+ 	 * Get just the first code, look it up in the token table,
+ 	 * and return the first char from this token.
+diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c
+index f18e6dfc68c5..262a3799bae1 100644
+--- a/scripts/kallsyms.c
++++ b/scripts/kallsyms.c
+@@ -58,6 +58,7 @@ static struct addr_range percpu_range = {
+ static struct sym_entry **table;
+ static unsigned int table_size, table_cnt;
+ static int all_symbols;
++static int uncompressed;
+ static int absolute_percpu;
+ static int base_relative;
+ 
+@@ -487,6 +488,9 @@ static void write_src(void)
+ 
+ 	free(markers);
+ 
++	if (uncompressed)
++		return;
++
+ 	output_label("kallsyms_token_table");
+ 	off = 0;
+ 	for (i = 0; i < 256; i++) {
+@@ -538,6 +542,9 @@ static unsigned char *find_token(unsigned char *str, int len,
+ {
+ 	int i;
+ 
++	if (uncompressed)
++		return NULL;
++
+ 	for (i = 0; i < len - 1; i++) {
+ 		if (str[i] == token[0] && str[i+1] == token[1])
+ 			return &str[i];
+@@ -610,6 +617,9 @@ static void optimize_result(void)
+ {
+ 	int i, best;
+ 
++	if (uncompressed)
++		return;
++
+ 	/* using the '\0' symbol last allows compress_symbols to use standard
+ 	 * fast string functions */
+ 	for (i = 255; i >= 0; i--) {
+@@ -774,6 +784,8 @@ int main(int argc, char **argv)
+ 				absolute_percpu = 1;
+ 			else if (strcmp(argv[i], "--base-relative") == 0)
+ 				base_relative = 1;
++			else if (strcmp(argv[i], "--uncompressed") == 0)
++				uncompressed = 1;
+ 			else
+ 				usage();
+ 		}
+diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh
+index eecc1863e556..8b8c9000892a 100755
+--- a/scripts/link-vmlinux.sh
++++ b/scripts/link-vmlinux.sh
+@@ -156,6 +156,10 @@ kallsyms()
+ 		kallsymopt="${kallsymopt} --base-relative"
+ 	fi
+ 
++	if [ -n "${CONFIG_KALLSYMS_UNCOMPRESSED}" ]; then
++		kallsymopt="${kallsymopt} --uncompressed"
++	fi
++
+ 	info KSYMS ${2}
+ 	${NM} -n ${1} | scripts/kallsyms ${kallsymopt} > ${2}
+ }
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0037-kernel-when-KALLSYMS-is-disabled-print-module-addres.patch b/nixos/modules/kernel-patches/0037-kernel-when-KALLSYMS-is-disabled-print-module-addres.patch
new file mode 100644
index 0000000..27cebc9
--- /dev/null
+++ b/nixos/modules/kernel-patches/0037-kernel-when-KALLSYMS-is-disabled-print-module-addres.patch
@@ -0,0 +1,49 @@
+From 045761a60c8beb28e82b68e284bf8e395036688f Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 27 Sep 2022 16:21:26 +0200
+Subject: [PATCH 37/96] kernel: when KALLSYMS is disabled, print module address
+ + size for matching backtrace entries
+
+[john@phrozen.org: felix will add this to his upstream queue]
+
+lede-commit 53827cdc824556cda910b23ce5030c363b8f1461
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ lib/vsprintf.c | 12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/lib/vsprintf.c b/lib/vsprintf.c
+index 3c1853a9d1c0..9803f88d7d21 100644
+--- a/lib/vsprintf.c
++++ b/lib/vsprintf.c
+@@ -980,8 +980,10 @@ char *symbol_string(char *buf, char *end, void *ptr,
+ 		    struct printf_spec spec, const char *fmt)
+ {
+ 	unsigned long value;
+-#ifdef CONFIG_KALLSYMS
+ 	char sym[KSYM_SYMBOL_LEN];
++#ifndef CONFIG_KALLSYMS
++	struct module *mod;
++	int len;
+ #endif
+ 
+ 	if (fmt[1] == 'R')
+@@ -1002,8 +1004,14 @@ char *symbol_string(char *buf, char *end, void *ptr,
+ 
+ 	return string_nocheck(buf, end, sym, spec);
+ #else
+-	return special_hex_number(buf, end, value, sizeof(void *));
++	len = snprintf(sym, sizeof(sym), "0x%lx", value);
++	mod = __module_address(value);
++	if (mod)
++		snprintf(sym + len, sizeof(sym) - len, " [%s@%p+0x%x]",
++			 mod->name, mod->core_layout.base,
++			 mod->core_layout.size);
+ #endif
++	return string(buf, end, sym, spec);
+ }
+ 
+ static const struct printf_spec default_str_spec = {
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0038-usr-sanitize-deps_initramfs-list.patch b/nixos/modules/kernel-patches/0038-usr-sanitize-deps_initramfs-list.patch
new file mode 100644
index 0000000..684905c
--- /dev/null
+++ b/nixos/modules/kernel-patches/0038-usr-sanitize-deps_initramfs-list.patch
@@ -0,0 +1,37 @@
+From 994a9ab083bc730f0057131d8fde7cb0dc7b490f Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <juhosg@openwrt.org>
+Date: Tue, 27 Sep 2022 16:21:26 +0200
+Subject: [PATCH 38/96] usr: sanitize deps_initramfs list
+
+If any filename in the intramfs dependency
+list contains a colon, that causes a kernel
+build error like this:
+
+/devel/openwrt/build_dir/linux-ar71xx_generic/linux-3.6.6/usr/Makefile:58: *** multiple target patterns.  Stop.
+make[5]: *** [usr] Error 2
+
+Fix it by removing such filenames from the
+deps_initramfs list.
+
+Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ usr/Makefile | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/usr/Makefile b/usr/Makefile
+index 59d9e8b07a01..5a8a00cf8580 100644
+--- a/usr/Makefile
++++ b/usr/Makefile
+@@ -56,6 +56,8 @@ hostprogs := gen_init_cpio
+ # The dependency list is generated by gen_initramfs.sh -l
+ -include $(obj)/.initramfs_data.cpio.d
+ 
++deps_initramfs := $(foreach v,$(deps_initramfs),$(if $(findstring :,$(v)),,$(v)))
++
+ # do not try to update files included in initramfs
+ $(deps_initramfs): ;
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0039-hack-net-wireless-make-the-wl12xx-glue-code-availabl.patch b/nixos/modules/kernel-patches/0039-hack-net-wireless-make-the-wl12xx-glue-code-availabl.patch
new file mode 100644
index 0000000..38ce38d
--- /dev/null
+++ b/nixos/modules/kernel-patches/0039-hack-net-wireless-make-the-wl12xx-glue-code-availabl.patch
@@ -0,0 +1,27 @@
+From 7484653f41a82ac2dae41e686645f423e24f19a8 Mon Sep 17 00:00:00 2001
+From: Imre Kaloz <kaloz@openwrt.org>
+Date: Tue, 27 Sep 2022 16:21:26 +0200
+Subject: [PATCH 39/96] hack: net: wireless: make the wl12xx glue code
+ available with compat-wireless, too
+
+Signed-off-by: Imre Kaloz <kaloz@openwrt.org>
+---
+ drivers/net/wireless/ti/Kconfig | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/net/wireless/ti/Kconfig b/drivers/net/wireless/ti/Kconfig
+index 7c0b17a76fe2..9f0edc5aca27 100644
+--- a/drivers/net/wireless/ti/Kconfig
++++ b/drivers/net/wireless/ti/Kconfig
+@@ -20,7 +20,7 @@ source "drivers/net/wireless/ti/wlcore/Kconfig"
+ 
+ config WILINK_PLATFORM_DATA
+ 	bool "TI WiLink platform data"
+-	depends on WLCORE_SDIO || WL1251_SDIO
++	depends on WLCORE_SDIO || WL1251_SDIO || ARCH_OMAP2PLUS
+ 	default y
+ 	help
+ 	Small platform data bit needed to pass data to the sdio modules.
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0040-generic-platform-mikrotik-build-bits-5.4.patch b/nixos/modules/kernel-patches/0040-generic-platform-mikrotik-build-bits-5.4.patch
new file mode 100644
index 0000000..15f615e
--- /dev/null
+++ b/nixos/modules/kernel-patches/0040-generic-platform-mikrotik-build-bits-5.4.patch
@@ -0,0 +1,38 @@
+From 6abb32c1bb4fdf810faba9b732582406578ca104 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Thibaut=20VAR=C3=88NE?= <hacks@slashdirt.org>
+Date: Sat, 28 Mar 2020 12:11:50 +0100
+Subject: [PATCH 40/96] generic: platform/mikrotik build bits (5.4)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This patch adds platform/mikrotik kernel build bits
+
+Signed-off-by: Thibaut VARÈNE <hacks@slashdirt.org>
+---
+ drivers/platform/Kconfig  | 2 ++
+ drivers/platform/Makefile | 1 +
+ 2 files changed, 3 insertions(+)
+
+diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig
+index 18fc6a08569e..ff069e4a2625 100644
+--- a/drivers/platform/Kconfig
++++ b/drivers/platform/Kconfig
+@@ -15,3 +15,5 @@ source "drivers/platform/mellanox/Kconfig"
+ source "drivers/platform/olpc/Kconfig"
+ 
+ source "drivers/platform/surface/Kconfig"
++
++source "drivers/platform/mikrotik/Kconfig"
+diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile
+index 4de08ef4ec9d..9cf599863f71 100644
+--- a/drivers/platform/Makefile
++++ b/drivers/platform/Makefile
+@@ -10,3 +10,4 @@ obj-$(CONFIG_OLPC_EC)		+= olpc/
+ obj-$(CONFIG_GOLDFISH)		+= goldfish/
+ obj-$(CONFIG_CHROME_PLATFORMS)	+= chrome/
+ obj-$(CONFIG_SURFACE_PLATFORMS)	+= surface/
++obj-$(CONFIG_MIKROTIK)		+= mikrotik/
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0041-fix-errors-in-unresolved-weak-symbols-on-arm.patch b/nixos/modules/kernel-patches/0041-fix-errors-in-unresolved-weak-symbols-on-arm.patch
new file mode 100644
index 0000000..3e86460
--- /dev/null
+++ b/nixos/modules/kernel-patches/0041-fix-errors-in-unresolved-weak-symbols-on-arm.patch
@@ -0,0 +1,29 @@
+From 5e60b036c4ea403bd96eb4eac0f711cd38c9e766 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 27 Sep 2022 16:21:35 +0200
+Subject: [PATCH 41/96] fix errors in unresolved weak symbols on arm
+
+lede-commit: 570699d4838a907c3ef9f2819bf19eb72997b32f
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ arch/arm/kernel/module.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/arch/arm/kernel/module.c b/arch/arm/kernel/module.c
+index d59c36dc0494..200a31b0acad 100644
+--- a/arch/arm/kernel/module.c
++++ b/arch/arm/kernel/module.c
+@@ -146,6 +146,10 @@ apply_relocate(Elf32_Shdr *sechdrs, const char *strtab, unsigned int symindex,
+ 			return -ENOEXEC;
+ 		}
+ 
++		if ((IS_ERR_VALUE(sym->st_value) || !sym->st_value) &&
++		    ELF_ST_BIND(sym->st_info) == STB_WEAK)
++			continue;
++
+ 		loc = dstsec->sh_addr + rel->r_offset;
+ 
+ 		switch (ELF32_R_TYPE(rel->r_info)) {
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0042-arc-add-OWRTDTB-section.patch b/nixos/modules/kernel-patches/0042-arc-add-OWRTDTB-section.patch
new file mode 100644
index 0000000..402ea47
--- /dev/null
+++ b/nixos/modules/kernel-patches/0042-arc-add-OWRTDTB-section.patch
@@ -0,0 +1,93 @@
+From ec54842bfce8240815a9ce7a1db0935851fe9ea8 Mon Sep 17 00:00:00 2001
+From: Evgeniy Didin <Evgeniy.Didin@synopsys.com>
+Date: Fri, 15 Mar 2019 18:53:38 +0300
+Subject: [PATCH 42/96] arc add OWRTDTB section
+
+This change allows OpenWRT to patch resulting kernel binary with
+external .dtb.
+
+That allows us to re-use exactky the same vmlinux on different boards
+given its ARC core configurations match (at least cache line sizes etc).
+
+""patch-dtb" searches for ASCII "OWRTDTB:" strign and copies external
+.dtb right after it, keeping the string in place.
+
+Signed-off-by: Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>
+Signed-off-by: Alexey Brodkin <abrodkin@synopsys.com>
+Signed-off-by: Evgeniy Didin <Evgeniy.Didin@synopsys.com>
+---
+ arch/arc/kernel/head.S        | 10 ++++++++++
+ arch/arc/kernel/setup.c       |  4 +++-
+ arch/arc/kernel/vmlinux.lds.S | 13 +++++++++++++
+ 3 files changed, 26 insertions(+), 1 deletion(-)
+
+diff --git a/arch/arc/kernel/head.S b/arch/arc/kernel/head.S
+index 9152782444b5..167d81ce6ab7 100644
+--- a/arch/arc/kernel/head.S
++++ b/arch/arc/kernel/head.S
+@@ -88,6 +88,16 @@
+ 	DSP_EARLY_INIT
+ .endm
+ 
++	; Here "patch-dtb" will embed external .dtb
++	; Note "patch-dtb" searches for ASCII "OWRTDTB:" string
++	; and pastes .dtb right after it, hense the string precedes
++	; __image_dtb symbol.
++	.section .owrt, "aw",@progbits
++	.ascii  "OWRTDTB:"
++ENTRY(__image_dtb)
++	.fill   0x4000
++END(__image_dtb)
++
+ 	.section .init.text, "ax",@progbits
+ 
+ ;----------------------------------------------------------------
+diff --git a/arch/arc/kernel/setup.c b/arch/arc/kernel/setup.c
+index 41f07b3e594e..d0c3e7817022 100644
+--- a/arch/arc/kernel/setup.c
++++ b/arch/arc/kernel/setup.c
+@@ -495,6 +495,8 @@ static inline bool uboot_arg_invalid(unsigned long addr)
+ /* We always pass 0 as magic from U-boot */
+ #define UBOOT_MAGIC_VALUE	0
+ 
++extern struct boot_param_header __image_dtb;
++
+ void __init handle_uboot_args(void)
+ {
+ 	bool use_embedded_dtb = true;
+@@ -533,7 +535,7 @@ void __init handle_uboot_args(void)
+ ignore_uboot_args:
+ 
+ 	if (use_embedded_dtb) {
+-		machine_desc = setup_machine_fdt(__dtb_start);
++		machine_desc = setup_machine_fdt(&__image_dtb);
+ 		if (!machine_desc)
+ 			panic("Embedded DT invalid\n");
+ 	}
+diff --git a/arch/arc/kernel/vmlinux.lds.S b/arch/arc/kernel/vmlinux.lds.S
+index 529ae50f9fe2..ecdcd6842c0d 100644
+--- a/arch/arc/kernel/vmlinux.lds.S
++++ b/arch/arc/kernel/vmlinux.lds.S
+@@ -27,6 +27,19 @@ SECTIONS
+ 
+ 	. = CONFIG_LINUX_LINK_BASE;
+ 
++	/*
++	* In OpenWRT we want to patch built binary embedding .dtb of choice.
++	* This is implemented with "patch-dtb" utility which searches for
++	* "OWRTDTB:" string in first 16k of image and if it is found
++	* copies .dtb right after mentioned string.
++	*
++	* Note: "OWRTDTB:" won't be overwritten with .dtb, .dtb will follow it.
++	*/
++ 	.owrt : {
++		*(.owrt)
++	. = ALIGN(PAGE_SIZE);
++	}
++
+ 	_int_vec_base_lds = .;
+ 	.vector : {
+ 		*(.vector)
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0043-arc-enable-unaligned-access-in-kernel-mode.patch b/nixos/modules/kernel-patches/0043-arc-enable-unaligned-access-in-kernel-mode.patch
new file mode 100644
index 0000000..95d86d4
--- /dev/null
+++ b/nixos/modules/kernel-patches/0043-arc-enable-unaligned-access-in-kernel-mode.patch
@@ -0,0 +1,31 @@
+From e4a3cb7256682f9f2e0fd2b3c5e06441052a9822 Mon Sep 17 00:00:00 2001
+From: Alexey Brodkin <abrodkin@synopsys.com>
+Date: Tue, 27 Sep 2022 16:21:35 +0200
+Subject: [PATCH 43/96] arc: enable unaligned access in kernel mode
+
+This enables misaligned access handling even in kernel mode.
+Some wireless drivers (ath9k-htc and mt7601u) use misaligned accesses
+here and there and to cope with that without fixing stuff in the drivers
+we're just gracefully handling it on ARC.
+
+Signed-off-by: Alexey Brodkin <abrodkin@synopsys.com>
+---
+ arch/arc/kernel/unaligned.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/arch/arc/kernel/unaligned.c b/arch/arc/kernel/unaligned.c
+index 99a9b92ed98d..a4da2cbcac97 100644
+--- a/arch/arc/kernel/unaligned.c
++++ b/arch/arc/kernel/unaligned.c
+@@ -202,7 +202,7 @@ int misaligned_fixup(unsigned long address, struct pt_regs *regs,
+ 	char buf[TASK_COMM_LEN];
+ 
+ 	/* handle user mode only and only if enabled by sysadmin */
+-	if (!user_mode(regs) || !unaligned_enabled)
++	if (!unaligned_enabled)
+ 		return 1;
+ 
+ 	if (no_unaligned_warning) {
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0044-mtd-mtdsplit-support.patch b/nixos/modules/kernel-patches/0044-mtd-mtdsplit-support.patch
new file mode 100644
index 0000000..cf98c20
--- /dev/null
+++ b/nixos/modules/kernel-patches/0044-mtd-mtdsplit-support.patch
@@ -0,0 +1,340 @@
+From 3048cf485cbb35e59865e1d841bfbb06a093987a Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 11:47:35 +0200
+Subject: [PATCH 44/96] mtd: mtdsplit support
+
+---
+ drivers/mtd/Kconfig            |  19 ++++
+ drivers/mtd/Makefile           |   2 +
+ drivers/mtd/mtdpart.c          | 169 ++++++++++++++++++++++++++++-----
+ include/linux/mtd/mtd.h        |  25 +++++
+ include/linux/mtd/partitions.h |   7 ++
+ 5 files changed, 197 insertions(+), 25 deletions(-)
+
+diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
+index 796a2eccbef0..f9ed93c4cf0f 100644
+--- a/drivers/mtd/Kconfig
++++ b/drivers/mtd/Kconfig
+@@ -12,6 +12,25 @@ menuconfig MTD
+ 
+ if MTD
+ 
++menu "OpenWrt specific MTD options"
++
++config MTD_ROOTFS_ROOT_DEV
++	bool "Automatically set 'rootfs' partition to be root filesystem"
++	default y
++
++config MTD_SPLIT_FIRMWARE
++	bool "Automatically split firmware partition for kernel+rootfs"
++	default y
++
++config MTD_SPLIT_FIRMWARE_NAME
++	string "Firmware partition name"
++	depends on MTD_SPLIT_FIRMWARE
++	default "firmware"
++
++source "drivers/mtd/mtdsplit/Kconfig"
++
++endmenu
++
+ config MTD_TESTS
+ 	tristate "MTD tests support (DANGEROUS)"
+ 	depends on m
+diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
+index 593d0593a038..b14b7fe0f597 100644
+--- a/drivers/mtd/Makefile
++++ b/drivers/mtd/Makefile
+@@ -9,6 +9,8 @@ mtd-y				:= mtdcore.o mtdsuper.o mtdconcat.o mtdpart.o mtdchar.o
+ 
+ obj-y				+= parsers/
+ 
++obj-$(CONFIG_MTD_SPLIT)		+= mtdsplit/
++
+ # 'Users' - code which presents functionality to userspace.
+ obj-$(CONFIG_MTD_BLKDEVS)	+= mtd_blkdevs.o
+ obj-$(CONFIG_MTD_BLOCK)		+= mtdblock.o
+diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c
+index d442fa94c872..f1ed12aae1fe 100644
+--- a/drivers/mtd/mtdpart.c
++++ b/drivers/mtd/mtdpart.c
+@@ -15,11 +15,13 @@
+ #include <linux/kmod.h>
+ #include <linux/mtd/mtd.h>
+ #include <linux/mtd/partitions.h>
++#include <linux/magic.h>
+ #include <linux/err.h>
+ #include <linux/of.h>
+ #include <linux/of_platform.h>
+ 
+ #include "mtdcore.h"
++#include "mtdsplit/mtdsplit.h"
+ 
+ /*
+  * MTD methods which simply translate the effective address and pass through
+@@ -236,6 +238,146 @@ static int mtd_add_partition_attrs(struct mtd_info *new)
+ 	return ret;
+ }
+ 
++static DEFINE_SPINLOCK(part_parser_lock);
++static LIST_HEAD(part_parsers);
++
++static struct mtd_part_parser *mtd_part_parser_get(const char *name)
++{
++	struct mtd_part_parser *p, *ret = NULL;
++
++	spin_lock(&part_parser_lock);
++
++	list_for_each_entry(p, &part_parsers, list)
++		if (!strcmp(p->name, name) && try_module_get(p->owner)) {
++			ret = p;
++			break;
++		}
++
++	spin_unlock(&part_parser_lock);
++
++	return ret;
++}
++
++static inline void mtd_part_parser_put(const struct mtd_part_parser *p)
++{
++	module_put(p->owner);
++}
++
++static struct mtd_part_parser *
++get_partition_parser_by_type(enum mtd_parser_type type,
++			     struct mtd_part_parser *start)
++{
++	struct mtd_part_parser *p, *ret = NULL;
++
++	spin_lock(&part_parser_lock);
++
++	p = list_prepare_entry(start, &part_parsers, list);
++	if (start)
++		mtd_part_parser_put(start);
++
++	list_for_each_entry_continue(p, &part_parsers, list) {
++		if (p->type == type && try_module_get(p->owner)) {
++			ret = p;
++			break;
++		}
++	}
++
++	spin_unlock(&part_parser_lock);
++
++	return ret;
++}
++
++static int parse_mtd_partitions_by_type(struct mtd_info *master,
++					enum mtd_parser_type type,
++					const struct mtd_partition **pparts,
++					struct mtd_part_parser_data *data)
++{
++	struct mtd_part_parser *prev = NULL;
++	int ret = 0;
++
++	while (1) {
++		struct mtd_part_parser *parser;
++
++		parser = get_partition_parser_by_type(type, prev);
++		if (!parser)
++			break;
++
++		ret = (*parser->parse_fn)(master, pparts, data);
++
++		if (ret > 0) {
++			mtd_part_parser_put(parser);
++			printk(KERN_NOTICE
++			       "%d %s partitions found on MTD device %s\n",
++			       ret, parser->name, master->name);
++			break;
++		}
++
++		prev = parser;
++	}
++
++	return ret;
++}
++
++static int
++run_parsers_by_type(struct mtd_info *child, enum mtd_parser_type type)
++{
++	struct mtd_partition *parts;
++	int nr_parts;
++	int i;
++
++	nr_parts = parse_mtd_partitions_by_type(child, type, (const struct mtd_partition **)&parts,
++						NULL);
++	if (nr_parts <= 0)
++		return nr_parts;
++
++	if (WARN_ON(!parts))
++		return 0;
++
++	for (i = 0; i < nr_parts; i++) {
++		/* adjust partition offsets */
++		parts[i].offset += child->part.offset;
++
++		mtd_add_partition(child->parent,
++				  parts[i].name,
++				  parts[i].offset,
++				  parts[i].size);
++	}
++
++	kfree(parts);
++
++	return nr_parts;
++}
++
++#ifdef CONFIG_MTD_SPLIT_FIRMWARE_NAME
++#define SPLIT_FIRMWARE_NAME	CONFIG_MTD_SPLIT_FIRMWARE_NAME
++#else
++#define SPLIT_FIRMWARE_NAME	"unused"
++#endif
++
++static void split_firmware(struct mtd_info *master, struct mtd_info *part)
++{
++	run_parsers_by_type(part, MTD_PARSER_TYPE_FIRMWARE);
++}
++
++static void mtd_partition_split(struct mtd_info *master, struct mtd_info *part)
++{
++	static int rootfs_found = 0;
++
++	if (rootfs_found)
++		return;
++
++	if (!strcmp(part->name, "rootfs")) {
++		run_parsers_by_type(part, MTD_PARSER_TYPE_ROOTFS);
++
++		rootfs_found = 1;
++	}
++
++	if (IS_ENABLED(CONFIG_MTD_SPLIT_FIRMWARE) &&
++	    !strcmp(part->name, SPLIT_FIRMWARE_NAME) &&
++	    !of_find_property(mtd_get_of_node(part), "compatible", NULL))
++		split_firmware(master, part);
++}
++
+ int mtd_add_partition(struct mtd_info *parent, const char *name,
+ 		      long long offset, long long length)
+ {
+@@ -274,6 +416,7 @@ int mtd_add_partition(struct mtd_info *parent, const char *name,
+ 	if (ret)
+ 		goto err_remove_part;
+ 
++	mtd_partition_split(parent, child);
+ 	mtd_add_partition_attrs(child);
+ 
+ 	return 0;
+@@ -422,6 +565,7 @@ int add_mtd_partitions(struct mtd_info *parent,
+ 			goto err_del_partitions;
+ 		}
+ 
++		mtd_partition_split(master, child);
+ 		mtd_add_partition_attrs(child);
+ 
+ 		/* Look for subpartitions */
+@@ -438,31 +582,6 @@ int add_mtd_partitions(struct mtd_info *parent,
+ 	return ret;
+ }
+ 
+-static DEFINE_SPINLOCK(part_parser_lock);
+-static LIST_HEAD(part_parsers);
+-
+-static struct mtd_part_parser *mtd_part_parser_get(const char *name)
+-{
+-	struct mtd_part_parser *p, *ret = NULL;
+-
+-	spin_lock(&part_parser_lock);
+-
+-	list_for_each_entry(p, &part_parsers, list)
+-		if (!strcmp(p->name, name) && try_module_get(p->owner)) {
+-			ret = p;
+-			break;
+-		}
+-
+-	spin_unlock(&part_parser_lock);
+-
+-	return ret;
+-}
+-
+-static inline void mtd_part_parser_put(const struct mtd_part_parser *p)
+-{
+-	module_put(p->owner);
+-}
+-
+ /*
+  * Many partition parsers just expected the core to kfree() all their data in
+  * one chunk. Do that by default.
+diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
+index 955aee14b0f7..e2359a3569f0 100644
+--- a/include/linux/mtd/mtd.h
++++ b/include/linux/mtd/mtd.h
+@@ -608,6 +608,24 @@ static inline void mtd_align_erase_req(struct mtd_info *mtd,
+ 		req->len += mtd->erasesize - mod;
+ }
+ 
++static inline uint64_t mtd_roundup_to_eb(uint64_t sz, struct mtd_info *mtd)
++{
++	if (mtd_mod_by_eb(sz, mtd) == 0)
++		return sz;
++
++	/* Round up to next erase block */
++	return (mtd_div_by_eb(sz, mtd) + 1) * mtd->erasesize;
++}
++
++static inline uint64_t mtd_rounddown_to_eb(uint64_t sz, struct mtd_info *mtd)
++{
++	if (mtd_mod_by_eb(sz, mtd) == 0)
++		return sz;
++
++	/* Round down to the start of the current erase block */
++	return (mtd_div_by_eb(sz, mtd)) * mtd->erasesize;
++}
++
+ static inline uint32_t mtd_div_by_ws(uint64_t sz, struct mtd_info *mtd)
+ {
+ 	if (mtd->writesize_shift)
+@@ -680,6 +698,13 @@ extern void __put_mtd_device(struct mtd_info *mtd);
+ extern struct mtd_info *get_mtd_device_nm(const char *name);
+ extern void put_mtd_device(struct mtd_info *mtd);
+ 
++static inline uint64_t mtdpart_get_offset(const struct mtd_info *mtd)
++{
++	if (!mtd_is_partition(mtd))
++		return 0;
++
++	return mtd->part.offset;
++}
+ 
+ struct mtd_notifier {
+ 	void (*add)(struct mtd_info *mtd);
+diff --git a/include/linux/mtd/partitions.h b/include/linux/mtd/partitions.h
+index b74a539ec581..65ba0dbf961d 100644
+--- a/include/linux/mtd/partitions.h
++++ b/include/linux/mtd/partitions.h
+@@ -75,6 +75,12 @@ struct mtd_part_parser_data {
+  * Functions dealing with the various ways of partitioning the space
+  */
+ 
++enum mtd_parser_type {
++	MTD_PARSER_TYPE_DEVICE = 0,
++	MTD_PARSER_TYPE_ROOTFS,
++	MTD_PARSER_TYPE_FIRMWARE,
++};
++
+ struct mtd_part_parser {
+ 	struct list_head list;
+ 	struct module *owner;
+@@ -83,6 +89,7 @@ struct mtd_part_parser {
+ 	int (*parse_fn)(struct mtd_info *, const struct mtd_partition **,
+ 			struct mtd_part_parser_data *);
+ 	void (*cleanup)(const struct mtd_partition *pparts, int nr_parts);
++	enum mtd_parser_type type;
+ };
+ 
+ /* Container for passing around a set of parsed partitions */
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0045-mtd-spi-nor-write-support-for-minor-aligned-partitio.patch b/nixos/modules/kernel-patches/0045-mtd-spi-nor-write-support-for-minor-aligned-partitio.patch
new file mode 100644
index 0000000..1374147
--- /dev/null
+++ b/nixos/modules/kernel-patches/0045-mtd-spi-nor-write-support-for-minor-aligned-partitio.patch
@@ -0,0 +1,238 @@
+From cb9e7995c29b8c25bd84b3f47797d67dbf6dfd0e Mon Sep 17 00:00:00 2001
+From: John Thomson <git@johnthomson.fastmail.com.au>
+Date: Fri, 25 Dec 2020 18:50:08 +1000
+Subject: [PATCH 45/96] mtd: spi-nor: write support for minor aligned
+ partitions
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Do not prevent writing to mtd partitions where a partition boundary sits
+on a minor erasesize boundary.
+This addresses a FIXME that has been present since the start of the
+linux git history:
+/* Doesn't start on a boundary of major erase size */
+/* FIXME: Let it be writable if it is on a boundary of
+ * _minor_ erase size though */
+
+Allow a uniform erase region spi-nor device to be configured
+to use the non-uniform erase regions code path for an erase with:
+CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE=y
+
+On supporting hardware (SECT_4K: majority of current SPI-NOR device)
+provide the facility for an erase to use the least number
+of SPI-NOR operations, as well as access to 4K erase without
+requiring CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
+
+Introduce erasesize_minor to the mtd struct,
+the smallest erasesize supported by the device
+
+On existing devices, this is useful where write support is wanted
+for data on a 4K partition, such as some u-boot-env partitions,
+or RouterBoot soft_config, while still netting the performance
+benefits of using 64K sectors
+
+Performance:
+time mtd erase firmware
+OpenWrt 5.10 ramips MT7621 w25q128jv 0xfc0000 partition length
+
+Without this patch
+MTD_SPI_NOR_USE_4K_SECTORS=y	|n
+real    2m 11.66s		|0m 50.86s
+user    0m 0.00s		|0m 0.00s
+sys     1m 56.20s		|0m 50.80s
+
+With this patch
+MTD_SPI_NOR_USE_VARIABLE_ERASE=n|y		|4K_SECTORS=y
+real    0m 51.68s		|0m 50.85s	|2m 12.89s
+user    0m 0.00s		|0m 0.00s	|0m 0.01s
+sys     0m 46.94s		|0m 50.38s	|2m 12.46s
+
+Signed-off-by: John Thomson <git@johnthomson.fastmail.com.au>
+Signed-off-by: Thibaut VARÈNE <hacks+kernel@slashdirt.org>
+---
+ drivers/mtd/mtdcore.c       | 10 ++++++++++
+ drivers/mtd/mtdpart.c       | 35 +++++++++++++++++++++++++----------
+ drivers/mtd/spi-nor/Kconfig | 10 ++++++++++
+ drivers/mtd/spi-nor/core.c  | 11 +++++++++--
+ include/linux/mtd/mtd.h     |  2 ++
+ 5 files changed, 56 insertions(+), 12 deletions(-)
+
+diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
+index 9eb0680db312..ccb80197d960 100644
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -168,6 +168,15 @@ static ssize_t mtd_erasesize_show(struct device *dev,
+ }
+ MTD_DEVICE_ATTR_RO(erasesize);
+ 
++static ssize_t mtd_erasesize_minor_show(struct device *dev,
++		struct device_attribute *attr, char *buf)
++{
++	struct mtd_info *mtd = dev_get_drvdata(dev);
++
++	return sysfs_emit(buf, "%lu\n", (unsigned long)mtd->erasesize_minor);
++}
++MTD_DEVICE_ATTR_RO(erasesize_minor);
++
+ static ssize_t mtd_writesize_show(struct device *dev,
+ 		struct device_attribute *attr, char *buf)
+ {
+@@ -313,6 +322,7 @@ static struct attribute *mtd_attrs[] = {
+ 	&dev_attr_flags.attr,
+ 	&dev_attr_size.attr,
+ 	&dev_attr_erasesize.attr,
++	&dev_attr_erasesize_minor.attr,
+ 	&dev_attr_writesize.attr,
+ 	&dev_attr_subpagesize.attr,
+ 	&dev_attr_oobsize.attr,
+diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c
+index f1ed12aae1fe..0b7be183a1f6 100644
+--- a/drivers/mtd/mtdpart.c
++++ b/drivers/mtd/mtdpart.c
+@@ -41,6 +41,7 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
+ 	struct mtd_info *master = mtd_get_master(parent);
+ 	int wr_alignment = (parent->flags & MTD_NO_ERASE) ?
+ 			   master->writesize : master->erasesize;
++	int wr_alignment_minor = 0;
+ 	u64 parent_size = mtd_is_partition(parent) ?
+ 			  parent->part.size : parent->size;
+ 	struct mtd_info *child;
+@@ -165,6 +166,7 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
+ 	} else {
+ 		/* Single erase size */
+ 		child->erasesize = master->erasesize;
++		child->erasesize_minor = master->erasesize_minor;
+ 	}
+ 
+ 	/*
+@@ -172,26 +174,39 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
+ 	 * exposes several regions with different erasesize. Adjust
+ 	 * wr_alignment accordingly.
+ 	 */
+-	if (!(child->flags & MTD_NO_ERASE))
++	if (!(child->flags & MTD_NO_ERASE)) {
+ 		wr_alignment = child->erasesize;
++		wr_alignment_minor = child->erasesize_minor;
++	}
+ 
+ 	tmp = mtd_get_master_ofs(child, 0);
+ 	remainder = do_div(tmp, wr_alignment);
+ 	if ((child->flags & MTD_WRITEABLE) && remainder) {
+-		/* Doesn't start on a boundary of major erase size */
+-		/* FIXME: Let it be writable if it is on a boundary of
+-		 * _minor_ erase size though */
+-		child->flags &= ~MTD_WRITEABLE;
+-		printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase/write block boundary -- force read-only\n",
+-			part->name);
++		if (wr_alignment_minor) {
++			/* rely on minor being a factor of major erasesize */
++			tmp = remainder;
++			remainder = do_div(tmp, wr_alignment_minor);
++		}
++		if (remainder) {
++			child->flags &= ~MTD_WRITEABLE;
++			printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase/write block boundary -- force read-only\n",
++				part->name);
++		}
+ 	}
+ 
+ 	tmp = mtd_get_master_ofs(child, 0) + child->part.size;
+ 	remainder = do_div(tmp, wr_alignment);
+ 	if ((child->flags & MTD_WRITEABLE) && remainder) {
+-		child->flags &= ~MTD_WRITEABLE;
+-		printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase/write block -- force read-only\n",
+-			part->name);
++		if (wr_alignment_minor) {
++			tmp = remainder;
++			remainder = do_div(tmp, wr_alignment_minor);
++		}
++
++		if (remainder) {
++			child->flags &= ~MTD_WRITEABLE;
++			printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase/write block -- force read-only\n",
++				part->name);
++		}
+ 	}
+ 
+ 	child->size = child->part.size;
+diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
+index 24cd25de2b8b..09df9f1a8127 100644
+--- a/drivers/mtd/spi-nor/Kconfig
++++ b/drivers/mtd/spi-nor/Kconfig
+@@ -10,6 +10,16 @@ menuconfig MTD_SPI_NOR
+ 
+ if MTD_SPI_NOR
+ 
++config MTD_SPI_NOR_USE_VARIABLE_ERASE
++	bool "Disable uniform_erase to allow use of all hardware supported erasesizes"
++	depends on !MTD_SPI_NOR_USE_4K_SECTORS
++	default n
++	help
++	  Allow mixed use of all hardware supported erasesizes,
++	  by forcing spi_nor to use the multiple eraseregions code path.
++	  For example: A 68K erase will use one 64K erase, and one 4K erase
++	  on supporting hardware.
++
+ config MTD_SPI_NOR_USE_4K_SECTORS
+ 	bool "Use small 4096 B erase sectors"
+ 	default y
+diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
+index e758ebfe1a9f..f237f050b43c 100644
+--- a/drivers/mtd/spi-nor/core.c
++++ b/drivers/mtd/spi-nor/core.c
+@@ -1048,6 +1048,8 @@ static u8 spi_nor_convert_3to4_erase(u8 opcode)
+ 
+ static bool spi_nor_has_uniform_erase(const struct spi_nor *nor)
+ {
++	if (IS_ENABLED(CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE))
++		return false;
+ 	return !!nor->params->erase_map.uniform_erase_type;
+ }
+ 
+@@ -2144,6 +2146,7 @@ static int spi_nor_select_erase(struct spi_nor *nor)
+ {
+ 	struct spi_nor_erase_map *map = &nor->params->erase_map;
+ 	const struct spi_nor_erase_type *erase = NULL;
++	const struct spi_nor_erase_type *erase_minor = NULL;
+ 	struct mtd_info *mtd = &nor->mtd;
+ 	u32 wanted_size = nor->info->sector_size;
+ 	int i;
+@@ -2176,8 +2179,9 @@ static int spi_nor_select_erase(struct spi_nor *nor)
+ 	 */
+ 	for (i = SNOR_ERASE_TYPE_MAX - 1; i >= 0; i--) {
+ 		if (map->erase_type[i].size) {
+-			erase = &map->erase_type[i];
+-			break;
++			if (!erase)
++				erase = &map->erase_type[i];
++			erase_minor = &map->erase_type[i];
+ 		}
+ 	}
+ 
+@@ -2185,6 +2189,9 @@ static int spi_nor_select_erase(struct spi_nor *nor)
+ 		return -EINVAL;
+ 
+ 	mtd->erasesize = erase->size;
++	if (IS_ENABLED(CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE) &&
++			erase_minor && erase_minor->size < erase->size)
++		mtd->erasesize_minor = erase_minor->size;
+ 	return 0;
+ }
+ 
+diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
+index e2359a3569f0..cd7807a23123 100644
+--- a/include/linux/mtd/mtd.h
++++ b/include/linux/mtd/mtd.h
+@@ -238,6 +238,8 @@ struct mtd_info {
+ 	 * information below if they desire
+ 	 */
+ 	uint32_t erasesize;
++	/* "Minor" (smallest) erase size supported by the whole device */
++	uint32_t erasesize_minor;
+ 	/* Minimal writable flash unit size. In case of NOR flash it is 1 (even
+ 	 * though individual bits can be cleared), in case of NAND flash it is
+ 	 * one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0046-add-patch-for-including-unpartitioned-space-in-the-r.patch b/nixos/modules/kernel-patches/0046-add-patch-for-including-unpartitioned-space-in-the-r.patch
new file mode 100644
index 0000000..a4a0576
--- /dev/null
+++ b/nixos/modules/kernel-patches/0046-add-patch-for-including-unpartitioned-space-in-the-r.patch
@@ -0,0 +1,49 @@
+From e9a2fef0e8f45928beb729eefde10d8e38dc75c1 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 27 Sep 2022 16:21:36 +0200
+Subject: [PATCH 46/96] add patch for including unpartitioned space in the
+ rootfs partition for redboot devices (if applicable)
+
+[john@phrozen.org: used by ixp and others]
+
+lede-commit: 394918851f84e4d00fa16eb900e7700e95091f00
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ drivers/mtd/parsers/redboot.c | 19 +++++++++++++------
+ 1 file changed, 13 insertions(+), 6 deletions(-)
+
+diff --git a/drivers/mtd/parsers/redboot.c b/drivers/mtd/parsers/redboot.c
+index a16b42a88581..5d07ed6e1365 100644
+--- a/drivers/mtd/parsers/redboot.c
++++ b/drivers/mtd/parsers/redboot.c
+@@ -278,14 +278,21 @@ static int parse_redboot_partitions(struct mtd_info *master,
+ #endif
+ 		names += strlen(names) + 1;
+ 
+-#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
+ 		if (fl->next && fl->img->flash_base + fl->img->size + master->erasesize <= fl->next->img->flash_base) {
+-			i++;
+-			parts[i].offset = parts[i - 1].size + parts[i - 1].offset;
+-			parts[i].size = fl->next->img->flash_base - parts[i].offset;
+-			parts[i].name = nullname;
+-		}
++			if (!strcmp(parts[i].name, "rootfs")) {
++				parts[i].size = fl->next->img->flash_base;
++				parts[i].size &= ~(master->erasesize - 1);
++				parts[i].size -= parts[i].offset;
++#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
++				nrparts--;
++			} else {
++				i++;
++				parts[i].offset = parts[i-1].size + parts[i-1].offset;
++				parts[i].size = fl->next->img->flash_base - parts[i].offset;
++				parts[i].name = nullname;
+ #endif
++			}
++		}
+ 		tmp_fl = fl;
+ 		fl = fl->next;
+ 		kfree(tmp_fl);
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0047-Add-myloader-partition-table-parser.patch b/nixos/modules/kernel-patches/0047-Add-myloader-partition-table-parser.patch
new file mode 100644
index 0000000..6e9426b
--- /dev/null
+++ b/nixos/modules/kernel-patches/0047-Add-myloader-partition-table-parser.patch
@@ -0,0 +1,247 @@
+From 03cf122501d9458a0eae29e3504c5e55aeac1ca6 Mon Sep 17 00:00:00 2001
+From: Florian Fainelli <f.fainelli@gmail.com>
+Date: Tue, 27 Sep 2022 16:21:36 +0200
+Subject: [PATCH 47/96] Add myloader partition table parser
+
+[john@phozen.org: shoud be upstreamable]
+
+lede-commit: d8bf22859b51faa09d22c056fe221a45d2f7a3b8
+Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
+[adjust for kernel 5.4, add myloader.c to patch]
+Signed-off-by: Adrian Schmutzler <freifunk@adrianschmutzler.de>
+---
+ drivers/mtd/parsers/Kconfig    |  16 +++
+ drivers/mtd/parsers/Makefile   |   1 +
+ drivers/mtd/parsers/myloader.c | 181 +++++++++++++++++++++++++++++++++
+ 3 files changed, 198 insertions(+)
+ create mode 100644 drivers/mtd/parsers/myloader.c
+
+diff --git a/drivers/mtd/parsers/Kconfig b/drivers/mtd/parsers/Kconfig
+index 23763d16e4f9..a3f344c27454 100644
+--- a/drivers/mtd/parsers/Kconfig
++++ b/drivers/mtd/parsers/Kconfig
+@@ -57,6 +57,22 @@ config MTD_CMDLINE_PARTS
+ 
+ 	  If unsure, say 'N'.
+ 
++config MTD_MYLOADER_PARTS
++	tristate "MyLoader partition parsing"
++	depends on ADM5120 || ATH25 || ATH79
++	help
++	  MyLoader is a bootloader which allows the user to define partitions
++	  in flash devices, by putting a table in the second erase block
++	  on the device, similar to a partition table. This table gives the 
++	  offsets and lengths of the user defined partitions.
++
++	  If you need code which can detect and parse these tables, and
++	  register MTD 'partitions' corresponding to each image detected,
++	  enable this option.
++
++	  You will still need the parsing functions to be called by the driver
++	  for your particular device. It won't happen automatically.
++
+ config MTD_OF_PARTS
+ 	tristate "OpenFirmware (device tree) partitioning parser"
+ 	default y
+diff --git a/drivers/mtd/parsers/Makefile b/drivers/mtd/parsers/Makefile
+index 2e98aa048278..fbce2f3206df 100644
+--- a/drivers/mtd/parsers/Makefile
++++ b/drivers/mtd/parsers/Makefile
+@@ -3,6 +3,7 @@ obj-$(CONFIG_MTD_AR7_PARTS)		+= ar7part.o
+ obj-$(CONFIG_MTD_BCM47XX_PARTS)		+= bcm47xxpart.o
+ obj-$(CONFIG_MTD_BCM63XX_PARTS)		+= bcm63xxpart.o
+ obj-$(CONFIG_MTD_CMDLINE_PARTS)		+= cmdlinepart.o
++obj-$(CONFIG_MTD_MYLOADER_PARTS)		+= myloader.o
+ obj-$(CONFIG_MTD_OF_PARTS)		+= ofpart.o
+ ofpart-y				+= ofpart_core.o
+ ofpart-$(CONFIG_MTD_OF_PARTS_BCM4908)	+= ofpart_bcm4908.o
+diff --git a/drivers/mtd/parsers/myloader.c b/drivers/mtd/parsers/myloader.c
+new file mode 100644
+index 000000000000..50ed8b197ed2
+--- /dev/null
++++ b/drivers/mtd/parsers/myloader.c
+@@ -0,0 +1,181 @@
++/*
++ *  Parse MyLoader-style flash partition tables and produce a Linux partition
++ *  array to match.
++ *
++ *  Copyright (C) 2007-2009 Gabor Juhos <juhosg@openwrt.org>
++ *
++ *  This file was based on drivers/mtd/redboot.c
++ *  Author: Red Hat, Inc. - David Woodhouse <dwmw2@cambridge.redhat.com>
++ *
++ *  This program is free software; you can redistribute it and/or modify it
++ *  under the terms of the GNU General Public License version 2 as published
++ *  by the Free Software Foundation.
++ */
++
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/version.h>
++#include <linux/slab.h>
++#include <linux/init.h>
++#include <linux/vmalloc.h>
++#include <linux/mtd/mtd.h>
++#include <linux/mtd/partitions.h>
++#include <linux/byteorder/generic.h>
++#include <linux/myloader.h>
++
++#define BLOCK_LEN_MIN		0x10000
++#define PART_NAME_LEN		32
++
++struct part_data {
++	struct mylo_partition_table	tab;
++	char names[MYLO_MAX_PARTITIONS][PART_NAME_LEN];
++};
++
++static int myloader_parse_partitions(struct mtd_info *master,
++				     const struct mtd_partition **pparts,
++				     struct mtd_part_parser_data *data)
++{
++	struct part_data *buf;
++	struct mylo_partition_table *tab;
++	struct mylo_partition *part;
++	struct mtd_partition *mtd_parts;
++	struct mtd_partition *mtd_part;
++	int num_parts;
++	int ret, i;
++	size_t retlen;
++	char *names;
++	unsigned long offset;
++	unsigned long blocklen;
++
++	buf = vmalloc(sizeof(*buf));
++	if (!buf) {
++		return -ENOMEM;
++		goto out;
++	}
++	tab = &buf->tab;
++
++	blocklen = master->erasesize;
++	if (blocklen < BLOCK_LEN_MIN)
++		blocklen = BLOCK_LEN_MIN;
++
++	offset = blocklen;
++
++	/* Find the partition table */
++	for (i = 0; i < 4; i++, offset += blocklen) {
++		printk(KERN_DEBUG "%s: searching for MyLoader partition table"
++				" at offset 0x%lx\n", master->name, offset);
++
++		ret = mtd_read(master, offset, sizeof(*buf), &retlen,
++			       (void *)buf);
++		if (ret)
++			goto out_free_buf;
++
++		if (retlen != sizeof(*buf)) {
++			ret = -EIO;
++			goto out_free_buf;
++		}
++
++		/* Check for Partition Table magic number */
++		if (tab->magic == le32_to_cpu(MYLO_MAGIC_PARTITIONS))
++			break;
++
++	}
++
++	if (tab->magic != le32_to_cpu(MYLO_MAGIC_PARTITIONS)) {
++		printk(KERN_DEBUG "%s: no MyLoader partition table found\n",
++			master->name);
++		ret = 0;
++		goto out_free_buf;
++	}
++
++	/* The MyLoader and the Partition Table is always present */
++	num_parts = 2;
++
++	/* Detect number of used partitions */
++	for (i = 0; i < MYLO_MAX_PARTITIONS; i++) {
++		part = &tab->partitions[i];
++
++		if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE)
++			continue;
++
++		num_parts++;
++	}
++
++	mtd_parts = kzalloc((num_parts * sizeof(*mtd_part) +
++				num_parts * PART_NAME_LEN), GFP_KERNEL);
++
++	if (!mtd_parts) {
++		ret = -ENOMEM;
++		goto out_free_buf;
++	}
++
++	mtd_part = mtd_parts;
++	names = (char *)&mtd_parts[num_parts];
++
++	strncpy(names, "myloader", PART_NAME_LEN);
++	mtd_part->name = names;
++	mtd_part->offset = 0;
++	mtd_part->size = offset;
++	mtd_part->mask_flags = MTD_WRITEABLE;
++	mtd_part++;
++	names += PART_NAME_LEN;
++
++	strncpy(names, "partition_table", PART_NAME_LEN);
++	mtd_part->name = names;
++	mtd_part->offset = offset;
++	mtd_part->size = blocklen;
++	mtd_part->mask_flags = MTD_WRITEABLE;
++	mtd_part++;
++	names += PART_NAME_LEN;
++
++	for (i = 0; i < MYLO_MAX_PARTITIONS; i++) {
++		part = &tab->partitions[i];
++
++		if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE)
++			continue;
++
++		if ((buf->names[i][0]) && (buf->names[i][0] != '\xff'))
++			strncpy(names, buf->names[i], PART_NAME_LEN);
++		else
++			snprintf(names, PART_NAME_LEN, "partition%d", i);
++
++		mtd_part->offset = le32_to_cpu(part->addr);
++		mtd_part->size = le32_to_cpu(part->size);
++		mtd_part->name = names;
++		mtd_part++;
++		names += PART_NAME_LEN;
++	}
++
++	*pparts = mtd_parts;
++	ret = num_parts;
++
++ out_free_buf:
++	vfree(buf);
++ out:
++	return ret;
++}
++
++static struct mtd_part_parser myloader_mtd_parser = {
++	.owner		= THIS_MODULE,
++	.parse_fn	= myloader_parse_partitions,
++	.name		= "MyLoader",
++};
++
++static int __init myloader_mtd_parser_init(void)
++{
++	register_mtd_parser(&myloader_mtd_parser);
++
++	return 0;
++}
++
++static void __exit myloader_mtd_parser_exit(void)
++{
++	deregister_mtd_parser(&myloader_mtd_parser);
++}
++
++module_init(myloader_mtd_parser_init);
++module_exit(myloader_mtd_parser_exit);
++
++MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");
++MODULE_DESCRIPTION("Parsing code for MyLoader partition tables");
++MODULE_LICENSE("GPL v2");
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0048-mtd-bcm47xxpart-check-for-bad-blocks-when-calculatin.patch b/nixos/modules/kernel-patches/0048-mtd-bcm47xxpart-check-for-bad-blocks-when-calculatin.patch
new file mode 100644
index 0000000..1bb5ae6
--- /dev/null
+++ b/nixos/modules/kernel-patches/0048-mtd-bcm47xxpart-check-for-bad-blocks-when-calculatin.patch
@@ -0,0 +1,81 @@
+From 73f6ab674b17ed3dc907c2ff04f0a934c90519ee Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <zajec5@gmail.com>
+Date: Tue, 27 Sep 2022 16:21:36 +0200
+Subject: [PATCH 48/96] mtd: bcm47xxpart: check for bad blocks when calculating
+ offsets
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Rafał Miłecki <zajec5@gmail.com>
+---
+ drivers/mtd/parsers/parser_trx.c | 35 ++++++++++++++++++++++++++++----
+ 1 file changed, 31 insertions(+), 4 deletions(-)
+
+diff --git a/drivers/mtd/parsers/parser_trx.c b/drivers/mtd/parsers/parser_trx.c
+index 4814cf218e17..a1aed25d433f 100644
+--- a/drivers/mtd/parsers/parser_trx.c
++++ b/drivers/mtd/parsers/parser_trx.c
+@@ -25,6 +25,33 @@ struct trx_header {
+ 	uint32_t offset[3];
+ } __packed;
+ 
++/*
++ * Calculate real end offset (address) for a given amount of data. It checks
++ * all blocks skipping bad ones.
++ */
++static size_t parser_trx_real_offset(struct mtd_info *mtd, size_t bytes)
++{
++	size_t real_offset = 0;
++
++	if (mtd_block_isbad(mtd, real_offset))
++		pr_warn("Base offset shouldn't be at bad block");
++
++	while (bytes >= mtd->erasesize) {
++		bytes -= mtd->erasesize;
++		real_offset += mtd->erasesize;
++		while (mtd_block_isbad(mtd, real_offset)) {
++			real_offset += mtd->erasesize;
++
++			if (real_offset >= mtd->size)
++				return real_offset - mtd->erasesize;
++		}
++	}
++
++	real_offset += bytes;
++
++	return real_offset;
++}
++
+ static const char *parser_trx_data_part_name(struct mtd_info *master,
+ 					     size_t offset)
+ {
+@@ -86,21 +113,21 @@ static int parser_trx_parse(struct mtd_info *mtd,
+ 	if (trx.offset[2]) {
+ 		part = &parts[curr_part++];
+ 		part->name = "loader";
+-		part->offset = trx.offset[i];
++		part->offset = parser_trx_real_offset(mtd, trx.offset[i]);
+ 		i++;
+ 	}
+ 
+ 	if (trx.offset[i]) {
+ 		part = &parts[curr_part++];
+ 		part->name = "linux";
+-		part->offset = trx.offset[i];
++		part->offset = parser_trx_real_offset(mtd, trx.offset[i]);
+ 		i++;
+ 	}
+ 
+ 	if (trx.offset[i]) {
+ 		part = &parts[curr_part++];
+-		part->name = parser_trx_data_part_name(mtd, trx.offset[i]);
+-		part->offset = trx.offset[i];
++		part->offset = parser_trx_real_offset(mtd, trx.offset[i]);
++		part->name = parser_trx_data_part_name(mtd, part->offset);
+ 		i++;
+ 	}
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0049-mtd-bcm47xxpart-detect-T_Meter-partition.patch b/nixos/modules/kernel-patches/0049-mtd-bcm47xxpart-detect-T_Meter-partition.patch
new file mode 100644
index 0000000..eda2b10
--- /dev/null
+++ b/nixos/modules/kernel-patches/0049-mtd-bcm47xxpart-detect-T_Meter-partition.patch
@@ -0,0 +1,47 @@
+From 80a6d73ee374e5c0034434881b0bc9b118ad381f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <zajec5@gmail.com>
+Date: Tue, 27 Sep 2022 16:21:36 +0200
+Subject: [PATCH 49/96] mtd: bcm47xxpart: detect T_Meter partition
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+It can be found on many Netgear devices. It consists of many 0x30 blocks
+starting with 4D 54.
+
+Signed-off-by: Rafał Miłecki <zajec5@gmail.com>
+---
+ drivers/mtd/parsers/bcm47xxpart.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/drivers/mtd/parsers/bcm47xxpart.c b/drivers/mtd/parsers/bcm47xxpart.c
+index 50fcf4c2174b..045954f7ddc2 100644
+--- a/drivers/mtd/parsers/bcm47xxpart.c
++++ b/drivers/mtd/parsers/bcm47xxpart.c
+@@ -35,6 +35,7 @@
+ #define NVRAM_HEADER			0x48534C46	/* FLSH */
+ #define POT_MAGIC1			0x54544f50	/* POTT */
+ #define POT_MAGIC2			0x504f		/* OP */
++#define T_METER_MAGIC			0x4D540000	/* MT */
+ #define ML_MAGIC1			0x39685a42
+ #define ML_MAGIC2			0x26594131
+ #define TRX_MAGIC			0x30524448
+@@ -179,6 +180,15 @@ static int bcm47xxpart_parse(struct mtd_info *master,
+ 			continue;
+ 		}
+ 
++		/* T_Meter */
++		if ((le32_to_cpu(buf[0x000 / 4]) & 0xFFFF0000) == T_METER_MAGIC &&
++		    (le32_to_cpu(buf[0x030 / 4]) & 0xFFFF0000) == T_METER_MAGIC &&
++		    (le32_to_cpu(buf[0x060 / 4]) & 0xFFFF0000) == T_METER_MAGIC) {
++			bcm47xxpart_add_part(&parts[curr_part++], "T_Meter", offset,
++					     MTD_WRITEABLE);
++			continue;
++		}
++
+ 		/* TRX */
+ 		if (buf[0x000 / 4] == TRX_MAGIC) {
+ 			struct trx_header *trx;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0050-kernel-disable-cfi-cmdset-0002-erase-suspend.patch b/nixos/modules/kernel-patches/0050-kernel-disable-cfi-cmdset-0002-erase-suspend.patch
new file mode 100644
index 0000000..f285a4d
--- /dev/null
+++ b/nixos/modules/kernel-patches/0050-kernel-disable-cfi-cmdset-0002-erase-suspend.patch
@@ -0,0 +1,32 @@
+From 0b665a6c06dc23540d79f36704335d1063351f65 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 27 Sep 2022 16:21:39 +0200
+Subject: [PATCH 50/96] kernel: disable cfi cmdset 0002 erase suspend
+
+on some platforms, erase suspend leads to data corruption and lockups when write
+ops collide with erase ops. this has been observed on the buffalo wzr-hp-g300nh.
+rather than play whack-a-mole with a hard to reproduce issue on a variety of devices,
+simply disable erase suspend, as it will usually not produce any useful gain on
+the small filesystems used on embedded hardware.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ drivers/mtd/chips/cfi_cmdset_0002.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c
+index 67453f59c69c..a6692d72f24b 100644
+--- a/drivers/mtd/chips/cfi_cmdset_0002.c
++++ b/drivers/mtd/chips/cfi_cmdset_0002.c
+@@ -908,7 +908,7 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
+ 		return 0;
+ 
+ 	case FL_ERASING:
+-		if (!cfip || !(cfip->EraseSuspend & (0x1|0x2)) ||
++		if (1 /* no suspend */ || !cfip || !(cfip->EraseSuspend & (0x1|0x2)) ||
+ 		    !(mode == FL_READY || mode == FL_POINT ||
+ 		    (mode == FL_WRITING && (cfip->EraseSuspend & 0x2))))
+ 			goto sleep;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0051-Issue-map-read-after-Write-Buffer-Load-command-to-en.patch b/nixos/modules/kernel-patches/0051-Issue-map-read-after-Write-Buffer-Load-command-to-en.patch
new file mode 100644
index 0000000..c0faf03
--- /dev/null
+++ b/nixos/modules/kernel-patches/0051-Issue-map-read-after-Write-Buffer-Load-command-to-en.patch
@@ -0,0 +1,26 @@
+From 4575feb6cb9d44a15acdb66dba1d0e7f2b6f2c9e Mon Sep 17 00:00:00 2001
+From: George Kashperko <george@znau.edu.ua>
+Date: Tue, 27 Sep 2022 16:21:39 +0200
+Subject: [PATCH 51/96] Issue map read after Write Buffer Load command to
+ ensure chip is ready to receive data.
+
+Signed-off-by: George Kashperko <george@znau.edu.ua>
+---
+ drivers/mtd/chips/cfi_cmdset_0002.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c
+index a6692d72f24b..13694138a8b8 100644
+--- a/drivers/mtd/chips/cfi_cmdset_0002.c
++++ b/drivers/mtd/chips/cfi_cmdset_0002.c
+@@ -2052,6 +2052,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip,
+ 
+ 	/* Write Buffer Load */
+ 	map_write(map, CMD(0x25), cmd_adr);
++	(void) map_read(map, cmd_adr);
+ 
+ 	chip->state = FL_WRITING_TO_BUFFER;
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0052-mtd-spinand-add-support-for-ESMT-F50x1G41LB.patch b/nixos/modules/kernel-patches/0052-mtd-spinand-add-support-for-ESMT-F50x1G41LB.patch
new file mode 100644
index 0000000..aa8d4ff
--- /dev/null
+++ b/nixos/modules/kernel-patches/0052-mtd-spinand-add-support-for-ESMT-F50x1G41LB.patch
@@ -0,0 +1,155 @@
+From 212a7751ee6e76db714bb1dc50c80aa21c0ce871 Mon Sep 17 00:00:00 2001
+From: Chuanhong Guo <gch981213@gmail.com>
+Date: Wed, 13 Apr 2022 11:58:17 +0800
+Subject: [PATCH 52/96] mtd: spinand: add support for ESMT F50x1G41LB
+
+This patch adds support for ESMT F50L1G41LB and F50D1G41LB.
+It seems that ESMT likes to use random JEDEC ID from other vendors.
+Their 1G chips uses 0xc8 from GigaDevice and 2G/4G chips uses 0x2c from
+Micron. For this reason, the ESMT entry is named esmt_c8 with explicit
+JEDEC ID in variable name.
+
+Datasheets:
+https://www.esmt.com.tw/upload/pdf/ESMT/datasheets/F50L1G41LB(2M).pdf
+https://www.esmt.com.tw/upload/pdf/ESMT/datasheets/F50D1G41LB(2M).pdf
+
+Signed-off-by: Chuanhong Guo <gch981213@gmail.com>
+---
+ drivers/mtd/nand/spi/Makefile |  2 +-
+ drivers/mtd/nand/spi/core.c   |  1 +
+ drivers/mtd/nand/spi/esmt.c   | 89 +++++++++++++++++++++++++++++++++++
+ include/linux/mtd/spinand.h   |  1 +
+ 4 files changed, 92 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/mtd/nand/spi/esmt.c
+
+diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
+index 80dabe6ff0f3..9c64d9fcb189 100644
+--- a/drivers/mtd/nand/spi/Makefile
++++ b/drivers/mtd/nand/spi/Makefile
+@@ -1,3 +1,3 @@
+ # SPDX-License-Identifier: GPL-2.0
+-spinand-objs := core.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o xtx.o
++spinand-objs := core.o esmt.o gigadevice.o macronix.o micron.o paragon.o toshiba.o winbond.o xtx.o
+ obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
+diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
+index d5b685d1605e..f9fd111f3364 100644
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -927,6 +927,7 @@ static const struct nand_ops spinand_ops = {
+ };
+ 
+ static const struct spinand_manufacturer *spinand_manufacturers[] = {
++	&esmt_c8_spinand_manufacturer,
+ 	&gigadevice_spinand_manufacturer,
+ 	&macronix_spinand_manufacturer,
+ 	&micron_spinand_manufacturer,
+diff --git a/drivers/mtd/nand/spi/esmt.c b/drivers/mtd/nand/spi/esmt.c
+new file mode 100644
+index 000000000000..dad0ba60bb79
+--- /dev/null
++++ b/drivers/mtd/nand/spi/esmt.c
+@@ -0,0 +1,89 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Author:
++ *	Chuanhong Guo <gch981213@gmail.com>
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include <linux/mtd/spinand.h>
++
++/* ESMT uses GigaDevice 0xc8 JECDEC ID on some SPI NANDs */
++#define SPINAND_MFR_ESMT_C8			0xc8
++
++static SPINAND_OP_VARIANTS(read_cache_variants,
++			   SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
++			   SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
++			   SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
++			   SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
++			   SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
++			   SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
++
++static SPINAND_OP_VARIANTS(write_cache_variants,
++			   SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
++			   SPINAND_PROG_LOAD(true, 0, NULL, 0));
++
++static SPINAND_OP_VARIANTS(update_cache_variants,
++			   SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
++			   SPINAND_PROG_LOAD(false, 0, NULL, 0));
++
++static int f50l1g41lb_ooblayout_ecc(struct mtd_info *mtd, int section,
++				    struct mtd_oob_region *region)
++{
++	if (section > 3)
++		return -ERANGE;
++
++	region->offset = 16 * section + 8;
++	region->length = 8;
++
++	return 0;
++}
++
++static int f50l1g41lb_ooblayout_free(struct mtd_info *mtd, int section,
++				     struct mtd_oob_region *region)
++{
++	if (section > 3)
++		return -ERANGE;
++
++	region->offset = 16 * section + 2;
++	region->length = 6;
++
++	return 0;
++}
++
++static const struct mtd_ooblayout_ops f50l1g41lb_ooblayout = {
++	.ecc = f50l1g41lb_ooblayout_ecc,
++	.free = f50l1g41lb_ooblayout_free,
++};
++
++static const struct spinand_info esmt_c8_spinand_table[] = {
++	SPINAND_INFO("F50L1G41LB",
++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x01),
++		     NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
++		     NAND_ECCREQ(1, 512),
++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++					      &write_cache_variants,
++					      &update_cache_variants),
++		     0,
++		     SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL)),
++	SPINAND_INFO("F50D1G41LB",
++		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x11),
++		     NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
++		     NAND_ECCREQ(1, 512),
++		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
++					      &write_cache_variants,
++					      &update_cache_variants),
++		     0,
++		     SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL)),
++};
++
++static const struct spinand_manufacturer_ops esmt_spinand_manuf_ops = {
++};
++
++const struct spinand_manufacturer esmt_c8_spinand_manufacturer = {
++	.id = SPINAND_MFR_ESMT_C8,
++	.name = "ESMT",
++	.chips = esmt_c8_spinand_table,
++	.nchips = ARRAY_SIZE(esmt_c8_spinand_table),
++	.ops = &esmt_spinand_manuf_ops,
++};
+diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
+index 5584d3bb6556..3b42e7392226 100644
+--- a/include/linux/mtd/spinand.h
++++ b/include/linux/mtd/spinand.h
+@@ -260,6 +260,7 @@ struct spinand_manufacturer {
+ };
+ 
+ /* SPI NAND manufacturers */
++extern const struct spinand_manufacturer esmt_c8_spinand_manufacturer;
+ extern const struct spinand_manufacturer gigadevice_spinand_manufacturer;
+ extern const struct spinand_manufacturer macronix_spinand_manufacturer;
+ extern const struct spinand_manufacturer micron_spinand_manufacturer;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0053-mtd-add-EOF-marker-support-to-the-UBI-layer.patch b/nixos/modules/kernel-patches/0053-mtd-add-EOF-marker-support-to-the-UBI-layer.patch
new file mode 100644
index 0000000..165106b
--- /dev/null
+++ b/nixos/modules/kernel-patches/0053-mtd-add-EOF-marker-support-to-the-UBI-layer.patch
@@ -0,0 +1,69 @@
+From 799bf4a0592b64708a5cb61b092f4b6a27cae435 Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <juhosg@openwrt.org>
+Date: Tue, 27 Sep 2022 16:22:01 +0200
+Subject: [PATCH 53/96] mtd: add EOF marker support to the UBI layer
+
+Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
+---
+ drivers/mtd/ubi/attach.c | 25 ++++++++++++++++++++++---
+ drivers/mtd/ubi/ubi.h    |  1 +
+ 2 files changed, 23 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c
+index ae5abe492b52..7a16e0e252a7 100644
+--- a/drivers/mtd/ubi/attach.c
++++ b/drivers/mtd/ubi/attach.c
+@@ -926,6 +926,13 @@ static bool vol_ignored(int vol_id)
+ #endif
+ }
+ 
++static bool ec_hdr_has_eof(struct ubi_ec_hdr *ech)
++{
++	return ech->padding1[0] == 'E' &&
++	       ech->padding1[1] == 'O' &&
++	       ech->padding1[2] == 'F';
++}
++
+ /**
+  * scan_peb - scan and process UBI headers of a PEB.
+  * @ubi: UBI device description object
+@@ -958,9 +965,21 @@ static int scan_peb(struct ubi_device *ubi, struct ubi_attach_info *ai,
+ 		return 0;
+ 	}
+ 
+-	err = ubi_io_read_ec_hdr(ubi, pnum, ech, 0);
+-	if (err < 0)
+-		return err;
++	if (!ai->eof_found) {
++		err = ubi_io_read_ec_hdr(ubi, pnum, ech, 0);
++		if (err < 0)
++			return err;
++
++		if (ec_hdr_has_eof(ech)) {
++			pr_notice("UBI: EOF marker found, PEBs from %d will be erased\n",
++				pnum);
++			ai->eof_found = true;
++		}
++	}
++
++	if (ai->eof_found)
++		err = UBI_IO_FF_BITFLIPS;
++
+ 	switch (err) {
+ 	case 0:
+ 		break;
+diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h
+index 078112e23dfd..6f39e47b4869 100644
+--- a/drivers/mtd/ubi/ubi.h
++++ b/drivers/mtd/ubi/ubi.h
+@@ -778,6 +778,7 @@ struct ubi_attach_info {
+ 	int mean_ec;
+ 	uint64_t ec_sum;
+ 	int ec_count;
++	bool eof_found;
+ 	struct kmem_cache *aeb_slab_cache;
+ 	struct ubi_ec_hdr *ech;
+ 	struct ubi_vid_io_buf *vidb;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0054-mtd-core-add-get_mtd_device_by_node.patch b/nixos/modules/kernel-patches/0054-mtd-core-add-get_mtd_device_by_node.patch
new file mode 100644
index 0000000..6a57d72
--- /dev/null
+++ b/nixos/modules/kernel-patches/0054-mtd-core-add-get_mtd_device_by_node.patch
@@ -0,0 +1,82 @@
+From 1065fc1f12c66020a9e49a9951a7ba39112bf405 Mon Sep 17 00:00:00 2001
+From: Bernhard Frauendienst <kernel@nospam.obeliks.de>
+Date: Sat, 1 Sep 2018 00:30:11 +0200
+Subject: [PATCH 54/96] mtd: core: add get_mtd_device_by_node
+
+Add function to retrieve a mtd device by its OF node. Since drivers can
+assign arbitrary names to mtd devices in the absence of a label
+property, there is no other reliable way to retrieve a mtd device for a
+given OF node.
+
+Signed-off-by: Bernhard Frauendienst <kernel@nospam.obeliks.de>
+Reviewed-by: Miquel Raynal <miquel.raynal@bootlin.com>
+---
+ drivers/mtd/mtdcore.c   | 38 ++++++++++++++++++++++++++++++++++++++
+ include/linux/mtd/mtd.h |  2 ++
+ 2 files changed, 40 insertions(+)
+
+diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
+index ccb80197d960..c8f8da3f3736 100644
+--- a/drivers/mtd/mtdcore.c
++++ b/drivers/mtd/mtdcore.c
+@@ -1202,6 +1202,44 @@ struct mtd_info *get_mtd_device_nm(const char *name)
+ }
+ EXPORT_SYMBOL_GPL(get_mtd_device_nm);
+ 
++/**
++ *	get_mtd_device_by_node - obtain a validated handle for an MTD device
++ *	by of_node
++ *	@of_node: OF node of MTD device to open
++ *
++ *	This function returns MTD device description structure in case of
++ *	success and an error code in case of failure.
++ */
++struct mtd_info *get_mtd_device_by_node(const struct device_node *of_node)
++{
++	int err = -ENODEV;
++	struct mtd_info *mtd = NULL, *other;
++
++	mutex_lock(&mtd_table_mutex);
++
++	mtd_for_each_device(other) {
++		if (of_node == other->dev.of_node) {
++			mtd = other;
++			break;
++		}
++	}
++
++	if (!mtd)
++		goto out_unlock;
++
++	err = __get_mtd_device(mtd);
++	if (err)
++		goto out_unlock;
++
++	mutex_unlock(&mtd_table_mutex);
++	return mtd;
++
++out_unlock:
++	mutex_unlock(&mtd_table_mutex);
++	return ERR_PTR(err);
++}
++EXPORT_SYMBOL_GPL(get_mtd_device_by_node);
++
+ void put_mtd_device(struct mtd_info *mtd)
+ {
+ 	mutex_lock(&mtd_table_mutex);
+diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
+index cd7807a23123..3ec4f745f1b4 100644
+--- a/include/linux/mtd/mtd.h
++++ b/include/linux/mtd/mtd.h
+@@ -698,6 +698,8 @@ extern struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num);
+ extern int __get_mtd_device(struct mtd_info *mtd);
+ extern void __put_mtd_device(struct mtd_info *mtd);
+ extern struct mtd_info *get_mtd_device_nm(const char *name);
++extern struct mtd_info *get_mtd_device_by_node(
++		const struct device_node *of_node);
+ extern void put_mtd_device(struct mtd_info *mtd);
+ 
+ static inline uint64_t mtdpart_get_offset(const struct mtd_info *mtd)
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0055-dt-bindings-add-bindings-for-mtd-concat-devices.patch b/nixos/modules/kernel-patches/0055-dt-bindings-add-bindings-for-mtd-concat-devices.patch
new file mode 100644
index 0000000..b327f77
--- /dev/null
+++ b/nixos/modules/kernel-patches/0055-dt-bindings-add-bindings-for-mtd-concat-devices.patch
@@ -0,0 +1,58 @@
+From b69bbb97eeabeaa1aa6fafcffcf331edd2ec2693 Mon Sep 17 00:00:00 2001
+From: Bernhard Frauendienst <kernel@nospam.obeliks.de>
+Date: Wed, 5 Sep 2018 01:32:51 +0200
+Subject: [PATCH 55/96] dt-bindings: add bindings for mtd-concat devices
+
+Document virtual mtd-concat device bindings.
+
+Signed-off-by: Bernhard Frauendienst <kernel@nospam.obeliks.de>
+---
+ .../devicetree/bindings/mtd/mtd-concat.txt    | 36 +++++++++++++++++++
+ 1 file changed, 36 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/mtd/mtd-concat.txt
+
+diff --git a/Documentation/devicetree/bindings/mtd/mtd-concat.txt b/Documentation/devicetree/bindings/mtd/mtd-concat.txt
+new file mode 100644
+index 000000000000..2daf3157b163
+--- /dev/null
++++ b/Documentation/devicetree/bindings/mtd/mtd-concat.txt
+@@ -0,0 +1,36 @@
++Virtual MTD concat device
++
++Requires properties:
++- devices: list of phandles to mtd nodes that should be concatenated
++
++Example:
++
++&spi {
++	flash0: flash@0 {
++		...
++	};
++	flash1: flash@1 {
++		...
++	};
++};
++
++flash {
++	compatible = "mtd-concat";
++
++	devices = <&flash0 &flash1>;
++
++	partitions {
++		compatible = "fixed-partitions";
++
++		partition@0 {
++			label = "boot";
++			reg = <0x0000000 0x0040000>;
++			read-only;
++		};
++
++		partition@40000 {
++			label = "firmware";
++			reg = <0x0040000 0x1fc0000>;
++		};
++	}
++}
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0056-mtd-mtdconcat-add-dt-driver-for-concat-devices.patch b/nixos/modules/kernel-patches/0056-mtd-mtdconcat-add-dt-driver-for-concat-devices.patch
new file mode 100644
index 0000000..92f5603
--- /dev/null
+++ b/nixos/modules/kernel-patches/0056-mtd-mtdconcat-add-dt-driver-for-concat-devices.patch
@@ -0,0 +1,232 @@
+From 0a58b4681f061bf6a49c7102b08359c3ea19d84f Mon Sep 17 00:00:00 2001
+From: Bernhard Frauendienst <kernel@nospam.obeliks.de>
+Date: Sat, 25 Aug 2018 12:35:22 +0200
+Subject: [PATCH 56/96] mtd: mtdconcat: add dt driver for concat devices
+
+Some mtd drivers like physmap variants have support for concatenating
+multiple mtd devices, but there is no generic way to define such a
+concat device from within the device tree.
+
+This is useful for some SoC boards that use multiple flash chips as
+memory banks of a single mtd device, with partitions spanning chip
+borders.
+
+This commit adds a driver for creating virtual mtd-concat devices. They
+must have a compatible = "mtd-concat" line, and define a list of devices
+to concat in the 'devices' property, for example:
+
+flash {
+  compatible = "mtd-concat";
+
+  devices = <&flash0 &flash1>;
+
+  partitions {
+    ...
+  };
+};
+
+The driver is added to the very end of the mtd Makefile to increase the
+likelyhood of all child devices already being loaded at the time of
+probing, preventing unnecessary deferred probes.
+
+Signed-off-by: Bernhard Frauendienst <kernel@nospam.obeliks.de>
+---
+ drivers/mtd/Kconfig                 |   2 +
+ drivers/mtd/Makefile                |   3 +
+ drivers/mtd/composite/Kconfig       |  12 +++
+ drivers/mtd/composite/Makefile      |   6 ++
+ drivers/mtd/composite/virt_concat.c | 128 ++++++++++++++++++++++++++++
+ 5 files changed, 151 insertions(+)
+ create mode 100644 drivers/mtd/composite/Kconfig
+ create mode 100644 drivers/mtd/composite/Makefile
+ create mode 100644 drivers/mtd/composite/virt_concat.c
+
+diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
+index f9ed93c4cf0f..7e4a3fa1dfd0 100644
+--- a/drivers/mtd/Kconfig
++++ b/drivers/mtd/Kconfig
+@@ -241,4 +241,6 @@ source "drivers/mtd/ubi/Kconfig"
+ 
+ source "drivers/mtd/hyperbus/Kconfig"
+ 
++source "drivers/mtd/composite/Kconfig"
++
+ endif # MTD
+diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
+index b14b7fe0f597..fbea3736b488 100644
+--- a/drivers/mtd/Makefile
++++ b/drivers/mtd/Makefile
+@@ -33,3 +33,6 @@ obj-y		+= chips/ lpddr/ maps/ devices/ nand/ tests/
+ obj-$(CONFIG_MTD_SPI_NOR)	+= spi-nor/
+ obj-$(CONFIG_MTD_UBI)		+= ubi/
+ obj-$(CONFIG_MTD_HYPERBUS)	+= hyperbus/
++
++# Composite drivers must be loaded last
++obj-y				+= composite/
+diff --git a/drivers/mtd/composite/Kconfig b/drivers/mtd/composite/Kconfig
+new file mode 100644
+index 000000000000..0490fc0284bb
+--- /dev/null
++++ b/drivers/mtd/composite/Kconfig
+@@ -0,0 +1,12 @@
++menu "Composite MTD device drivers"
++	depends on MTD!=n
++
++config MTD_VIRT_CONCAT
++	tristate "Virtual concat MTD device"
++	help
++	  This driver allows creation of a virtual MTD concat device, which
++	  concatenates multiple underlying MTD devices to a single device.
++	  This is required by some SoC boards where multiple memory banks are
++	  used as one device with partitions spanning across device boundaries.
++
++endmenu
+diff --git a/drivers/mtd/composite/Makefile b/drivers/mtd/composite/Makefile
+new file mode 100644
+index 000000000000..8421a0a30606
+--- /dev/null
++++ b/drivers/mtd/composite/Makefile
+@@ -0,0 +1,6 @@
++# SPDX-License-Identifier: GPL-2.0
++#
++# linux/drivers/mtd/composite/Makefile
++#
++
++obj-$(CONFIG_MTD_VIRT_CONCAT)   += virt_concat.o
+diff --git a/drivers/mtd/composite/virt_concat.c b/drivers/mtd/composite/virt_concat.c
+new file mode 100644
+index 000000000000..bfd432188c35
+--- /dev/null
++++ b/drivers/mtd/composite/virt_concat.c
+@@ -0,0 +1,128 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Virtual concat MTD device driver
++ *
++ * Copyright (C) 2018 Bernhard Frauendienst
++ * Author: Bernhard Frauendienst, kernel@nospam.obeliks.de
++ */
++
++#include <linux/module.h>
++#include <linux/device.h>
++#include <linux/mtd/concat.h>
++#include <linux/mtd/mtd.h>
++#include <linux/mtd/partitions.h>
++#include <linux/of.h>
++#include <linux/of_platform.h>
++#include <linux/slab.h>
++
++/*
++ * struct of_virt_concat - platform device driver data.
++ * @cmtd the final mtd_concat device
++ * @num_devices the number of devices in @devices
++ * @devices points to an array of devices already loaded
++ */
++struct of_virt_concat {
++	struct mtd_info	*cmtd;
++	int num_devices;
++	struct mtd_info	**devices;
++};
++
++static int virt_concat_remove(struct platform_device *pdev)
++{
++	struct of_virt_concat *info;
++	int i;
++
++	info = platform_get_drvdata(pdev);
++	if (!info)
++		return 0;
++
++	// unset data for when this is called after a probe error
++	platform_set_drvdata(pdev, NULL);
++
++	if (info->cmtd) {
++		mtd_device_unregister(info->cmtd);
++		mtd_concat_destroy(info->cmtd);
++	}
++
++	if (info->devices) {
++		for (i = 0; i < info->num_devices; i++)
++			put_mtd_device(info->devices[i]);
++	}
++
++	return 0;
++}
++
++static int virt_concat_probe(struct platform_device *pdev)
++{
++	struct device_node *node = pdev->dev.of_node;
++	struct of_phandle_iterator it;
++	struct of_virt_concat *info;
++	struct mtd_info *mtd;
++	int err = 0, count;
++
++	count = of_count_phandle_with_args(node, "devices", NULL);
++	if (count <= 0)
++		return -EINVAL;
++
++	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
++	if (!info)
++		return -ENOMEM;
++	info->devices = devm_kcalloc(&pdev->dev, count,
++				     sizeof(*(info->devices)), GFP_KERNEL);
++	if (!info->devices) {
++		err = -ENOMEM;
++		goto err_remove;
++	}
++
++	platform_set_drvdata(pdev, info);
++
++	of_for_each_phandle(&it, err, node, "devices", NULL, 0) {
++		mtd = get_mtd_device_by_node(it.node);
++		if (IS_ERR(mtd)) {
++			of_node_put(it.node);
++			err = -EPROBE_DEFER;
++			goto err_remove;
++		}
++
++		info->devices[info->num_devices++] = mtd;
++	}
++
++	info->cmtd = mtd_concat_create(info->devices, info->num_devices,
++				       dev_name(&pdev->dev));
++	if (!info->cmtd) {
++		err = -ENXIO;
++		goto err_remove;
++	}
++
++	info->cmtd->dev.parent = &pdev->dev;
++	mtd_set_of_node(info->cmtd, node);
++	mtd_device_register(info->cmtd, NULL, 0);
++
++	return 0;
++
++err_remove:
++	virt_concat_remove(pdev);
++
++	return err;
++}
++
++static const struct of_device_id virt_concat_of_match[] = {
++	{ .compatible = "mtd-concat", },
++	{ /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, virt_concat_of_match);
++
++static struct platform_driver virt_concat_driver = {
++	.probe = virt_concat_probe,
++	.remove = virt_concat_remove,
++	.driver	 = {
++		.name   = "virt-mtdconcat",
++		.of_match_table = virt_concat_of_match,
++	},
++};
++
++module_platform_driver(virt_concat_driver);
++
++MODULE_LICENSE("GPL v2");
++MODULE_AUTHOR("Bernhard Frauendienst <kernel@nospam.obeliks.de>");
++MODULE_DESCRIPTION("Virtual concat MTD device driver");
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0057-fs-add-cdrom-dependency.patch b/nixos/modules/kernel-patches/0057-fs-add-cdrom-dependency.patch
new file mode 100644
index 0000000..1a2db45
--- /dev/null
+++ b/nixos/modules/kernel-patches/0057-fs-add-cdrom-dependency.patch
@@ -0,0 +1,63 @@
+From 9284aa002832679a506de26a13378d4364524ca4 Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 12:21:15 +0200
+Subject: [PATCH 57/96] fs: add cdrom dependency
+
+---
+ fs/hfs/Kconfig     | 1 +
+ fs/hfsplus/Kconfig | 1 +
+ fs/isofs/Kconfig   | 1 +
+ fs/udf/Kconfig     | 1 +
+ 4 files changed, 4 insertions(+)
+
+diff --git a/fs/hfs/Kconfig b/fs/hfs/Kconfig
+index 129926b5142d..e0d2c647aa07 100644
+--- a/fs/hfs/Kconfig
++++ b/fs/hfs/Kconfig
+@@ -2,6 +2,7 @@
+ config HFS_FS
+ 	tristate "Apple Macintosh file system support"
+ 	depends on BLOCK
++	select CDROM
+ 	select NLS
+ 	help
+ 	  If you say Y here, you will be able to mount Macintosh-formatted
+diff --git a/fs/hfsplus/Kconfig b/fs/hfsplus/Kconfig
+index 7d4229aecec0..648d91d1881f 100644
+--- a/fs/hfsplus/Kconfig
++++ b/fs/hfsplus/Kconfig
+@@ -2,6 +2,7 @@
+ config HFSPLUS_FS
+ 	tristate "Apple Extended HFS file system support"
+ 	depends on BLOCK
++	select CDROM
+ 	select NLS
+ 	select NLS_UTF8
+ 	help
+diff --git a/fs/isofs/Kconfig b/fs/isofs/Kconfig
+index 08ffd37b9bb8..f74680379207 100644
+--- a/fs/isofs/Kconfig
++++ b/fs/isofs/Kconfig
+@@ -1,6 +1,7 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+ config ISO9660_FS
+ 	tristate "ISO 9660 CDROM file system support"
++	select CDROM
+ 	help
+ 	  This is the standard file system used on CD-ROMs.  It was previously
+ 	  known as "High Sierra File System" and is called "hsfs" on other
+diff --git a/fs/udf/Kconfig b/fs/udf/Kconfig
+index 26e1a49f3ba7..3f85a084d2b5 100644
+--- a/fs/udf/Kconfig
++++ b/fs/udf/Kconfig
+@@ -1,6 +1,7 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+ config UDF_FS
+ 	tristate "UDF file system support"
++	select CDROM
+ 	select CRC_ITU_T
+ 	select NLS
+ 	help
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0058-fs-add-jffs2-lzma-support-not-activated-by-default-y.patch b/nixos/modules/kernel-patches/0058-fs-add-jffs2-lzma-support-not-activated-by-default-y.patch
new file mode 100644
index 0000000..380eb21
--- /dev/null
+++ b/nixos/modules/kernel-patches/0058-fs-add-jffs2-lzma-support-not-activated-by-default-y.patch
@@ -0,0 +1,5235 @@
+From 8bb3779dc3924250dc68185f37038bb0954f268a Mon Sep 17 00:00:00 2001
+From: "Alexandros C. Couloumbis" <alex@ozo.com>
+Date: Tue, 27 Sep 2022 16:22:04 +0200
+Subject: [PATCH 58/96] fs: add jffs2/lzma support (not activated by default
+ yet)
+
+lede-commit: c2c88d315fa0e881f8b19da07b62859b915b11b2
+Signed-off-by: Alexandros C. Couloumbis <alex@ozo.com>
+---
+ fs/jffs2/Kconfig             |    9 +
+ fs/jffs2/Makefile            |    3 +
+ fs/jffs2/compr.c             |    6 +
+ fs/jffs2/compr.h             |   10 +-
+ fs/jffs2/compr_lzma.c        |  128 ++
+ fs/jffs2/super.c             |   33 +-
+ include/linux/lzma.h         |   62 +
+ include/linux/lzma/LzFind.h  |  115 ++
+ include/linux/lzma/LzHash.h  |   54 +
+ include/linux/lzma/LzmaDec.h |  231 ++++
+ include/linux/lzma/LzmaEnc.h |   80 ++
+ include/linux/lzma/Types.h   |  226 ++++
+ include/uapi/linux/jffs2.h   |    1 +
+ lib/Kconfig                  |    6 +
+ lib/Makefile                 |   12 +
+ lib/lzma/LzFind.c            |  761 ++++++++++++
+ lib/lzma/LzmaDec.c           |  999 +++++++++++++++
+ lib/lzma/LzmaEnc.c           | 2271 ++++++++++++++++++++++++++++++++++
+ lib/lzma/Makefile            |    7 +
+ 19 files changed, 5008 insertions(+), 6 deletions(-)
+ create mode 100644 fs/jffs2/compr_lzma.c
+ create mode 100644 include/linux/lzma.h
+ create mode 100644 include/linux/lzma/LzFind.h
+ create mode 100644 include/linux/lzma/LzHash.h
+ create mode 100644 include/linux/lzma/LzmaDec.h
+ create mode 100644 include/linux/lzma/LzmaEnc.h
+ create mode 100644 include/linux/lzma/Types.h
+ create mode 100644 lib/lzma/LzFind.c
+ create mode 100644 lib/lzma/LzmaDec.c
+ create mode 100644 lib/lzma/LzmaEnc.c
+ create mode 100644 lib/lzma/Makefile
+
+diff --git a/fs/jffs2/Kconfig b/fs/jffs2/Kconfig
+index 7c96bc107218..2944e0a912b3 100644
+--- a/fs/jffs2/Kconfig
++++ b/fs/jffs2/Kconfig
+@@ -136,6 +136,15 @@ config JFFS2_LZO
+ 	  This feature was added in July, 2007. Say 'N' if you need
+ 	  compatibility with older bootloaders or kernels.
+ 
++config JFFS2_LZMA
++	bool "JFFS2 LZMA compression support" if JFFS2_COMPRESSION_OPTIONS
++	select LZMA_COMPRESS
++	select LZMA_DECOMPRESS
++	depends on JFFS2_FS
++	default n
++	help
++	  JFFS2 wrapper to the LZMA C SDK
++
+ config JFFS2_RTIME
+ 	bool "JFFS2 RTIME compression support" if JFFS2_COMPRESSION_OPTIONS
+ 	depends on JFFS2_FS
+diff --git a/fs/jffs2/Makefile b/fs/jffs2/Makefile
+index 5294969d5bf9..11a89b96e5f3 100644
+--- a/fs/jffs2/Makefile
++++ b/fs/jffs2/Makefile
+@@ -19,4 +19,7 @@ jffs2-$(CONFIG_JFFS2_RUBIN)	+= compr_rubin.o
+ jffs2-$(CONFIG_JFFS2_RTIME)	+= compr_rtime.o
+ jffs2-$(CONFIG_JFFS2_ZLIB)	+= compr_zlib.o
+ jffs2-$(CONFIG_JFFS2_LZO)	+= compr_lzo.o
++jffs2-$(CONFIG_JFFS2_LZMA)	+= compr_lzma.o
+ jffs2-$(CONFIG_JFFS2_SUMMARY)   += summary.o
++
++CFLAGS_compr_lzma.o += -Iinclude/linux -Ilib/lzma
+diff --git a/fs/jffs2/compr.c b/fs/jffs2/compr.c
+index 4849a4c9a0e2..6ece471685d4 100644
+--- a/fs/jffs2/compr.c
++++ b/fs/jffs2/compr.c
+@@ -378,6 +378,9 @@ int __init jffs2_compressors_init(void)
+ #ifdef CONFIG_JFFS2_LZO
+ 	jffs2_lzo_init();
+ #endif
++#ifdef CONFIG_JFFS2_LZMA
++	jffs2_lzma_init();
++#endif
+ /* Setting default compression mode */
+ #ifdef CONFIG_JFFS2_CMODE_NONE
+ 	jffs2_compression_mode = JFFS2_COMPR_MODE_NONE;
+@@ -401,6 +404,9 @@ int __init jffs2_compressors_init(void)
+ int jffs2_compressors_exit(void)
+ {
+ /* Unregistering compressors */
++#ifdef CONFIG_JFFS2_LZMA
++	jffs2_lzma_exit();
++#endif
+ #ifdef CONFIG_JFFS2_LZO
+ 	jffs2_lzo_exit();
+ #endif
+diff --git a/fs/jffs2/compr.h b/fs/jffs2/compr.h
+index 5e91d578f4ed..32db2e1ec610 100644
+--- a/fs/jffs2/compr.h
++++ b/fs/jffs2/compr.h
+@@ -29,9 +29,9 @@
+ #define JFFS2_DYNRUBIN_PRIORITY  20
+ #define JFFS2_LZARI_PRIORITY     30
+ #define JFFS2_RTIME_PRIORITY     50
+-#define JFFS2_ZLIB_PRIORITY      60
+-#define JFFS2_LZO_PRIORITY       80
+-
++#define JFFS2_LZMA_PRIORITY      70
++#define JFFS2_ZLIB_PRIORITY      80
++#define JFFS2_LZO_PRIORITY       90
+ 
+ #define JFFS2_RUBINMIPS_DISABLED /* RUBINs will be used only */
+ #define JFFS2_DYNRUBIN_DISABLED  /*	   for decompression */
+@@ -101,5 +101,9 @@ void jffs2_zlib_exit(void);
+ int jffs2_lzo_init(void);
+ void jffs2_lzo_exit(void);
+ #endif
++#ifdef CONFIG_JFFS2_LZMA
++int jffs2_lzma_init(void);
++void jffs2_lzma_exit(void);
++#endif
+ 
+ #endif /* __JFFS2_COMPR_H__ */
+diff --git a/fs/jffs2/compr_lzma.c b/fs/jffs2/compr_lzma.c
+new file mode 100644
+index 000000000000..ec0d9a6020de
+--- /dev/null
++++ b/fs/jffs2/compr_lzma.c
+@@ -0,0 +1,128 @@
++/*
++ * JFFS2 -- Journalling Flash File System, Version 2.
++ *
++ * For licensing information, see the file 'LICENCE' in this directory.
++ *
++ * JFFS2 wrapper to the LZMA C SDK
++ *
++ */
++
++#include <linux/lzma.h>
++#include "compr.h"
++
++#ifdef __KERNEL__
++	static DEFINE_MUTEX(deflate_mutex);
++#endif
++
++CLzmaEncHandle *p;
++Byte propsEncoded[LZMA_PROPS_SIZE];
++SizeT propsSize = sizeof(propsEncoded);
++
++STATIC void lzma_free_workspace(void)
++{
++	LzmaEnc_Destroy(p, &lzma_alloc, &lzma_alloc);
++}
++
++STATIC int INIT lzma_alloc_workspace(CLzmaEncProps *props)
++{
++	if ((p = (CLzmaEncHandle *)LzmaEnc_Create(&lzma_alloc)) == NULL)
++	{
++		PRINT_ERROR("Failed to allocate lzma deflate workspace\n");
++		return -ENOMEM;
++	}
++
++	if (LzmaEnc_SetProps(p, props) != SZ_OK)
++	{
++		lzma_free_workspace();
++		return -1;
++	}
++
++	if (LzmaEnc_WriteProperties(p, propsEncoded, &propsSize) != SZ_OK)
++	{
++		lzma_free_workspace();
++		return -1;
++	}
++
++	return 0;
++}
++
++STATIC int jffs2_lzma_compress(unsigned char *data_in, unsigned char *cpage_out,
++			      uint32_t *sourcelen, uint32_t *dstlen)
++{
++	SizeT compress_size = (SizeT)(*dstlen);
++	int ret;
++
++	#ifdef __KERNEL__
++		mutex_lock(&deflate_mutex);
++	#endif
++
++	ret = LzmaEnc_MemEncode(p, cpage_out, &compress_size, data_in, *sourcelen,
++		0, NULL, &lzma_alloc, &lzma_alloc);
++
++	#ifdef __KERNEL__
++		mutex_unlock(&deflate_mutex);
++	#endif
++
++	if (ret != SZ_OK)
++		return -1;
++
++	*dstlen = (uint32_t)compress_size;
++
++	return 0;
++}
++
++STATIC int jffs2_lzma_decompress(unsigned char *data_in, unsigned char *cpage_out,
++				 uint32_t srclen, uint32_t destlen)
++{
++	int ret;
++	SizeT dl = (SizeT)destlen;
++	SizeT sl = (SizeT)srclen;
++	ELzmaStatus status;
++
++	ret = LzmaDecode(cpage_out, &dl, data_in, &sl, propsEncoded,
++		propsSize, LZMA_FINISH_ANY, &status, &lzma_alloc);
++
++	if (ret != SZ_OK || status == LZMA_STATUS_NOT_FINISHED || dl != (SizeT)destlen)
++		return -1;
++
++	return 0;
++}
++
++static struct jffs2_compressor jffs2_lzma_comp = {
++	.priority = JFFS2_LZMA_PRIORITY,
++	.name = "lzma",
++	.compr = JFFS2_COMPR_LZMA,
++	.compress = &jffs2_lzma_compress,
++	.decompress = &jffs2_lzma_decompress,
++	.disabled = 0,
++};
++
++int INIT jffs2_lzma_init(void)
++{
++	int ret;
++	CLzmaEncProps props;
++	LzmaEncProps_Init(&props);
++
++	props.dictSize = LZMA_BEST_DICT(0x2000);
++	props.level = LZMA_BEST_LEVEL;
++	props.lc = LZMA_BEST_LC;
++	props.lp = LZMA_BEST_LP;
++	props.pb = LZMA_BEST_PB;
++	props.fb = LZMA_BEST_FB;
++
++	ret = lzma_alloc_workspace(&props);
++	if (ret < 0)
++		return ret;
++
++	ret = jffs2_register_compressor(&jffs2_lzma_comp);
++	if (ret)
++		lzma_free_workspace();
++
++	return ret;
++}
++
++void jffs2_lzma_exit(void)
++{
++	jffs2_unregister_compressor(&jffs2_lzma_comp);
++	lzma_free_workspace();
++}
+diff --git a/fs/jffs2/super.c b/fs/jffs2/super.c
+index 7ea37f49f1e1..9b93358d4586 100644
+--- a/fs/jffs2/super.c
++++ b/fs/jffs2/super.c
+@@ -374,14 +374,41 @@ static int __init init_jffs2_fs(void)
+ 	BUILD_BUG_ON(sizeof(struct jffs2_raw_inode) != 68);
+ 	BUILD_BUG_ON(sizeof(struct jffs2_raw_summary) != 32);
+ 
+-	pr_info("version 2.2."
++	pr_info("version 2.2"
+ #ifdef CONFIG_JFFS2_FS_WRITEBUFFER
+ 	       " (NAND)"
+ #endif
+ #ifdef CONFIG_JFFS2_SUMMARY
+-	       " (SUMMARY) "
++	       " (SUMMARY)"
+ #endif
+-	       " © 2001-2006 Red Hat, Inc.\n");
++#ifdef CONFIG_JFFS2_ZLIB
++	       " (ZLIB)"
++#endif
++#ifdef CONFIG_JFFS2_LZO
++	       " (LZO)"
++#endif
++#ifdef CONFIG_JFFS2_LZMA
++	       " (LZMA)"
++#endif
++#ifdef CONFIG_JFFS2_RTIME
++	       " (RTIME)"
++#endif
++#ifdef CONFIG_JFFS2_RUBIN
++	       " (RUBIN)"
++#endif
++#ifdef  CONFIG_JFFS2_CMODE_NONE
++	       " (CMODE_NONE)"
++#endif
++#ifdef CONFIG_JFFS2_CMODE_PRIORITY
++	       " (CMODE_PRIORITY)"
++#endif
++#ifdef CONFIG_JFFS2_CMODE_SIZE
++	       " (CMODE_SIZE)"
++#endif
++#ifdef CONFIG_JFFS2_CMODE_FAVOURLZO
++	       " (CMODE_FAVOURLZO)"
++#endif
++	       " (c) 2001-2006 Red Hat, Inc.\n");
+ 
+ 	jffs2_inode_cachep = kmem_cache_create("jffs2_i",
+ 					     sizeof(struct jffs2_inode_info),
+diff --git a/include/linux/lzma.h b/include/linux/lzma.h
+new file mode 100644
+index 000000000000..4299d19edf59
+--- /dev/null
++++ b/include/linux/lzma.h
+@@ -0,0 +1,62 @@
++#ifndef __LZMA_H__
++#define __LZMA_H__
++
++#ifdef __KERNEL__
++	#include <linux/kernel.h>
++	#include <linux/sched.h>
++	#include <linux/slab.h>
++	#include <linux/vmalloc.h>
++	#include <linux/init.h>
++	#define LZMA_MALLOC vmalloc
++	#define LZMA_FREE vfree
++	#define PRINT_ERROR(msg) printk(KERN_WARNING #msg)
++	#define INIT __init
++	#define STATIC static
++#else
++	#include <stdint.h>
++	#include <stdlib.h>
++	#include <stdio.h>
++	#include <unistd.h>
++	#include <string.h>
++	#include <asm/types.h>
++	#include <errno.h>
++	#include <linux/jffs2.h>
++	#ifndef PAGE_SIZE
++		extern int page_size;
++		#define PAGE_SIZE page_size
++	#endif
++	#define LZMA_MALLOC malloc
++	#define LZMA_FREE free
++	#define PRINT_ERROR(msg) fprintf(stderr, msg)
++	#define INIT
++	#define STATIC
++#endif
++
++#include "lzma/LzmaDec.h"
++#include "lzma/LzmaEnc.h"
++
++#define LZMA_BEST_LEVEL (9)
++#define LZMA_BEST_LC    (0)
++#define LZMA_BEST_LP    (0)
++#define LZMA_BEST_PB    (0)
++#define LZMA_BEST_FB  (273)
++
++#define LZMA_BEST_DICT(n) (((int)((n) / 2)) * 2)
++
++static void *p_lzma_malloc(void *p, size_t size)
++{
++	if (size == 0)
++		return NULL;
++
++	return LZMA_MALLOC(size);
++}
++
++static void p_lzma_free(void *p, void *address)
++{
++	if (address != NULL)
++		LZMA_FREE(address);
++}
++
++static ISzAlloc lzma_alloc = {p_lzma_malloc, p_lzma_free};
++
++#endif
+diff --git a/include/linux/lzma/LzFind.h b/include/linux/lzma/LzFind.h
+new file mode 100644
+index 000000000000..010c4b92ba33
+--- /dev/null
++++ b/include/linux/lzma/LzFind.h
+@@ -0,0 +1,115 @@
++/* LzFind.h -- Match finder for LZ algorithms
++2009-04-22 : Igor Pavlov : Public domain */
++
++#ifndef __LZ_FIND_H
++#define __LZ_FIND_H
++
++#include "Types.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++typedef UInt32 CLzRef;
++
++typedef struct _CMatchFinder
++{
++  Byte *buffer;
++  UInt32 pos;
++  UInt32 posLimit;
++  UInt32 streamPos;
++  UInt32 lenLimit;
++
++  UInt32 cyclicBufferPos;
++  UInt32 cyclicBufferSize; /* it must be = (historySize + 1) */
++
++  UInt32 matchMaxLen;
++  CLzRef *hash;
++  CLzRef *son;
++  UInt32 hashMask;
++  UInt32 cutValue;
++
++  Byte *bufferBase;
++  ISeqInStream *stream;
++  int streamEndWasReached;
++
++  UInt32 blockSize;
++  UInt32 keepSizeBefore;
++  UInt32 keepSizeAfter;
++
++  UInt32 numHashBytes;
++  int directInput;
++  size_t directInputRem;
++  int btMode;
++  int bigHash;
++  UInt32 historySize;
++  UInt32 fixedHashSize;
++  UInt32 hashSizeSum;
++  UInt32 numSons;
++  SRes result;
++  UInt32 crc[256];
++} CMatchFinder;
++
++#define Inline_MatchFinder_GetPointerToCurrentPos(p) ((p)->buffer)
++#define Inline_MatchFinder_GetIndexByte(p, index) ((p)->buffer[(Int32)(index)])
++
++#define Inline_MatchFinder_GetNumAvailableBytes(p) ((p)->streamPos - (p)->pos)
++
++int MatchFinder_NeedMove(CMatchFinder *p);
++Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p);
++void MatchFinder_MoveBlock(CMatchFinder *p);
++void MatchFinder_ReadIfRequired(CMatchFinder *p);
++
++void MatchFinder_Construct(CMatchFinder *p);
++
++/* Conditions:
++     historySize <= 3 GB
++     keepAddBufferBefore + matchMaxLen + keepAddBufferAfter < 511MB
++*/
++int MatchFinder_Create(CMatchFinder *p, UInt32 historySize,
++    UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter,
++    ISzAlloc *alloc);
++void MatchFinder_Free(CMatchFinder *p, ISzAlloc *alloc);
++void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, UInt32 numItems);
++void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue);
++
++UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *buffer, CLzRef *son,
++    UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue,
++    UInt32 *distances, UInt32 maxLen);
++
++/*
++Conditions:
++  Mf_GetNumAvailableBytes_Func must be called before each Mf_GetMatchLen_Func.
++  Mf_GetPointerToCurrentPos_Func's result must be used only before any other function
++*/
++
++typedef void (*Mf_Init_Func)(void *object);
++typedef Byte (*Mf_GetIndexByte_Func)(void *object, Int32 index);
++typedef UInt32 (*Mf_GetNumAvailableBytes_Func)(void *object);
++typedef const Byte * (*Mf_GetPointerToCurrentPos_Func)(void *object);
++typedef UInt32 (*Mf_GetMatches_Func)(void *object, UInt32 *distances);
++typedef void (*Mf_Skip_Func)(void *object, UInt32);
++
++typedef struct _IMatchFinder
++{
++  Mf_Init_Func Init;
++  Mf_GetIndexByte_Func GetIndexByte;
++  Mf_GetNumAvailableBytes_Func GetNumAvailableBytes;
++  Mf_GetPointerToCurrentPos_Func GetPointerToCurrentPos;
++  Mf_GetMatches_Func GetMatches;
++  Mf_Skip_Func Skip;
++} IMatchFinder;
++
++void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable);
++
++void MatchFinder_Init(CMatchFinder *p);
++UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances);
++UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances);
++void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num);
++void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/include/linux/lzma/LzHash.h b/include/linux/lzma/LzHash.h
+new file mode 100644
+index 000000000000..f3e89966cc70
+--- /dev/null
++++ b/include/linux/lzma/LzHash.h
+@@ -0,0 +1,54 @@
++/* LzHash.h -- HASH functions for LZ algorithms
++2009-02-07 : Igor Pavlov : Public domain */
++
++#ifndef __LZ_HASH_H
++#define __LZ_HASH_H
++
++#define kHash2Size (1 << 10)
++#define kHash3Size (1 << 16)
++#define kHash4Size (1 << 20)
++
++#define kFix3HashSize (kHash2Size)
++#define kFix4HashSize (kHash2Size + kHash3Size)
++#define kFix5HashSize (kHash2Size + kHash3Size + kHash4Size)
++
++#define HASH2_CALC hashValue = cur[0] | ((UInt32)cur[1] << 8);
++
++#define HASH3_CALC { \
++  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
++  hash2Value = temp & (kHash2Size - 1); \
++  hashValue = (temp ^ ((UInt32)cur[2] << 8)) & p->hashMask; }
++
++#define HASH4_CALC { \
++  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
++  hash2Value = temp & (kHash2Size - 1); \
++  hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); \
++  hashValue = (temp ^ ((UInt32)cur[2] << 8) ^ (p->crc[cur[3]] << 5)) & p->hashMask; }
++
++#define HASH5_CALC { \
++  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
++  hash2Value = temp & (kHash2Size - 1); \
++  hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); \
++  hash4Value = (temp ^ ((UInt32)cur[2] << 8) ^ (p->crc[cur[3]] << 5)); \
++  hashValue = (hash4Value ^ (p->crc[cur[4]] << 3)) & p->hashMask; \
++  hash4Value &= (kHash4Size - 1); }
++
++/* #define HASH_ZIP_CALC hashValue = ((cur[0] | ((UInt32)cur[1] << 8)) ^ p->crc[cur[2]]) & 0xFFFF; */
++#define HASH_ZIP_CALC hashValue = ((cur[2] | ((UInt32)cur[0] << 8)) ^ p->crc[cur[1]]) & 0xFFFF;
++
++
++#define MT_HASH2_CALC \
++  hash2Value = (p->crc[cur[0]] ^ cur[1]) & (kHash2Size - 1);
++
++#define MT_HASH3_CALC { \
++  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
++  hash2Value = temp & (kHash2Size - 1); \
++  hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); }
++
++#define MT_HASH4_CALC { \
++  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
++  hash2Value = temp & (kHash2Size - 1); \
++  hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); \
++  hash4Value = (temp ^ ((UInt32)cur[2] << 8) ^ (p->crc[cur[3]] << 5)) & (kHash4Size - 1); }
++
++#endif
+diff --git a/include/linux/lzma/LzmaDec.h b/include/linux/lzma/LzmaDec.h
+new file mode 100644
+index 000000000000..7927acd0d4eb
+--- /dev/null
++++ b/include/linux/lzma/LzmaDec.h
+@@ -0,0 +1,231 @@
++/* LzmaDec.h -- LZMA Decoder
++2009-02-07 : Igor Pavlov : Public domain */
++
++#ifndef __LZMA_DEC_H
++#define __LZMA_DEC_H
++
++#include "Types.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++/* #define _LZMA_PROB32 */
++/* _LZMA_PROB32 can increase the speed on some CPUs,
++   but memory usage for CLzmaDec::probs will be doubled in that case */
++
++#ifdef _LZMA_PROB32
++#define CLzmaProb UInt32
++#else
++#define CLzmaProb UInt16
++#endif
++
++
++/* ---------- LZMA Properties ---------- */
++
++#define LZMA_PROPS_SIZE 5
++
++typedef struct _CLzmaProps
++{
++  unsigned lc, lp, pb;
++  UInt32 dicSize;
++} CLzmaProps;
++
++/* LzmaProps_Decode - decodes properties
++Returns:
++  SZ_OK
++  SZ_ERROR_UNSUPPORTED - Unsupported properties
++*/
++
++SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size);
++
++
++/* ---------- LZMA Decoder state ---------- */
++
++/* LZMA_REQUIRED_INPUT_MAX = number of required input bytes for worst case.
++   Num bits = log2((2^11 / 31) ^ 22) + 26 < 134 + 26 = 160; */
++
++#define LZMA_REQUIRED_INPUT_MAX 20
++
++typedef struct
++{
++  CLzmaProps prop;
++  CLzmaProb *probs;
++  Byte *dic;
++  const Byte *buf;
++  UInt32 range, code;
++  SizeT dicPos;
++  SizeT dicBufSize;
++  UInt32 processedPos;
++  UInt32 checkDicSize;
++  unsigned state;
++  UInt32 reps[4];
++  unsigned remainLen;
++  int needFlush;
++  int needInitState;
++  UInt32 numProbs;
++  unsigned tempBufSize;
++  Byte tempBuf[LZMA_REQUIRED_INPUT_MAX];
++} CLzmaDec;
++
++#define LzmaDec_Construct(p) { (p)->dic = 0; (p)->probs = 0; }
++
++void LzmaDec_Init(CLzmaDec *p);
++
++/* There are two types of LZMA streams:
++     0) Stream with end mark. That end mark adds about 6 bytes to compressed size.
++     1) Stream without end mark. You must know exact uncompressed size to decompress such stream. */
++
++typedef enum
++{
++  LZMA_FINISH_ANY,   /* finish at any point */
++  LZMA_FINISH_END    /* block must be finished at the end */
++} ELzmaFinishMode;
++
++/* ELzmaFinishMode has meaning only if the decoding reaches output limit !!!
++
++   You must use LZMA_FINISH_END, when you know that current output buffer
++   covers last bytes of block. In other cases you must use LZMA_FINISH_ANY.
++
++   If LZMA decoder sees end marker before reaching output limit, it returns SZ_OK,
++   and output value of destLen will be less than output buffer size limit.
++   You can check status result also.
++
++   You can use multiple checks to test data integrity after full decompression:
++     1) Check Result and "status" variable.
++     2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize.
++     3) Check that output(srcLen) = compressedSize, if you know real compressedSize.
++        You must use correct finish mode in that case. */
++
++typedef enum
++{
++  LZMA_STATUS_NOT_SPECIFIED,               /* use main error code instead */
++  LZMA_STATUS_FINISHED_WITH_MARK,          /* stream was finished with end mark. */
++  LZMA_STATUS_NOT_FINISHED,                /* stream was not finished */
++  LZMA_STATUS_NEEDS_MORE_INPUT,            /* you must provide more input bytes */
++  LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK  /* there is probability that stream was finished without end mark */
++} ELzmaStatus;
++
++/* ELzmaStatus is used only as output value for function call */
++
++
++/* ---------- Interfaces ---------- */
++
++/* There are 3 levels of interfaces:
++     1) Dictionary Interface
++     2) Buffer Interface
++     3) One Call Interface
++   You can select any of these interfaces, but don't mix functions from different
++   groups for same object. */
++
++
++/* There are two variants to allocate state for Dictionary Interface:
++     1) LzmaDec_Allocate / LzmaDec_Free
++     2) LzmaDec_AllocateProbs / LzmaDec_FreeProbs
++   You can use variant 2, if you set dictionary buffer manually.
++   For Buffer Interface you must always use variant 1.
++
++LzmaDec_Allocate* can return:
++  SZ_OK
++  SZ_ERROR_MEM         - Memory allocation error
++  SZ_ERROR_UNSUPPORTED - Unsupported properties
++*/
++
++SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc);
++void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc);
++
++SRes LzmaDec_Allocate(CLzmaDec *state, const Byte *prop, unsigned propsSize, ISzAlloc *alloc);
++void LzmaDec_Free(CLzmaDec *state, ISzAlloc *alloc);
++
++/* ---------- Dictionary Interface ---------- */
++
++/* You can use it, if you want to eliminate the overhead for data copying from
++   dictionary to some other external buffer.
++   You must work with CLzmaDec variables directly in this interface.
++
++   STEPS:
++     LzmaDec_Constr()
++     LzmaDec_Allocate()
++     for (each new stream)
++     {
++       LzmaDec_Init()
++       while (it needs more decompression)
++       {
++         LzmaDec_DecodeToDic()
++         use data from CLzmaDec::dic and update CLzmaDec::dicPos
++       }
++     }
++     LzmaDec_Free()
++*/
++
++/* LzmaDec_DecodeToDic
++
++   The decoding to internal dictionary buffer (CLzmaDec::dic).
++   You must manually update CLzmaDec::dicPos, if it reaches CLzmaDec::dicBufSize !!!
++
++finishMode:
++  It has meaning only if the decoding reaches output limit (dicLimit).
++  LZMA_FINISH_ANY - Decode just dicLimit bytes.
++  LZMA_FINISH_END - Stream must be finished after dicLimit.
++
++Returns:
++  SZ_OK
++    status:
++      LZMA_STATUS_FINISHED_WITH_MARK
++      LZMA_STATUS_NOT_FINISHED
++      LZMA_STATUS_NEEDS_MORE_INPUT
++      LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
++  SZ_ERROR_DATA - Data error
++*/
++
++SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit,
++    const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status);
++
++
++/* ---------- Buffer Interface ---------- */
++
++/* It's zlib-like interface.
++   See LzmaDec_DecodeToDic description for information about STEPS and return results,
++   but you must use LzmaDec_DecodeToBuf instead of LzmaDec_DecodeToDic and you don't need
++   to work with CLzmaDec variables manually.
++
++finishMode:
++  It has meaning only if the decoding reaches output limit (*destLen).
++  LZMA_FINISH_ANY - Decode just destLen bytes.
++  LZMA_FINISH_END - Stream must be finished after (*destLen).
++*/
++
++SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen,
++    const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status);
++
++
++/* ---------- One Call Interface ---------- */
++
++/* LzmaDecode
++
++finishMode:
++  It has meaning only if the decoding reaches output limit (*destLen).
++  LZMA_FINISH_ANY - Decode just destLen bytes.
++  LZMA_FINISH_END - Stream must be finished after (*destLen).
++
++Returns:
++  SZ_OK
++    status:
++      LZMA_STATUS_FINISHED_WITH_MARK
++      LZMA_STATUS_NOT_FINISHED
++      LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
++  SZ_ERROR_DATA - Data error
++  SZ_ERROR_MEM  - Memory allocation error
++  SZ_ERROR_UNSUPPORTED - Unsupported properties
++  SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src).
++*/
++
++SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen,
++    const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode,
++    ELzmaStatus *status, ISzAlloc *alloc);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/include/linux/lzma/LzmaEnc.h b/include/linux/lzma/LzmaEnc.h
+new file mode 100644
+index 000000000000..200d60eb83cd
+--- /dev/null
++++ b/include/linux/lzma/LzmaEnc.h
+@@ -0,0 +1,80 @@
++/*  LzmaEnc.h -- LZMA Encoder
++2009-02-07 : Igor Pavlov : Public domain */
++
++#ifndef __LZMA_ENC_H
++#define __LZMA_ENC_H
++
++#include "Types.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#define LZMA_PROPS_SIZE 5
++
++typedef struct _CLzmaEncProps
++{
++  int level;       /*  0 <= level <= 9 */
++  UInt32 dictSize; /* (1 << 12) <= dictSize <= (1 << 27) for 32-bit version
++                      (1 << 12) <= dictSize <= (1 << 30) for 64-bit version
++                       default = (1 << 24) */
++  int lc;          /* 0 <= lc <= 8, default = 3 */
++  int lp;          /* 0 <= lp <= 4, default = 0 */
++  int pb;          /* 0 <= pb <= 4, default = 2 */
++  int algo;        /* 0 - fast, 1 - normal, default = 1 */
++  int fb;          /* 5 <= fb <= 273, default = 32 */
++  int btMode;      /* 0 - hashChain Mode, 1 - binTree mode - normal, default = 1 */
++  int numHashBytes; /* 2, 3 or 4, default = 4 */
++  UInt32 mc;        /* 1 <= mc <= (1 << 30), default = 32 */
++  unsigned writeEndMark;  /* 0 - do not write EOPM, 1 - write EOPM, default = 0 */
++  int numThreads;  /* 1 or 2, default = 2 */
++} CLzmaEncProps;
++
++void LzmaEncProps_Init(CLzmaEncProps *p);
++void LzmaEncProps_Normalize(CLzmaEncProps *p);
++UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2);
++
++
++/* ---------- CLzmaEncHandle Interface ---------- */
++
++/* LzmaEnc_* functions can return the following exit codes:
++Returns:
++  SZ_OK           - OK
++  SZ_ERROR_MEM    - Memory allocation error
++  SZ_ERROR_PARAM  - Incorrect paramater in props
++  SZ_ERROR_WRITE  - Write callback error.
++  SZ_ERROR_PROGRESS - some break from progress callback
++  SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version)
++*/
++
++typedef void * CLzmaEncHandle;
++
++CLzmaEncHandle LzmaEnc_Create(ISzAlloc *alloc);
++void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAlloc *alloc, ISzAlloc *allocBig);
++SRes LzmaEnc_SetProps(CLzmaEncHandle p, const CLzmaEncProps *props);
++SRes LzmaEnc_WriteProperties(CLzmaEncHandle p, Byte *properties, SizeT *size);
++SRes LzmaEnc_Encode(CLzmaEncHandle p, ISeqOutStream *outStream, ISeqInStream *inStream,
++    ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig);
++SRes LzmaEnc_MemEncode(CLzmaEncHandle p, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen,
++    int writeEndMark, ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig);
++
++/* ---------- One Call Interface ---------- */
++
++/* LzmaEncode
++Return code:
++  SZ_OK               - OK
++  SZ_ERROR_MEM        - Memory allocation error
++  SZ_ERROR_PARAM      - Incorrect paramater
++  SZ_ERROR_OUTPUT_EOF - output buffer overflow
++  SZ_ERROR_THREAD     - errors in multithreading functions (only for Mt version)
++*/
++
++SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen,
++    const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark,
++    ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/include/linux/lzma/Types.h b/include/linux/lzma/Types.h
+new file mode 100644
+index 000000000000..4751acde0722
+--- /dev/null
++++ b/include/linux/lzma/Types.h
+@@ -0,0 +1,226 @@
++/* Types.h -- Basic types
++2009-11-23 : Igor Pavlov : Public domain */
++
++#ifndef __7Z_TYPES_H
++#define __7Z_TYPES_H
++
++#include <stddef.h>
++
++#ifdef _WIN32
++#include <windows.h>
++#endif
++
++#ifndef EXTERN_C_BEGIN
++#ifdef __cplusplus
++#define EXTERN_C_BEGIN extern "C" {
++#define EXTERN_C_END }
++#else
++#define EXTERN_C_BEGIN
++#define EXTERN_C_END
++#endif
++#endif
++
++EXTERN_C_BEGIN
++
++#define SZ_OK 0
++
++#define SZ_ERROR_DATA 1
++#define SZ_ERROR_MEM 2
++#define SZ_ERROR_CRC 3
++#define SZ_ERROR_UNSUPPORTED 4
++#define SZ_ERROR_PARAM 5
++#define SZ_ERROR_INPUT_EOF 6
++#define SZ_ERROR_OUTPUT_EOF 7
++#define SZ_ERROR_READ 8
++#define SZ_ERROR_WRITE 9
++#define SZ_ERROR_PROGRESS 10
++#define SZ_ERROR_FAIL 11
++#define SZ_ERROR_THREAD 12
++
++#define SZ_ERROR_ARCHIVE 16
++#define SZ_ERROR_NO_ARCHIVE 17
++
++typedef int SRes;
++
++#ifdef _WIN32
++typedef DWORD WRes;
++#else
++typedef int WRes;
++#endif
++
++#ifndef RINOK
++#define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; }
++#endif
++
++typedef unsigned char Byte;
++typedef short Int16;
++typedef unsigned short UInt16;
++
++#ifdef _LZMA_UINT32_IS_ULONG
++typedef long Int32;
++typedef unsigned long UInt32;
++#else
++typedef int Int32;
++typedef unsigned int UInt32;
++#endif
++
++#ifdef _SZ_NO_INT_64
++
++/* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers.
++   NOTES: Some code will work incorrectly in that case! */
++
++typedef long Int64;
++typedef unsigned long UInt64;
++
++#else
++
++#if defined(_MSC_VER) || defined(__BORLANDC__)
++typedef __int64 Int64;
++typedef unsigned __int64 UInt64;
++#else
++typedef long long int Int64;
++typedef unsigned long long int UInt64;
++#endif
++
++#endif
++
++#ifdef _LZMA_NO_SYSTEM_SIZE_T
++typedef UInt32 SizeT;
++#else
++typedef size_t SizeT;
++#endif
++
++typedef int Bool;
++#define True 1
++#define False 0
++
++
++#ifdef _WIN32
++#define MY_STD_CALL __stdcall
++#else
++#define MY_STD_CALL
++#endif
++
++#ifdef _MSC_VER
++
++#if _MSC_VER >= 1300
++#define MY_NO_INLINE __declspec(noinline)
++#else
++#define MY_NO_INLINE
++#endif
++
++#define MY_CDECL __cdecl
++#define MY_FAST_CALL __fastcall
++
++#else
++
++#define MY_CDECL
++#define MY_FAST_CALL
++
++#endif
++
++
++/* The following interfaces use first parameter as pointer to structure */
++
++typedef struct
++{
++  SRes (*Read)(void *p, void *buf, size_t *size);
++    /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream.
++       (output(*size) < input(*size)) is allowed */
++} ISeqInStream;
++
++/* it can return SZ_ERROR_INPUT_EOF */
++SRes SeqInStream_Read(ISeqInStream *stream, void *buf, size_t size);
++SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorType);
++SRes SeqInStream_ReadByte(ISeqInStream *stream, Byte *buf);
++
++typedef struct
++{
++  size_t (*Write)(void *p, const void *buf, size_t size);
++    /* Returns: result - the number of actually written bytes.
++       (result < size) means error */
++} ISeqOutStream;
++
++typedef enum
++{
++  SZ_SEEK_SET = 0,
++  SZ_SEEK_CUR = 1,
++  SZ_SEEK_END = 2
++} ESzSeek;
++
++typedef struct
++{
++  SRes (*Read)(void *p, void *buf, size_t *size);  /* same as ISeqInStream::Read */
++  SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin);
++} ISeekInStream;
++
++typedef struct
++{
++  SRes (*Look)(void *p, void **buf, size_t *size);
++    /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream.
++       (output(*size) > input(*size)) is not allowed
++       (output(*size) < input(*size)) is allowed */
++  SRes (*Skip)(void *p, size_t offset);
++    /* offset must be <= output(*size) of Look */
++
++  SRes (*Read)(void *p, void *buf, size_t *size);
++    /* reads directly (without buffer). It's same as ISeqInStream::Read */
++  SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin);
++} ILookInStream;
++
++SRes LookInStream_LookRead(ILookInStream *stream, void *buf, size_t *size);
++SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset);
++
++/* reads via ILookInStream::Read */
++SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType);
++SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size);
++
++#define LookToRead_BUF_SIZE (1 << 14)
++
++typedef struct
++{
++  ILookInStream s;
++  ISeekInStream *realStream;
++  size_t pos;
++  size_t size;
++  Byte buf[LookToRead_BUF_SIZE];
++} CLookToRead;
++
++void LookToRead_CreateVTable(CLookToRead *p, int lookahead);
++void LookToRead_Init(CLookToRead *p);
++
++typedef struct
++{
++  ISeqInStream s;
++  ILookInStream *realStream;
++} CSecToLook;
++
++void SecToLook_CreateVTable(CSecToLook *p);
++
++typedef struct
++{
++  ISeqInStream s;
++  ILookInStream *realStream;
++} CSecToRead;
++
++void SecToRead_CreateVTable(CSecToRead *p);
++
++typedef struct
++{
++  SRes (*Progress)(void *p, UInt64 inSize, UInt64 outSize);
++    /* Returns: result. (result != SZ_OK) means break.
++       Value (UInt64)(Int64)-1 for size means unknown value. */
++} ICompressProgress;
++
++typedef struct
++{
++  void *(*Alloc)(void *p, size_t size);
++  void (*Free)(void *p, void *address); /* address can be 0 */
++} ISzAlloc;
++
++#define IAlloc_Alloc(p, size) (p)->Alloc((p), size)
++#define IAlloc_Free(p, a) (p)->Free((p), a)
++
++EXTERN_C_END
++
++#endif
+diff --git a/include/uapi/linux/jffs2.h b/include/uapi/linux/jffs2.h
+index 784ba0b9690a..cae7fa82c1a0 100644
+--- a/include/uapi/linux/jffs2.h
++++ b/include/uapi/linux/jffs2.h
+@@ -46,6 +46,7 @@
+ #define JFFS2_COMPR_DYNRUBIN	0x05
+ #define JFFS2_COMPR_ZLIB	0x06
+ #define JFFS2_COMPR_LZO		0x07
++#define JFFS2_COMPR_LZMA	0x08
+ /* Compatibility flags. */
+ #define JFFS2_COMPAT_MASK 0xc000      /* What do to if an unknown nodetype is found */
+ #define JFFS2_NODE_ACCURATE 0x2000
+diff --git a/lib/Kconfig b/lib/Kconfig
+index eaaad4d85bf2..2f2da2fc2416 100644
+--- a/lib/Kconfig
++++ b/lib/Kconfig
+@@ -346,6 +346,12 @@ config ZSTD_DECOMPRESS
+ 
+ source "lib/xz/Kconfig"
+ 
++config LZMA_COMPRESS
++    tristate
++
++config LZMA_DECOMPRESS
++    tristate
++
+ #
+ # These all provide a common interface (hence the apparent duplication with
+ # ZLIB_INFLATE; DECOMPRESS_GZIP is just a wrapper.)
+diff --git a/lib/Makefile b/lib/Makefile
+index f99bf61f8bbc..884fb0a205dd 100644
+--- a/lib/Makefile
++++ b/lib/Makefile
+@@ -136,6 +136,16 @@ CFLAGS_kobject.o += -DDEBUG
+ CFLAGS_kobject_uevent.o += -DDEBUG
+ endif
+ 
++ifdef CONFIG_JFFS2_ZLIB
++  CONFIG_ZLIB_INFLATE:=y
++  CONFIG_ZLIB_DEFLATE:=y
++endif
++
++ifdef CONFIG_JFFS2_LZMA
++  CONFIG_LZMA_DECOMPRESS:=y
++  CONFIG_LZMA_COMPRESS:=y
++endif
++
+ obj-$(CONFIG_DEBUG_INFO_REDUCED) += debug_info.o
+ CFLAGS_debug_info.o += $(call cc-option, -femit-struct-debug-detailed=any)
+ 
+@@ -194,6 +204,8 @@ obj-$(CONFIG_ZSTD_COMPRESS) += zstd/
+ obj-$(CONFIG_ZSTD_DECOMPRESS) += zstd/
+ obj-$(CONFIG_XZ_DEC) += xz/
+ obj-$(CONFIG_RAID6_PQ) += raid6/
++obj-$(CONFIG_LZMA_COMPRESS) += lzma/
++obj-$(CONFIG_LZMA_DECOMPRESS) += lzma/
+ 
+ lib-$(CONFIG_DECOMPRESS_GZIP) += decompress_inflate.o
+ lib-$(CONFIG_DECOMPRESS_BZIP2) += decompress_bunzip2.o
+diff --git a/lib/lzma/LzFind.c b/lib/lzma/LzFind.c
+new file mode 100644
+index 000000000000..e753d04c692f
+--- /dev/null
++++ b/lib/lzma/LzFind.c
+@@ -0,0 +1,761 @@
++/* LzFind.c -- Match finder for LZ algorithms
++2009-04-22 : Igor Pavlov : Public domain */
++
++#include <string.h>
++
++#include "LzFind.h"
++#include "LzHash.h"
++
++#define kEmptyHashValue 0
++#define kMaxValForNormalize ((UInt32)0xFFFFFFFF)
++#define kNormalizeStepMin (1 << 10) /* it must be power of 2 */
++#define kNormalizeMask (~(kNormalizeStepMin - 1))
++#define kMaxHistorySize ((UInt32)3 << 30)
++
++#define kStartMaxLen 3
++
++static void LzInWindow_Free(CMatchFinder *p, ISzAlloc *alloc)
++{
++  if (!p->directInput)
++  {
++    alloc->Free(alloc, p->bufferBase);
++    p->bufferBase = 0;
++  }
++}
++
++/* keepSizeBefore + keepSizeAfter + keepSizeReserv must be < 4G) */
++
++static int LzInWindow_Create(CMatchFinder *p, UInt32 keepSizeReserv, ISzAlloc *alloc)
++{
++  UInt32 blockSize = p->keepSizeBefore + p->keepSizeAfter + keepSizeReserv;
++  if (p->directInput)
++  {
++    p->blockSize = blockSize;
++    return 1;
++  }
++  if (p->bufferBase == 0 || p->blockSize != blockSize)
++  {
++    LzInWindow_Free(p, alloc);
++    p->blockSize = blockSize;
++    p->bufferBase = (Byte *)alloc->Alloc(alloc, (size_t)blockSize);
++  }
++  return (p->bufferBase != 0);
++}
++
++Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p) { return p->buffer; }
++Byte MatchFinder_GetIndexByte(CMatchFinder *p, Int32 index) { return p->buffer[index]; }
++
++UInt32 MatchFinder_GetNumAvailableBytes(CMatchFinder *p) { return p->streamPos - p->pos; }
++
++void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue)
++{
++  p->posLimit -= subValue;
++  p->pos -= subValue;
++  p->streamPos -= subValue;
++}
++
++static void MatchFinder_ReadBlock(CMatchFinder *p)
++{
++  if (p->streamEndWasReached || p->result != SZ_OK)
++    return;
++  if (p->directInput)
++  {
++    UInt32 curSize = 0xFFFFFFFF - p->streamPos;
++    if (curSize > p->directInputRem)
++      curSize = (UInt32)p->directInputRem;
++    p->directInputRem -= curSize;
++    p->streamPos += curSize;
++    if (p->directInputRem == 0)
++      p->streamEndWasReached = 1;
++    return;
++  }
++  for (;;)
++  {
++    Byte *dest = p->buffer + (p->streamPos - p->pos);
++    size_t size = (p->bufferBase + p->blockSize - dest);
++    if (size == 0)
++      return;
++    p->result = p->stream->Read(p->stream, dest, &size);
++    if (p->result != SZ_OK)
++      return;
++    if (size == 0)
++    {
++      p->streamEndWasReached = 1;
++      return;
++    }
++    p->streamPos += (UInt32)size;
++    if (p->streamPos - p->pos > p->keepSizeAfter)
++      return;
++  }
++}
++
++void MatchFinder_MoveBlock(CMatchFinder *p)
++{
++  memmove(p->bufferBase,
++    p->buffer - p->keepSizeBefore,
++    (size_t)(p->streamPos - p->pos + p->keepSizeBefore));
++  p->buffer = p->bufferBase + p->keepSizeBefore;
++}
++
++int MatchFinder_NeedMove(CMatchFinder *p)
++{
++  if (p->directInput)
++    return 0;
++  /* if (p->streamEndWasReached) return 0; */
++  return ((size_t)(p->bufferBase + p->blockSize - p->buffer) <= p->keepSizeAfter);
++}
++
++void MatchFinder_ReadIfRequired(CMatchFinder *p)
++{
++  if (p->streamEndWasReached)
++    return;
++  if (p->keepSizeAfter >= p->streamPos - p->pos)
++    MatchFinder_ReadBlock(p);
++}
++
++static void MatchFinder_CheckAndMoveAndRead(CMatchFinder *p)
++{
++  if (MatchFinder_NeedMove(p))
++    MatchFinder_MoveBlock(p);
++  MatchFinder_ReadBlock(p);
++}
++
++static void MatchFinder_SetDefaultSettings(CMatchFinder *p)
++{
++  p->cutValue = 32;
++  p->btMode = 1;
++  p->numHashBytes = 4;
++  p->bigHash = 0;
++}
++
++#define kCrcPoly 0xEDB88320
++
++void MatchFinder_Construct(CMatchFinder *p)
++{
++  UInt32 i;
++  p->bufferBase = 0;
++  p->directInput = 0;
++  p->hash = 0;
++  MatchFinder_SetDefaultSettings(p);
++
++  for (i = 0; i < 256; i++)
++  {
++    UInt32 r = i;
++    int j;
++    for (j = 0; j < 8; j++)
++      r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1));
++    p->crc[i] = r;
++  }
++}
++
++static void MatchFinder_FreeThisClassMemory(CMatchFinder *p, ISzAlloc *alloc)
++{
++  alloc->Free(alloc, p->hash);
++  p->hash = 0;
++}
++
++void MatchFinder_Free(CMatchFinder *p, ISzAlloc *alloc)
++{
++  MatchFinder_FreeThisClassMemory(p, alloc);
++  LzInWindow_Free(p, alloc);
++}
++
++static CLzRef* AllocRefs(UInt32 num, ISzAlloc *alloc)
++{
++  size_t sizeInBytes = (size_t)num * sizeof(CLzRef);
++  if (sizeInBytes / sizeof(CLzRef) != num)
++    return 0;
++  return (CLzRef *)alloc->Alloc(alloc, sizeInBytes);
++}
++
++int MatchFinder_Create(CMatchFinder *p, UInt32 historySize,
++    UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter,
++    ISzAlloc *alloc)
++{
++  UInt32 sizeReserv;
++  if (historySize > kMaxHistorySize)
++  {
++    MatchFinder_Free(p, alloc);
++    return 0;
++  }
++  sizeReserv = historySize >> 1;
++  if (historySize > ((UInt32)2 << 30))
++    sizeReserv = historySize >> 2;
++  sizeReserv += (keepAddBufferBefore + matchMaxLen + keepAddBufferAfter) / 2 + (1 << 19);
++
++  p->keepSizeBefore = historySize + keepAddBufferBefore + 1;
++  p->keepSizeAfter = matchMaxLen + keepAddBufferAfter;
++  /* we need one additional byte, since we use MoveBlock after pos++ and before dictionary using */
++  if (LzInWindow_Create(p, sizeReserv, alloc))
++  {
++    UInt32 newCyclicBufferSize = historySize + 1;
++    UInt32 hs;
++    p->matchMaxLen = matchMaxLen;
++    {
++      p->fixedHashSize = 0;
++      if (p->numHashBytes == 2)
++        hs = (1 << 16) - 1;
++      else
++      {
++        hs = historySize - 1;
++        hs |= (hs >> 1);
++        hs |= (hs >> 2);
++        hs |= (hs >> 4);
++        hs |= (hs >> 8);
++        hs >>= 1;
++        hs |= 0xFFFF; /* don't change it! It's required for Deflate */
++        if (hs > (1 << 24))
++        {
++          if (p->numHashBytes == 3)
++            hs = (1 << 24) - 1;
++          else
++            hs >>= 1;
++        }
++      }
++      p->hashMask = hs;
++      hs++;
++      if (p->numHashBytes > 2) p->fixedHashSize += kHash2Size;
++      if (p->numHashBytes > 3) p->fixedHashSize += kHash3Size;
++      if (p->numHashBytes > 4) p->fixedHashSize += kHash4Size;
++      hs += p->fixedHashSize;
++    }
++
++    {
++      UInt32 prevSize = p->hashSizeSum + p->numSons;
++      UInt32 newSize;
++      p->historySize = historySize;
++      p->hashSizeSum = hs;
++      p->cyclicBufferSize = newCyclicBufferSize;
++      p->numSons = (p->btMode ? newCyclicBufferSize * 2 : newCyclicBufferSize);
++      newSize = p->hashSizeSum + p->numSons;
++      if (p->hash != 0 && prevSize == newSize)
++        return 1;
++      MatchFinder_FreeThisClassMemory(p, alloc);
++      p->hash = AllocRefs(newSize, alloc);
++      if (p->hash != 0)
++      {
++        p->son = p->hash + p->hashSizeSum;
++        return 1;
++      }
++    }
++  }
++  MatchFinder_Free(p, alloc);
++  return 0;
++}
++
++static void MatchFinder_SetLimits(CMatchFinder *p)
++{
++  UInt32 limit = kMaxValForNormalize - p->pos;
++  UInt32 limit2 = p->cyclicBufferSize - p->cyclicBufferPos;
++  if (limit2 < limit)
++    limit = limit2;
++  limit2 = p->streamPos - p->pos;
++  if (limit2 <= p->keepSizeAfter)
++  {
++    if (limit2 > 0)
++      limit2 = 1;
++  }
++  else
++    limit2 -= p->keepSizeAfter;
++  if (limit2 < limit)
++    limit = limit2;
++  {
++    UInt32 lenLimit = p->streamPos - p->pos;
++    if (lenLimit > p->matchMaxLen)
++      lenLimit = p->matchMaxLen;
++    p->lenLimit = lenLimit;
++  }
++  p->posLimit = p->pos + limit;
++}
++
++void MatchFinder_Init(CMatchFinder *p)
++{
++  UInt32 i;
++  for (i = 0; i < p->hashSizeSum; i++)
++    p->hash[i] = kEmptyHashValue;
++  p->cyclicBufferPos = 0;
++  p->buffer = p->bufferBase;
++  p->pos = p->streamPos = p->cyclicBufferSize;
++  p->result = SZ_OK;
++  p->streamEndWasReached = 0;
++  MatchFinder_ReadBlock(p);
++  MatchFinder_SetLimits(p);
++}
++
++static UInt32 MatchFinder_GetSubValue(CMatchFinder *p)
++{
++  return (p->pos - p->historySize - 1) & kNormalizeMask;
++}
++
++void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, UInt32 numItems)
++{
++  UInt32 i;
++  for (i = 0; i < numItems; i++)
++  {
++    UInt32 value = items[i];
++    if (value <= subValue)
++      value = kEmptyHashValue;
++    else
++      value -= subValue;
++    items[i] = value;
++  }
++}
++
++static void MatchFinder_Normalize(CMatchFinder *p)
++{
++  UInt32 subValue = MatchFinder_GetSubValue(p);
++  MatchFinder_Normalize3(subValue, p->hash, p->hashSizeSum + p->numSons);
++  MatchFinder_ReduceOffsets(p, subValue);
++}
++
++static void MatchFinder_CheckLimits(CMatchFinder *p)
++{
++  if (p->pos == kMaxValForNormalize)
++    MatchFinder_Normalize(p);
++  if (!p->streamEndWasReached && p->keepSizeAfter == p->streamPos - p->pos)
++    MatchFinder_CheckAndMoveAndRead(p);
++  if (p->cyclicBufferPos == p->cyclicBufferSize)
++    p->cyclicBufferPos = 0;
++  MatchFinder_SetLimits(p);
++}
++
++static UInt32 * Hc_GetMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son,
++    UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue,
++    UInt32 *distances, UInt32 maxLen)
++{
++  son[_cyclicBufferPos] = curMatch;
++  for (;;)
++  {
++    UInt32 delta = pos - curMatch;
++    if (cutValue-- == 0 || delta >= _cyclicBufferSize)
++      return distances;
++    {
++      const Byte *pb = cur - delta;
++      curMatch = son[_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)];
++      if (pb[maxLen] == cur[maxLen] && *pb == *cur)
++      {
++        UInt32 len = 0;
++        while (++len != lenLimit)
++          if (pb[len] != cur[len])
++            break;
++        if (maxLen < len)
++        {
++          *distances++ = maxLen = len;
++          *distances++ = delta - 1;
++          if (len == lenLimit)
++            return distances;
++        }
++      }
++    }
++  }
++}
++
++UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son,
++    UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue,
++    UInt32 *distances, UInt32 maxLen)
++{
++  CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1;
++  CLzRef *ptr1 = son + (_cyclicBufferPos << 1);
++  UInt32 len0 = 0, len1 = 0;
++  for (;;)
++  {
++    UInt32 delta = pos - curMatch;
++    if (cutValue-- == 0 || delta >= _cyclicBufferSize)
++    {
++      *ptr0 = *ptr1 = kEmptyHashValue;
++      return distances;
++    }
++    {
++      CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1);
++      const Byte *pb = cur - delta;
++      UInt32 len = (len0 < len1 ? len0 : len1);
++      if (pb[len] == cur[len])
++      {
++        if (++len != lenLimit && pb[len] == cur[len])
++          while (++len != lenLimit)
++            if (pb[len] != cur[len])
++              break;
++        if (maxLen < len)
++        {
++          *distances++ = maxLen = len;
++          *distances++ = delta - 1;
++          if (len == lenLimit)
++          {
++            *ptr1 = pair[0];
++            *ptr0 = pair[1];
++            return distances;
++          }
++        }
++      }
++      if (pb[len] < cur[len])
++      {
++        *ptr1 = curMatch;
++        ptr1 = pair + 1;
++        curMatch = *ptr1;
++        len1 = len;
++      }
++      else
++      {
++        *ptr0 = curMatch;
++        ptr0 = pair;
++        curMatch = *ptr0;
++        len0 = len;
++      }
++    }
++  }
++}
++
++static void SkipMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son,
++    UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue)
++{
++  CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1;
++  CLzRef *ptr1 = son + (_cyclicBufferPos << 1);
++  UInt32 len0 = 0, len1 = 0;
++  for (;;)
++  {
++    UInt32 delta = pos - curMatch;
++    if (cutValue-- == 0 || delta >= _cyclicBufferSize)
++    {
++      *ptr0 = *ptr1 = kEmptyHashValue;
++      return;
++    }
++    {
++      CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1);
++      const Byte *pb = cur - delta;
++      UInt32 len = (len0 < len1 ? len0 : len1);
++      if (pb[len] == cur[len])
++      {
++        while (++len != lenLimit)
++          if (pb[len] != cur[len])
++            break;
++        {
++          if (len == lenLimit)
++          {
++            *ptr1 = pair[0];
++            *ptr0 = pair[1];
++            return;
++          }
++        }
++      }
++      if (pb[len] < cur[len])
++      {
++        *ptr1 = curMatch;
++        ptr1 = pair + 1;
++        curMatch = *ptr1;
++        len1 = len;
++      }
++      else
++      {
++        *ptr0 = curMatch;
++        ptr0 = pair;
++        curMatch = *ptr0;
++        len0 = len;
++      }
++    }
++  }
++}
++
++#define MOVE_POS \
++  ++p->cyclicBufferPos; \
++  p->buffer++; \
++  if (++p->pos == p->posLimit) MatchFinder_CheckLimits(p);
++
++#define MOVE_POS_RET MOVE_POS return offset;
++
++static void MatchFinder_MovePos(CMatchFinder *p) { MOVE_POS; }
++
++#define GET_MATCHES_HEADER2(minLen, ret_op) \
++  UInt32 lenLimit; UInt32 hashValue; const Byte *cur; UInt32 curMatch; \
++  lenLimit = p->lenLimit; { if (lenLimit < minLen) { MatchFinder_MovePos(p); ret_op; }} \
++  cur = p->buffer;
++
++#define GET_MATCHES_HEADER(minLen) GET_MATCHES_HEADER2(minLen, return 0)
++#define SKIP_HEADER(minLen)        GET_MATCHES_HEADER2(minLen, continue)
++
++#define MF_PARAMS(p) p->pos, p->buffer, p->son, p->cyclicBufferPos, p->cyclicBufferSize, p->cutValue
++
++#define GET_MATCHES_FOOTER(offset, maxLen) \
++  offset = (UInt32)(GetMatchesSpec1(lenLimit, curMatch, MF_PARAMS(p), \
++  distances + offset, maxLen) - distances); MOVE_POS_RET;
++
++#define SKIP_FOOTER \
++  SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); MOVE_POS;
++
++static UInt32 Bt2_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
++{
++  UInt32 offset;
++  GET_MATCHES_HEADER(2)
++  HASH2_CALC;
++  curMatch = p->hash[hashValue];
++  p->hash[hashValue] = p->pos;
++  offset = 0;
++  GET_MATCHES_FOOTER(offset, 1)
++}
++
++UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
++{
++  UInt32 offset;
++  GET_MATCHES_HEADER(3)
++  HASH_ZIP_CALC;
++  curMatch = p->hash[hashValue];
++  p->hash[hashValue] = p->pos;
++  offset = 0;
++  GET_MATCHES_FOOTER(offset, 2)
++}
++
++static UInt32 Bt3_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
++{
++  UInt32 hash2Value, delta2, maxLen, offset;
++  GET_MATCHES_HEADER(3)
++
++  HASH3_CALC;
++
++  delta2 = p->pos - p->hash[hash2Value];
++  curMatch = p->hash[kFix3HashSize + hashValue];
++
++  p->hash[hash2Value] =
++  p->hash[kFix3HashSize + hashValue] = p->pos;
++
++
++  maxLen = 2;
++  offset = 0;
++  if (delta2 < p->cyclicBufferSize && *(cur - delta2) == *cur)
++  {
++    for (; maxLen != lenLimit; maxLen++)
++      if (cur[(ptrdiff_t)maxLen - delta2] != cur[maxLen])
++        break;
++    distances[0] = maxLen;
++    distances[1] = delta2 - 1;
++    offset = 2;
++    if (maxLen == lenLimit)
++    {
++      SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p));
++      MOVE_POS_RET;
++    }
++  }
++  GET_MATCHES_FOOTER(offset, maxLen)
++}
++
++static UInt32 Bt4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
++{
++  UInt32 hash2Value, hash3Value, delta2, delta3, maxLen, offset;
++  GET_MATCHES_HEADER(4)
++
++  HASH4_CALC;
++
++  delta2 = p->pos - p->hash[                hash2Value];
++  delta3 = p->pos - p->hash[kFix3HashSize + hash3Value];
++  curMatch = p->hash[kFix4HashSize + hashValue];
++
++  p->hash[                hash2Value] =
++  p->hash[kFix3HashSize + hash3Value] =
++  p->hash[kFix4HashSize + hashValue] = p->pos;
++
++  maxLen = 1;
++  offset = 0;
++  if (delta2 < p->cyclicBufferSize && *(cur - delta2) == *cur)
++  {
++    distances[0] = maxLen = 2;
++    distances[1] = delta2 - 1;
++    offset = 2;
++  }
++  if (delta2 != delta3 && delta3 < p->cyclicBufferSize && *(cur - delta3) == *cur)
++  {
++    maxLen = 3;
++    distances[offset + 1] = delta3 - 1;
++    offset += 2;
++    delta2 = delta3;
++  }
++  if (offset != 0)
++  {
++    for (; maxLen != lenLimit; maxLen++)
++      if (cur[(ptrdiff_t)maxLen - delta2] != cur[maxLen])
++        break;
++    distances[offset - 2] = maxLen;
++    if (maxLen == lenLimit)
++    {
++      SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p));
++      MOVE_POS_RET;
++    }
++  }
++  if (maxLen < 3)
++    maxLen = 3;
++  GET_MATCHES_FOOTER(offset, maxLen)
++}
++
++static UInt32 Hc4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
++{
++  UInt32 hash2Value, hash3Value, delta2, delta3, maxLen, offset;
++  GET_MATCHES_HEADER(4)
++
++  HASH4_CALC;
++
++  delta2 = p->pos - p->hash[                hash2Value];
++  delta3 = p->pos - p->hash[kFix3HashSize + hash3Value];
++  curMatch = p->hash[kFix4HashSize + hashValue];
++
++  p->hash[                hash2Value] =
++  p->hash[kFix3HashSize + hash3Value] =
++  p->hash[kFix4HashSize + hashValue] = p->pos;
++
++  maxLen = 1;
++  offset = 0;
++  if (delta2 < p->cyclicBufferSize && *(cur - delta2) == *cur)
++  {
++    distances[0] = maxLen = 2;
++    distances[1] = delta2 - 1;
++    offset = 2;
++  }
++  if (delta2 != delta3 && delta3 < p->cyclicBufferSize && *(cur - delta3) == *cur)
++  {
++    maxLen = 3;
++    distances[offset + 1] = delta3 - 1;
++    offset += 2;
++    delta2 = delta3;
++  }
++  if (offset != 0)
++  {
++    for (; maxLen != lenLimit; maxLen++)
++      if (cur[(ptrdiff_t)maxLen - delta2] != cur[maxLen])
++        break;
++    distances[offset - 2] = maxLen;
++    if (maxLen == lenLimit)
++    {
++      p->son[p->cyclicBufferPos] = curMatch;
++      MOVE_POS_RET;
++    }
++  }
++  if (maxLen < 3)
++    maxLen = 3;
++  offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p),
++    distances + offset, maxLen) - (distances));
++  MOVE_POS_RET
++}
++
++UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
++{
++  UInt32 offset;
++  GET_MATCHES_HEADER(3)
++  HASH_ZIP_CALC;
++  curMatch = p->hash[hashValue];
++  p->hash[hashValue] = p->pos;
++  offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p),
++    distances, 2) - (distances));
++  MOVE_POS_RET
++}
++
++static void Bt2_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
++{
++  do
++  {
++    SKIP_HEADER(2)
++    HASH2_CALC;
++    curMatch = p->hash[hashValue];
++    p->hash[hashValue] = p->pos;
++    SKIP_FOOTER
++  }
++  while (--num != 0);
++}
++
++void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
++{
++  do
++  {
++    SKIP_HEADER(3)
++    HASH_ZIP_CALC;
++    curMatch = p->hash[hashValue];
++    p->hash[hashValue] = p->pos;
++    SKIP_FOOTER
++  }
++  while (--num != 0);
++}
++
++static void Bt3_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
++{
++  do
++  {
++    UInt32 hash2Value;
++    SKIP_HEADER(3)
++    HASH3_CALC;
++    curMatch = p->hash[kFix3HashSize + hashValue];
++    p->hash[hash2Value] =
++    p->hash[kFix3HashSize + hashValue] = p->pos;
++    SKIP_FOOTER
++  }
++  while (--num != 0);
++}
++
++static void Bt4_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
++{
++  do
++  {
++    UInt32 hash2Value, hash3Value;
++    SKIP_HEADER(4)
++    HASH4_CALC;
++    curMatch = p->hash[kFix4HashSize + hashValue];
++    p->hash[                hash2Value] =
++    p->hash[kFix3HashSize + hash3Value] = p->pos;
++    p->hash[kFix4HashSize + hashValue] = p->pos;
++    SKIP_FOOTER
++  }
++  while (--num != 0);
++}
++
++static void Hc4_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
++{
++  do
++  {
++    UInt32 hash2Value, hash3Value;
++    SKIP_HEADER(4)
++    HASH4_CALC;
++    curMatch = p->hash[kFix4HashSize + hashValue];
++    p->hash[                hash2Value] =
++    p->hash[kFix3HashSize + hash3Value] =
++    p->hash[kFix4HashSize + hashValue] = p->pos;
++    p->son[p->cyclicBufferPos] = curMatch;
++    MOVE_POS
++  }
++  while (--num != 0);
++}
++
++void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
++{
++  do
++  {
++    SKIP_HEADER(3)
++    HASH_ZIP_CALC;
++    curMatch = p->hash[hashValue];
++    p->hash[hashValue] = p->pos;
++    p->son[p->cyclicBufferPos] = curMatch;
++    MOVE_POS
++  }
++  while (--num != 0);
++}
++
++void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable)
++{
++  vTable->Init = (Mf_Init_Func)MatchFinder_Init;
++  vTable->GetIndexByte = (Mf_GetIndexByte_Func)MatchFinder_GetIndexByte;
++  vTable->GetNumAvailableBytes = (Mf_GetNumAvailableBytes_Func)MatchFinder_GetNumAvailableBytes;
++  vTable->GetPointerToCurrentPos = (Mf_GetPointerToCurrentPos_Func)MatchFinder_GetPointerToCurrentPos;
++  if (!p->btMode)
++  {
++    vTable->GetMatches = (Mf_GetMatches_Func)Hc4_MatchFinder_GetMatches;
++    vTable->Skip = (Mf_Skip_Func)Hc4_MatchFinder_Skip;
++  }
++  else if (p->numHashBytes == 2)
++  {
++    vTable->GetMatches = (Mf_GetMatches_Func)Bt2_MatchFinder_GetMatches;
++    vTable->Skip = (Mf_Skip_Func)Bt2_MatchFinder_Skip;
++  }
++  else if (p->numHashBytes == 3)
++  {
++    vTable->GetMatches = (Mf_GetMatches_Func)Bt3_MatchFinder_GetMatches;
++    vTable->Skip = (Mf_Skip_Func)Bt3_MatchFinder_Skip;
++  }
++  else
++  {
++    vTable->GetMatches = (Mf_GetMatches_Func)Bt4_MatchFinder_GetMatches;
++    vTable->Skip = (Mf_Skip_Func)Bt4_MatchFinder_Skip;
++  }
++}
+diff --git a/lib/lzma/LzmaDec.c b/lib/lzma/LzmaDec.c
+new file mode 100644
+index 000000000000..3fab58344239
+--- /dev/null
++++ b/lib/lzma/LzmaDec.c
+@@ -0,0 +1,999 @@
++/* LzmaDec.c -- LZMA Decoder
++2009-09-20 : Igor Pavlov : Public domain */
++
++#include "LzmaDec.h"
++
++#include <string.h>
++
++#define kNumTopBits 24
++#define kTopValue ((UInt32)1 << kNumTopBits)
++
++#define kNumBitModelTotalBits 11
++#define kBitModelTotal (1 << kNumBitModelTotalBits)
++#define kNumMoveBits 5
++
++#define RC_INIT_SIZE 5
++
++#define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); }
++
++#define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound)
++#define UPDATE_0(p) range = bound; *(p) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits));
++#define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits));
++#define GET_BIT2(p, i, A0, A1) IF_BIT_0(p) \
++  { UPDATE_0(p); i = (i + i); A0; } else \
++  { UPDATE_1(p); i = (i + i) + 1; A1; }
++#define GET_BIT(p, i) GET_BIT2(p, i, ; , ;)
++
++#define TREE_GET_BIT(probs, i) { GET_BIT((probs + i), i); }
++#define TREE_DECODE(probs, limit, i) \
++  { i = 1; do { TREE_GET_BIT(probs, i); } while (i < limit); i -= limit; }
++
++/* #define _LZMA_SIZE_OPT */
++
++#ifdef _LZMA_SIZE_OPT
++#define TREE_6_DECODE(probs, i) TREE_DECODE(probs, (1 << 6), i)
++#else
++#define TREE_6_DECODE(probs, i) \
++  { i = 1; \
++  TREE_GET_BIT(probs, i); \
++  TREE_GET_BIT(probs, i); \
++  TREE_GET_BIT(probs, i); \
++  TREE_GET_BIT(probs, i); \
++  TREE_GET_BIT(probs, i); \
++  TREE_GET_BIT(probs, i); \
++  i -= 0x40; }
++#endif
++
++#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_ERROR; range <<= 8; code = (code << 8) | (*buf++); }
++
++#define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound)
++#define UPDATE_0_CHECK range = bound;
++#define UPDATE_1_CHECK range -= bound; code -= bound;
++#define GET_BIT2_CHECK(p, i, A0, A1) IF_BIT_0_CHECK(p) \
++  { UPDATE_0_CHECK; i = (i + i); A0; } else \
++  { UPDATE_1_CHECK; i = (i + i) + 1; A1; }
++#define GET_BIT_CHECK(p, i) GET_BIT2_CHECK(p, i, ; , ;)
++#define TREE_DECODE_CHECK(probs, limit, i) \
++  { i = 1; do { GET_BIT_CHECK(probs + i, i) } while (i < limit); i -= limit; }
++
++
++#define kNumPosBitsMax 4
++#define kNumPosStatesMax (1 << kNumPosBitsMax)
++
++#define kLenNumLowBits 3
++#define kLenNumLowSymbols (1 << kLenNumLowBits)
++#define kLenNumMidBits 3
++#define kLenNumMidSymbols (1 << kLenNumMidBits)
++#define kLenNumHighBits 8
++#define kLenNumHighSymbols (1 << kLenNumHighBits)
++
++#define LenChoice 0
++#define LenChoice2 (LenChoice + 1)
++#define LenLow (LenChoice2 + 1)
++#define LenMid (LenLow + (kNumPosStatesMax << kLenNumLowBits))
++#define LenHigh (LenMid + (kNumPosStatesMax << kLenNumMidBits))
++#define kNumLenProbs (LenHigh + kLenNumHighSymbols)
++
++
++#define kNumStates 12
++#define kNumLitStates 7
++
++#define kStartPosModelIndex 4
++#define kEndPosModelIndex 14
++#define kNumFullDistances (1 << (kEndPosModelIndex >> 1))
++
++#define kNumPosSlotBits 6
++#define kNumLenToPosStates 4
++
++#define kNumAlignBits 4
++#define kAlignTableSize (1 << kNumAlignBits)
++
++#define kMatchMinLen 2
++#define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols)
++
++#define IsMatch 0
++#define IsRep (IsMatch + (kNumStates << kNumPosBitsMax))
++#define IsRepG0 (IsRep + kNumStates)
++#define IsRepG1 (IsRepG0 + kNumStates)
++#define IsRepG2 (IsRepG1 + kNumStates)
++#define IsRep0Long (IsRepG2 + kNumStates)
++#define PosSlot (IsRep0Long + (kNumStates << kNumPosBitsMax))
++#define SpecPos (PosSlot + (kNumLenToPosStates << kNumPosSlotBits))
++#define Align (SpecPos + kNumFullDistances - kEndPosModelIndex)
++#define LenCoder (Align + kAlignTableSize)
++#define RepLenCoder (LenCoder + kNumLenProbs)
++#define Literal (RepLenCoder + kNumLenProbs)
++
++#define LZMA_BASE_SIZE 1846
++#define LZMA_LIT_SIZE 768
++
++#define LzmaProps_GetNumProbs(p) ((UInt32)LZMA_BASE_SIZE + (LZMA_LIT_SIZE << ((p)->lc + (p)->lp)))
++
++#if Literal != LZMA_BASE_SIZE
++StopCompilingDueBUG
++#endif
++
++#define LZMA_DIC_MIN (1 << 12)
++
++/* First LZMA-symbol is always decoded.
++And it decodes new LZMA-symbols while (buf < bufLimit), but "buf" is without last normalization
++Out:
++  Result:
++    SZ_OK - OK
++    SZ_ERROR_DATA - Error
++  p->remainLen:
++    < kMatchSpecLenStart : normal remain
++    = kMatchSpecLenStart : finished
++    = kMatchSpecLenStart + 1 : Flush marker
++    = kMatchSpecLenStart + 2 : State Init Marker
++*/
++
++static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte *bufLimit)
++{
++  CLzmaProb *probs = p->probs;
++
++  unsigned state = p->state;
++  UInt32 rep0 = p->reps[0], rep1 = p->reps[1], rep2 = p->reps[2], rep3 = p->reps[3];
++  unsigned pbMask = ((unsigned)1 << (p->prop.pb)) - 1;
++  unsigned lpMask = ((unsigned)1 << (p->prop.lp)) - 1;
++  unsigned lc = p->prop.lc;
++
++  Byte *dic = p->dic;
++  SizeT dicBufSize = p->dicBufSize;
++  SizeT dicPos = p->dicPos;
++
++  UInt32 processedPos = p->processedPos;
++  UInt32 checkDicSize = p->checkDicSize;
++  unsigned len = 0;
++
++  const Byte *buf = p->buf;
++  UInt32 range = p->range;
++  UInt32 code = p->code;
++
++  do
++  {
++    CLzmaProb *prob;
++    UInt32 bound;
++    unsigned ttt;
++    unsigned posState = processedPos & pbMask;
++
++    prob = probs + IsMatch + (state << kNumPosBitsMax) + posState;
++    IF_BIT_0(prob)
++    {
++      unsigned symbol;
++      UPDATE_0(prob);
++      prob = probs + Literal;
++      if (checkDicSize != 0 || processedPos != 0)
++        prob += (LZMA_LIT_SIZE * (((processedPos & lpMask) << lc) +
++        (dic[(dicPos == 0 ? dicBufSize : dicPos) - 1] >> (8 - lc))));
++
++      if (state < kNumLitStates)
++      {
++        state -= (state < 4) ? state : 3;
++        symbol = 1;
++        do { GET_BIT(prob + symbol, symbol) } while (symbol < 0x100);
++      }
++      else
++      {
++        unsigned matchByte = p->dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)];
++        unsigned offs = 0x100;
++        state -= (state < 10) ? 3 : 6;
++        symbol = 1;
++        do
++        {
++          unsigned bit;
++          CLzmaProb *probLit;
++          matchByte <<= 1;
++          bit = (matchByte & offs);
++          probLit = prob + offs + bit + symbol;
++          GET_BIT2(probLit, symbol, offs &= ~bit, offs &= bit)
++        }
++        while (symbol < 0x100);
++      }
++      dic[dicPos++] = (Byte)symbol;
++      processedPos++;
++      continue;
++    }
++    else
++    {
++      UPDATE_1(prob);
++      prob = probs + IsRep + state;
++      IF_BIT_0(prob)
++      {
++        UPDATE_0(prob);
++        state += kNumStates;
++        prob = probs + LenCoder;
++      }
++      else
++      {
++        UPDATE_1(prob);
++        if (checkDicSize == 0 && processedPos == 0)
++          return SZ_ERROR_DATA;
++        prob = probs + IsRepG0 + state;
++        IF_BIT_0(prob)
++        {
++          UPDATE_0(prob);
++          prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState;
++          IF_BIT_0(prob)
++          {
++            UPDATE_0(prob);
++            dic[dicPos] = dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)];
++            dicPos++;
++            processedPos++;
++            state = state < kNumLitStates ? 9 : 11;
++            continue;
++          }
++          UPDATE_1(prob);
++        }
++        else
++        {
++          UInt32 distance;
++          UPDATE_1(prob);
++          prob = probs + IsRepG1 + state;
++          IF_BIT_0(prob)
++          {
++            UPDATE_0(prob);
++            distance = rep1;
++          }
++          else
++          {
++            UPDATE_1(prob);
++            prob = probs + IsRepG2 + state;
++            IF_BIT_0(prob)
++            {
++              UPDATE_0(prob);
++              distance = rep2;
++            }
++            else
++            {
++              UPDATE_1(prob);
++              distance = rep3;
++              rep3 = rep2;
++            }
++            rep2 = rep1;
++          }
++          rep1 = rep0;
++          rep0 = distance;
++        }
++        state = state < kNumLitStates ? 8 : 11;
++        prob = probs + RepLenCoder;
++      }
++      {
++        unsigned limit, offset;
++        CLzmaProb *probLen = prob + LenChoice;
++        IF_BIT_0(probLen)
++        {
++          UPDATE_0(probLen);
++          probLen = prob + LenLow + (posState << kLenNumLowBits);
++          offset = 0;
++          limit = (1 << kLenNumLowBits);
++        }
++        else
++        {
++          UPDATE_1(probLen);
++          probLen = prob + LenChoice2;
++          IF_BIT_0(probLen)
++          {
++            UPDATE_0(probLen);
++            probLen = prob + LenMid + (posState << kLenNumMidBits);
++            offset = kLenNumLowSymbols;
++            limit = (1 << kLenNumMidBits);
++          }
++          else
++          {
++            UPDATE_1(probLen);
++            probLen = prob + LenHigh;
++            offset = kLenNumLowSymbols + kLenNumMidSymbols;
++            limit = (1 << kLenNumHighBits);
++          }
++        }
++        TREE_DECODE(probLen, limit, len);
++        len += offset;
++      }
++
++      if (state >= kNumStates)
++      {
++        UInt32 distance;
++        prob = probs + PosSlot +
++            ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << kNumPosSlotBits);
++        TREE_6_DECODE(prob, distance);
++        if (distance >= kStartPosModelIndex)
++        {
++          unsigned posSlot = (unsigned)distance;
++          int numDirectBits = (int)(((distance >> 1) - 1));
++          distance = (2 | (distance & 1));
++          if (posSlot < kEndPosModelIndex)
++          {
++            distance <<= numDirectBits;
++            prob = probs + SpecPos + distance - posSlot - 1;
++            {
++              UInt32 mask = 1;
++              unsigned i = 1;
++              do
++              {
++                GET_BIT2(prob + i, i, ; , distance |= mask);
++                mask <<= 1;
++              }
++              while (--numDirectBits != 0);
++            }
++          }
++          else
++          {
++            numDirectBits -= kNumAlignBits;
++            do
++            {
++              NORMALIZE
++              range >>= 1;
++
++              {
++                UInt32 t;
++                code -= range;
++                t = (0 - ((UInt32)code >> 31)); /* (UInt32)((Int32)code >> 31) */
++                distance = (distance << 1) + (t + 1);
++                code += range & t;
++              }
++              /*
++              distance <<= 1;
++              if (code >= range)
++              {
++                code -= range;
++                distance |= 1;
++              }
++              */
++            }
++            while (--numDirectBits != 0);
++            prob = probs + Align;
++            distance <<= kNumAlignBits;
++            {
++              unsigned i = 1;
++              GET_BIT2(prob + i, i, ; , distance |= 1);
++              GET_BIT2(prob + i, i, ; , distance |= 2);
++              GET_BIT2(prob + i, i, ; , distance |= 4);
++              GET_BIT2(prob + i, i, ; , distance |= 8);
++            }
++            if (distance == (UInt32)0xFFFFFFFF)
++            {
++              len += kMatchSpecLenStart;
++              state -= kNumStates;
++              break;
++            }
++          }
++        }
++        rep3 = rep2;
++        rep2 = rep1;
++        rep1 = rep0;
++        rep0 = distance + 1;
++        if (checkDicSize == 0)
++        {
++          if (distance >= processedPos)
++            return SZ_ERROR_DATA;
++        }
++        else if (distance >= checkDicSize)
++          return SZ_ERROR_DATA;
++        state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3;
++      }
++
++      len += kMatchMinLen;
++
++      if (limit == dicPos)
++        return SZ_ERROR_DATA;
++      {
++        SizeT rem = limit - dicPos;
++        unsigned curLen = ((rem < len) ? (unsigned)rem : len);
++        SizeT pos = (dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0);
++
++        processedPos += curLen;
++
++        len -= curLen;
++        if (pos + curLen <= dicBufSize)
++        {
++          Byte *dest = dic + dicPos;
++          ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dicPos;
++          const Byte *lim = dest + curLen;
++          dicPos += curLen;
++          do
++            *(dest) = (Byte)*(dest + src);
++          while (++dest != lim);
++        }
++        else
++        {
++          do
++          {
++            dic[dicPos++] = dic[pos];
++            if (++pos == dicBufSize)
++              pos = 0;
++          }
++          while (--curLen != 0);
++        }
++      }
++    }
++  }
++  while (dicPos < limit && buf < bufLimit);
++  NORMALIZE;
++  p->buf = buf;
++  p->range = range;
++  p->code = code;
++  p->remainLen = len;
++  p->dicPos = dicPos;
++  p->processedPos = processedPos;
++  p->reps[0] = rep0;
++  p->reps[1] = rep1;
++  p->reps[2] = rep2;
++  p->reps[3] = rep3;
++  p->state = state;
++
++  return SZ_OK;
++}
++
++static void MY_FAST_CALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit)
++{
++  if (p->remainLen != 0 && p->remainLen < kMatchSpecLenStart)
++  {
++    Byte *dic = p->dic;
++    SizeT dicPos = p->dicPos;
++    SizeT dicBufSize = p->dicBufSize;
++    unsigned len = p->remainLen;
++    UInt32 rep0 = p->reps[0];
++    if (limit - dicPos < len)
++      len = (unsigned)(limit - dicPos);
++
++    if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len)
++      p->checkDicSize = p->prop.dicSize;
++
++    p->processedPos += len;
++    p->remainLen -= len;
++    while (len-- != 0)
++    {
++      dic[dicPos] = dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)];
++      dicPos++;
++    }
++    p->dicPos = dicPos;
++  }
++}
++
++static int MY_FAST_CALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit)
++{
++  do
++  {
++    SizeT limit2 = limit;
++    if (p->checkDicSize == 0)
++    {
++      UInt32 rem = p->prop.dicSize - p->processedPos;
++      if (limit - p->dicPos > rem)
++        limit2 = p->dicPos + rem;
++    }
++    RINOK(LzmaDec_DecodeReal(p, limit2, bufLimit));
++    if (p->processedPos >= p->prop.dicSize)
++      p->checkDicSize = p->prop.dicSize;
++    LzmaDec_WriteRem(p, limit);
++  }
++  while (p->dicPos < limit && p->buf < bufLimit && p->remainLen < kMatchSpecLenStart);
++
++  if (p->remainLen > kMatchSpecLenStart)
++  {
++    p->remainLen = kMatchSpecLenStart;
++  }
++  return 0;
++}
++
++typedef enum
++{
++  DUMMY_ERROR, /* unexpected end of input stream */
++  DUMMY_LIT,
++  DUMMY_MATCH,
++  DUMMY_REP
++} ELzmaDummy;
++
++static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inSize)
++{
++  UInt32 range = p->range;
++  UInt32 code = p->code;
++  const Byte *bufLimit = buf + inSize;
++  CLzmaProb *probs = p->probs;
++  unsigned state = p->state;
++  ELzmaDummy res;
++
++  {
++    CLzmaProb *prob;
++    UInt32 bound;
++    unsigned ttt;
++    unsigned posState = (p->processedPos) & ((1 << p->prop.pb) - 1);
++
++    prob = probs + IsMatch + (state << kNumPosBitsMax) + posState;
++    IF_BIT_0_CHECK(prob)
++    {
++      UPDATE_0_CHECK
++
++      /* if (bufLimit - buf >= 7) return DUMMY_LIT; */
++
++      prob = probs + Literal;
++      if (p->checkDicSize != 0 || p->processedPos != 0)
++        prob += (LZMA_LIT_SIZE *
++          ((((p->processedPos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) +
++          (p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc))));
++
++      if (state < kNumLitStates)
++      {
++        unsigned symbol = 1;
++        do { GET_BIT_CHECK(prob + symbol, symbol) } while (symbol < 0x100);
++      }
++      else
++      {
++        unsigned matchByte = p->dic[p->dicPos - p->reps[0] +
++            ((p->dicPos < p->reps[0]) ? p->dicBufSize : 0)];
++        unsigned offs = 0x100;
++        unsigned symbol = 1;
++        do
++        {
++          unsigned bit;
++          CLzmaProb *probLit;
++          matchByte <<= 1;
++          bit = (matchByte & offs);
++          probLit = prob + offs + bit + symbol;
++          GET_BIT2_CHECK(probLit, symbol, offs &= ~bit, offs &= bit)
++        }
++        while (symbol < 0x100);
++      }
++      res = DUMMY_LIT;
++    }
++    else
++    {
++      unsigned len;
++      UPDATE_1_CHECK;
++
++      prob = probs + IsRep + state;
++      IF_BIT_0_CHECK(prob)
++      {
++        UPDATE_0_CHECK;
++        state = 0;
++        prob = probs + LenCoder;
++        res = DUMMY_MATCH;
++      }
++      else
++      {
++        UPDATE_1_CHECK;
++        res = DUMMY_REP;
++        prob = probs + IsRepG0 + state;
++        IF_BIT_0_CHECK(prob)
++        {
++          UPDATE_0_CHECK;
++          prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState;
++          IF_BIT_0_CHECK(prob)
++          {
++            UPDATE_0_CHECK;
++            NORMALIZE_CHECK;
++            return DUMMY_REP;
++          }
++          else
++          {
++            UPDATE_1_CHECK;
++          }
++        }
++        else
++        {
++          UPDATE_1_CHECK;
++          prob = probs + IsRepG1 + state;
++          IF_BIT_0_CHECK(prob)
++          {
++            UPDATE_0_CHECK;
++          }
++          else
++          {
++            UPDATE_1_CHECK;
++            prob = probs + IsRepG2 + state;
++            IF_BIT_0_CHECK(prob)
++            {
++              UPDATE_0_CHECK;
++            }
++            else
++            {
++              UPDATE_1_CHECK;
++            }
++          }
++        }
++        state = kNumStates;
++        prob = probs + RepLenCoder;
++      }
++      {
++        unsigned limit, offset;
++        CLzmaProb *probLen = prob + LenChoice;
++        IF_BIT_0_CHECK(probLen)
++        {
++          UPDATE_0_CHECK;
++          probLen = prob + LenLow + (posState << kLenNumLowBits);
++          offset = 0;
++          limit = 1 << kLenNumLowBits;
++        }
++        else
++        {
++          UPDATE_1_CHECK;
++          probLen = prob + LenChoice2;
++          IF_BIT_0_CHECK(probLen)
++          {
++            UPDATE_0_CHECK;
++            probLen = prob + LenMid + (posState << kLenNumMidBits);
++            offset = kLenNumLowSymbols;
++            limit = 1 << kLenNumMidBits;
++          }
++          else
++          {
++            UPDATE_1_CHECK;
++            probLen = prob + LenHigh;
++            offset = kLenNumLowSymbols + kLenNumMidSymbols;
++            limit = 1 << kLenNumHighBits;
++          }
++        }
++        TREE_DECODE_CHECK(probLen, limit, len);
++        len += offset;
++      }
++
++      if (state < 4)
++      {
++        unsigned posSlot;
++        prob = probs + PosSlot +
++            ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) <<
++            kNumPosSlotBits);
++        TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot);
++        if (posSlot >= kStartPosModelIndex)
++        {
++          int numDirectBits = ((posSlot >> 1) - 1);
++
++          /* if (bufLimit - buf >= 8) return DUMMY_MATCH; */
++
++          if (posSlot < kEndPosModelIndex)
++          {
++            prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits) - posSlot - 1;
++          }
++          else
++          {
++            numDirectBits -= kNumAlignBits;
++            do
++            {
++              NORMALIZE_CHECK
++              range >>= 1;
++              code -= range & (((code - range) >> 31) - 1);
++              /* if (code >= range) code -= range; */
++            }
++            while (--numDirectBits != 0);
++            prob = probs + Align;
++            numDirectBits = kNumAlignBits;
++          }
++          {
++            unsigned i = 1;
++            do
++            {
++              GET_BIT_CHECK(prob + i, i);
++            }
++            while (--numDirectBits != 0);
++          }
++        }
++      }
++    }
++  }
++  NORMALIZE_CHECK;
++  return res;
++}
++
++
++static void LzmaDec_InitRc(CLzmaDec *p, const Byte *data)
++{
++  p->code = ((UInt32)data[1] << 24) | ((UInt32)data[2] << 16) | ((UInt32)data[3] << 8) | ((UInt32)data[4]);
++  p->range = 0xFFFFFFFF;
++  p->needFlush = 0;
++}
++
++void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState)
++{
++  p->needFlush = 1;
++  p->remainLen = 0;
++  p->tempBufSize = 0;
++
++  if (initDic)
++  {
++    p->processedPos = 0;
++    p->checkDicSize = 0;
++    p->needInitState = 1;
++  }
++  if (initState)
++    p->needInitState = 1;
++}
++
++void LzmaDec_Init(CLzmaDec *p)
++{
++  p->dicPos = 0;
++  LzmaDec_InitDicAndState(p, True, True);
++}
++
++static void LzmaDec_InitStateReal(CLzmaDec *p)
++{
++  UInt32 numProbs = Literal + ((UInt32)LZMA_LIT_SIZE << (p->prop.lc + p->prop.lp));
++  UInt32 i;
++  CLzmaProb *probs = p->probs;
++  for (i = 0; i < numProbs; i++)
++    probs[i] = kBitModelTotal >> 1;
++  p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1;
++  p->state = 0;
++  p->needInitState = 0;
++}
++
++SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen,
++    ELzmaFinishMode finishMode, ELzmaStatus *status)
++{
++  SizeT inSize = *srcLen;
++  (*srcLen) = 0;
++  LzmaDec_WriteRem(p, dicLimit);
++
++  *status = LZMA_STATUS_NOT_SPECIFIED;
++
++  while (p->remainLen != kMatchSpecLenStart)
++  {
++      int checkEndMarkNow;
++
++      if (p->needFlush != 0)
++      {
++        for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--)
++          p->tempBuf[p->tempBufSize++] = *src++;
++        if (p->tempBufSize < RC_INIT_SIZE)
++        {
++          *status = LZMA_STATUS_NEEDS_MORE_INPUT;
++          return SZ_OK;
++        }
++        if (p->tempBuf[0] != 0)
++          return SZ_ERROR_DATA;
++
++        LzmaDec_InitRc(p, p->tempBuf);
++        p->tempBufSize = 0;
++      }
++
++      checkEndMarkNow = 0;
++      if (p->dicPos >= dicLimit)
++      {
++        if (p->remainLen == 0 && p->code == 0)
++        {
++          *status = LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK;
++          return SZ_OK;
++        }
++        if (finishMode == LZMA_FINISH_ANY)
++        {
++          *status = LZMA_STATUS_NOT_FINISHED;
++          return SZ_OK;
++        }
++        if (p->remainLen != 0)
++        {
++          *status = LZMA_STATUS_NOT_FINISHED;
++          return SZ_ERROR_DATA;
++        }
++        checkEndMarkNow = 1;
++      }
++
++      if (p->needInitState)
++        LzmaDec_InitStateReal(p);
++
++      if (p->tempBufSize == 0)
++      {
++        SizeT processed;
++        const Byte *bufLimit;
++        if (inSize < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow)
++        {
++          int dummyRes = LzmaDec_TryDummy(p, src, inSize);
++          if (dummyRes == DUMMY_ERROR)
++          {
++            memcpy(p->tempBuf, src, inSize);
++            p->tempBufSize = (unsigned)inSize;
++            (*srcLen) += inSize;
++            *status = LZMA_STATUS_NEEDS_MORE_INPUT;
++            return SZ_OK;
++          }
++          if (checkEndMarkNow && dummyRes != DUMMY_MATCH)
++          {
++            *status = LZMA_STATUS_NOT_FINISHED;
++            return SZ_ERROR_DATA;
++          }
++          bufLimit = src;
++        }
++        else
++          bufLimit = src + inSize - LZMA_REQUIRED_INPUT_MAX;
++        p->buf = src;
++        if (LzmaDec_DecodeReal2(p, dicLimit, bufLimit) != 0)
++          return SZ_ERROR_DATA;
++        processed = (SizeT)(p->buf - src);
++        (*srcLen) += processed;
++        src += processed;
++        inSize -= processed;
++      }
++      else
++      {
++        unsigned rem = p->tempBufSize, lookAhead = 0;
++        while (rem < LZMA_REQUIRED_INPUT_MAX && lookAhead < inSize)
++          p->tempBuf[rem++] = src[lookAhead++];
++        p->tempBufSize = rem;
++        if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow)
++        {
++          int dummyRes = LzmaDec_TryDummy(p, p->tempBuf, rem);
++          if (dummyRes == DUMMY_ERROR)
++          {
++            (*srcLen) += lookAhead;
++            *status = LZMA_STATUS_NEEDS_MORE_INPUT;
++            return SZ_OK;
++          }
++          if (checkEndMarkNow && dummyRes != DUMMY_MATCH)
++          {
++            *status = LZMA_STATUS_NOT_FINISHED;
++            return SZ_ERROR_DATA;
++          }
++        }
++        p->buf = p->tempBuf;
++        if (LzmaDec_DecodeReal2(p, dicLimit, p->buf) != 0)
++          return SZ_ERROR_DATA;
++        lookAhead -= (rem - (unsigned)(p->buf - p->tempBuf));
++        (*srcLen) += lookAhead;
++        src += lookAhead;
++        inSize -= lookAhead;
++        p->tempBufSize = 0;
++      }
++  }
++  if (p->code == 0)
++    *status = LZMA_STATUS_FINISHED_WITH_MARK;
++  return (p->code == 0) ? SZ_OK : SZ_ERROR_DATA;
++}
++
++SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status)
++{
++  SizeT outSize = *destLen;
++  SizeT inSize = *srcLen;
++  *srcLen = *destLen = 0;
++  for (;;)
++  {
++    SizeT inSizeCur = inSize, outSizeCur, dicPos;
++    ELzmaFinishMode curFinishMode;
++    SRes res;
++    if (p->dicPos == p->dicBufSize)
++      p->dicPos = 0;
++    dicPos = p->dicPos;
++    if (outSize > p->dicBufSize - dicPos)
++    {
++      outSizeCur = p->dicBufSize;
++      curFinishMode = LZMA_FINISH_ANY;
++    }
++    else
++    {
++      outSizeCur = dicPos + outSize;
++      curFinishMode = finishMode;
++    }
++
++    res = LzmaDec_DecodeToDic(p, outSizeCur, src, &inSizeCur, curFinishMode, status);
++    src += inSizeCur;
++    inSize -= inSizeCur;
++    *srcLen += inSizeCur;
++    outSizeCur = p->dicPos - dicPos;
++    memcpy(dest, p->dic + dicPos, outSizeCur);
++    dest += outSizeCur;
++    outSize -= outSizeCur;
++    *destLen += outSizeCur;
++    if (res != 0)
++      return res;
++    if (outSizeCur == 0 || outSize == 0)
++      return SZ_OK;
++  }
++}
++
++void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc)
++{
++  alloc->Free(alloc, p->probs);
++  p->probs = 0;
++}
++
++static void LzmaDec_FreeDict(CLzmaDec *p, ISzAlloc *alloc)
++{
++  alloc->Free(alloc, p->dic);
++  p->dic = 0;
++}
++
++void LzmaDec_Free(CLzmaDec *p, ISzAlloc *alloc)
++{
++  LzmaDec_FreeProbs(p, alloc);
++  LzmaDec_FreeDict(p, alloc);
++}
++
++SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size)
++{
++  UInt32 dicSize;
++  Byte d;
++
++  if (size < LZMA_PROPS_SIZE)
++    return SZ_ERROR_UNSUPPORTED;
++  else
++    dicSize = data[1] | ((UInt32)data[2] << 8) | ((UInt32)data[3] << 16) | ((UInt32)data[4] << 24);
++
++  if (dicSize < LZMA_DIC_MIN)
++    dicSize = LZMA_DIC_MIN;
++  p->dicSize = dicSize;
++
++  d = data[0];
++  if (d >= (9 * 5 * 5))
++    return SZ_ERROR_UNSUPPORTED;
++
++  p->lc = d % 9;
++  d /= 9;
++  p->pb = d / 5;
++  p->lp = d % 5;
++
++  return SZ_OK;
++}
++
++static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAlloc *alloc)
++{
++  UInt32 numProbs = LzmaProps_GetNumProbs(propNew);
++  if (p->probs == 0 || numProbs != p->numProbs)
++  {
++    LzmaDec_FreeProbs(p, alloc);
++    p->probs = (CLzmaProb *)alloc->Alloc(alloc, numProbs * sizeof(CLzmaProb));
++    p->numProbs = numProbs;
++    if (p->probs == 0)
++      return SZ_ERROR_MEM;
++  }
++  return SZ_OK;
++}
++
++SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc)
++{
++  CLzmaProps propNew;
++  RINOK(LzmaProps_Decode(&propNew, props, propsSize));
++  RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc));
++  p->prop = propNew;
++  return SZ_OK;
++}
++
++SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc)
++{
++  CLzmaProps propNew;
++  SizeT dicBufSize;
++  RINOK(LzmaProps_Decode(&propNew, props, propsSize));
++  RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc));
++  dicBufSize = propNew.dicSize;
++  if (p->dic == 0 || dicBufSize != p->dicBufSize)
++  {
++    LzmaDec_FreeDict(p, alloc);
++    p->dic = (Byte *)alloc->Alloc(alloc, dicBufSize);
++    if (p->dic == 0)
++    {
++      LzmaDec_FreeProbs(p, alloc);
++      return SZ_ERROR_MEM;
++    }
++  }
++  p->dicBufSize = dicBufSize;
++  p->prop = propNew;
++  return SZ_OK;
++}
++
++SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen,
++    const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode,
++    ELzmaStatus *status, ISzAlloc *alloc)
++{
++  CLzmaDec p;
++  SRes res;
++  SizeT inSize = *srcLen;
++  SizeT outSize = *destLen;
++  *srcLen = *destLen = 0;
++  if (inSize < RC_INIT_SIZE)
++    return SZ_ERROR_INPUT_EOF;
++
++  LzmaDec_Construct(&p);
++  res = LzmaDec_AllocateProbs(&p, propData, propSize, alloc);
++  if (res != 0)
++    return res;
++  p.dic = dest;
++  p.dicBufSize = outSize;
++
++  LzmaDec_Init(&p);
++
++  *srcLen = inSize;
++  res = LzmaDec_DecodeToDic(&p, outSize, src, srcLen, finishMode, status);
++
++  if (res == SZ_OK && *status == LZMA_STATUS_NEEDS_MORE_INPUT)
++    res = SZ_ERROR_INPUT_EOF;
++
++  (*destLen) = p.dicPos;
++  LzmaDec_FreeProbs(&p, alloc);
++  return res;
++}
+diff --git a/lib/lzma/LzmaEnc.c b/lib/lzma/LzmaEnc.c
+new file mode 100644
+index 000000000000..ee47d23054de
+--- /dev/null
++++ b/lib/lzma/LzmaEnc.c
+@@ -0,0 +1,2271 @@
++/* LzmaEnc.c -- LZMA Encoder
++2009-11-24 : Igor Pavlov : Public domain */
++
++#include <string.h>
++
++/* #define SHOW_STAT */
++/* #define SHOW_STAT2 */
++
++#if defined(SHOW_STAT) || defined(SHOW_STAT2)
++#include <stdio.h>
++#endif
++
++#include "LzmaEnc.h"
++
++/* disable MT */
++#define _7ZIP_ST
++
++#include "LzFind.h"
++#ifndef _7ZIP_ST
++#include "LzFindMt.h"
++#endif
++
++#ifdef SHOW_STAT
++static int ttt = 0;
++#endif
++
++#define kBlockSizeMax ((1 << LZMA_NUM_BLOCK_SIZE_BITS) - 1)
++
++#define kBlockSize (9 << 10)
++#define kUnpackBlockSize (1 << 18)
++#define kMatchArraySize (1 << 21)
++#define kMatchRecordMaxSize ((LZMA_MATCH_LEN_MAX * 2 + 3) * LZMA_MATCH_LEN_MAX)
++
++#define kNumMaxDirectBits (31)
++
++#define kNumTopBits 24
++#define kTopValue ((UInt32)1 << kNumTopBits)
++
++#define kNumBitModelTotalBits 11
++#define kBitModelTotal (1 << kNumBitModelTotalBits)
++#define kNumMoveBits 5
++#define kProbInitValue (kBitModelTotal >> 1)
++
++#define kNumMoveReducingBits 4
++#define kNumBitPriceShiftBits 4
++#define kBitPrice (1 << kNumBitPriceShiftBits)
++
++void LzmaEncProps_Init(CLzmaEncProps *p)
++{
++  p->level = 5;
++  p->dictSize = p->mc = 0;
++  p->lc = p->lp = p->pb = p->algo = p->fb = p->btMode = p->numHashBytes = p->numThreads = -1;
++  p->writeEndMark = 0;
++}
++
++void LzmaEncProps_Normalize(CLzmaEncProps *p)
++{
++  int level = p->level;
++  if (level < 0) level = 5;
++  p->level = level;
++  if (p->dictSize == 0) p->dictSize = (level <= 5 ? (1 << (level * 2 + 14)) : (level == 6 ? (1 << 25) : (1 << 26)));
++  if (p->lc < 0) p->lc = 3;
++  if (p->lp < 0) p->lp = 0;
++  if (p->pb < 0) p->pb = 2;
++  if (p->algo < 0) p->algo = (level < 5 ? 0 : 1);
++  if (p->fb < 0) p->fb = (level < 7 ? 32 : 64);
++  if (p->btMode < 0) p->btMode = (p->algo == 0 ? 0 : 1);
++  if (p->numHashBytes < 0) p->numHashBytes = 4;
++  if (p->mc == 0)  p->mc = (16 + (p->fb >> 1)) >> (p->btMode ? 0 : 1);
++  if (p->numThreads < 0)
++    p->numThreads =
++      #ifndef _7ZIP_ST
++      ((p->btMode && p->algo) ? 2 : 1);
++      #else
++      1;
++      #endif
++}
++
++UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2)
++{
++  CLzmaEncProps props = *props2;
++  LzmaEncProps_Normalize(&props);
++  return props.dictSize;
++}
++
++/* #define LZMA_LOG_BSR */
++/* Define it for Intel's CPU */
++
++
++#ifdef LZMA_LOG_BSR
++
++#define kDicLogSizeMaxCompress 30
++
++#define BSR2_RET(pos, res) { unsigned long i; _BitScanReverse(&i, (pos)); res = (i + i) + ((pos >> (i - 1)) & 1); }
++
++UInt32 GetPosSlot1(UInt32 pos)
++{
++  UInt32 res;
++  BSR2_RET(pos, res);
++  return res;
++}
++#define GetPosSlot2(pos, res) { BSR2_RET(pos, res); }
++#define GetPosSlot(pos, res) { if (pos < 2) res = pos; else BSR2_RET(pos, res); }
++
++#else
++
++#define kNumLogBits (9 + (int)sizeof(size_t) / 2)
++#define kDicLogSizeMaxCompress ((kNumLogBits - 1) * 2 + 7)
++
++void LzmaEnc_FastPosInit(Byte *g_FastPos)
++{
++  int c = 2, slotFast;
++  g_FastPos[0] = 0;
++  g_FastPos[1] = 1;
++
++  for (slotFast = 2; slotFast < kNumLogBits * 2; slotFast++)
++  {
++    UInt32 k = (1 << ((slotFast >> 1) - 1));
++    UInt32 j;
++    for (j = 0; j < k; j++, c++)
++      g_FastPos[c] = (Byte)slotFast;
++  }
++}
++
++#define BSR2_RET(pos, res) { UInt32 i = 6 + ((kNumLogBits - 1) & \
++  (0 - (((((UInt32)1 << (kNumLogBits + 6)) - 1) - pos) >> 31))); \
++  res = p->g_FastPos[pos >> i] + (i * 2); }
++/*
++#define BSR2_RET(pos, res) { res = (pos < (1 << (kNumLogBits + 6))) ? \
++  p->g_FastPos[pos >> 6] + 12 : \
++  p->g_FastPos[pos >> (6 + kNumLogBits - 1)] + (6 + (kNumLogBits - 1)) * 2; }
++*/
++
++#define GetPosSlot1(pos) p->g_FastPos[pos]
++#define GetPosSlot2(pos, res) { BSR2_RET(pos, res); }
++#define GetPosSlot(pos, res) { if (pos < kNumFullDistances) res = p->g_FastPos[pos]; else BSR2_RET(pos, res); }
++
++#endif
++
++
++#define LZMA_NUM_REPS 4
++
++typedef unsigned CState;
++
++typedef struct
++{
++  UInt32 price;
++
++  CState state;
++  int prev1IsChar;
++  int prev2;
++
++  UInt32 posPrev2;
++  UInt32 backPrev2;
++
++  UInt32 posPrev;
++  UInt32 backPrev;
++  UInt32 backs[LZMA_NUM_REPS];
++} COptimal;
++
++#define kNumOpts (1 << 12)
++
++#define kNumLenToPosStates 4
++#define kNumPosSlotBits 6
++#define kDicLogSizeMin 0
++#define kDicLogSizeMax 32
++#define kDistTableSizeMax (kDicLogSizeMax * 2)
++
++
++#define kNumAlignBits 4
++#define kAlignTableSize (1 << kNumAlignBits)
++#define kAlignMask (kAlignTableSize - 1)
++
++#define kStartPosModelIndex 4
++#define kEndPosModelIndex 14
++#define kNumPosModels (kEndPosModelIndex - kStartPosModelIndex)
++
++#define kNumFullDistances (1 << (kEndPosModelIndex >> 1))
++
++#ifdef _LZMA_PROB32
++#define CLzmaProb UInt32
++#else
++#define CLzmaProb UInt16
++#endif
++
++#define LZMA_PB_MAX 4
++#define LZMA_LC_MAX 8
++#define LZMA_LP_MAX 4
++
++#define LZMA_NUM_PB_STATES_MAX (1 << LZMA_PB_MAX)
++
++
++#define kLenNumLowBits 3
++#define kLenNumLowSymbols (1 << kLenNumLowBits)
++#define kLenNumMidBits 3
++#define kLenNumMidSymbols (1 << kLenNumMidBits)
++#define kLenNumHighBits 8
++#define kLenNumHighSymbols (1 << kLenNumHighBits)
++
++#define kLenNumSymbolsTotal (kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols)
++
++#define LZMA_MATCH_LEN_MIN 2
++#define LZMA_MATCH_LEN_MAX (LZMA_MATCH_LEN_MIN + kLenNumSymbolsTotal - 1)
++
++#define kNumStates 12
++
++typedef struct
++{
++  CLzmaProb choice;
++  CLzmaProb choice2;
++  CLzmaProb low[LZMA_NUM_PB_STATES_MAX << kLenNumLowBits];
++  CLzmaProb mid[LZMA_NUM_PB_STATES_MAX << kLenNumMidBits];
++  CLzmaProb high[kLenNumHighSymbols];
++} CLenEnc;
++
++typedef struct
++{
++  CLenEnc p;
++  UInt32 prices[LZMA_NUM_PB_STATES_MAX][kLenNumSymbolsTotal];
++  UInt32 tableSize;
++  UInt32 counters[LZMA_NUM_PB_STATES_MAX];
++} CLenPriceEnc;
++
++typedef struct
++{
++  UInt32 range;
++  Byte cache;
++  UInt64 low;
++  UInt64 cacheSize;
++  Byte *buf;
++  Byte *bufLim;
++  Byte *bufBase;
++  ISeqOutStream *outStream;
++  UInt64 processed;
++  SRes res;
++} CRangeEnc;
++
++typedef struct
++{
++  CLzmaProb *litProbs;
++
++  CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX];
++  CLzmaProb isRep[kNumStates];
++  CLzmaProb isRepG0[kNumStates];
++  CLzmaProb isRepG1[kNumStates];
++  CLzmaProb isRepG2[kNumStates];
++  CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX];
++
++  CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits];
++  CLzmaProb posEncoders[kNumFullDistances - kEndPosModelIndex];
++  CLzmaProb posAlignEncoder[1 << kNumAlignBits];
++
++  CLenPriceEnc lenEnc;
++  CLenPriceEnc repLenEnc;
++
++  UInt32 reps[LZMA_NUM_REPS];
++  UInt32 state;
++} CSaveState;
++
++typedef struct
++{
++  IMatchFinder matchFinder;
++  void *matchFinderObj;
++
++  #ifndef _7ZIP_ST
++  Bool mtMode;
++  CMatchFinderMt matchFinderMt;
++  #endif
++
++  CMatchFinder matchFinderBase;
++
++  #ifndef _7ZIP_ST
++  Byte pad[128];
++  #endif
++
++  UInt32 optimumEndIndex;
++  UInt32 optimumCurrentIndex;
++
++  UInt32 longestMatchLength;
++  UInt32 numPairs;
++  UInt32 numAvail;
++  COptimal opt[kNumOpts];
++
++  #ifndef LZMA_LOG_BSR
++  Byte g_FastPos[1 << kNumLogBits];
++  #endif
++
++  UInt32 ProbPrices[kBitModelTotal >> kNumMoveReducingBits];
++  UInt32 matches[LZMA_MATCH_LEN_MAX * 2 + 2 + 1];
++  UInt32 numFastBytes;
++  UInt32 additionalOffset;
++  UInt32 reps[LZMA_NUM_REPS];
++  UInt32 state;
++
++  UInt32 posSlotPrices[kNumLenToPosStates][kDistTableSizeMax];
++  UInt32 distancesPrices[kNumLenToPosStates][kNumFullDistances];
++  UInt32 alignPrices[kAlignTableSize];
++  UInt32 alignPriceCount;
++
++  UInt32 distTableSize;
++
++  unsigned lc, lp, pb;
++  unsigned lpMask, pbMask;
++
++  CLzmaProb *litProbs;
++
++  CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX];
++  CLzmaProb isRep[kNumStates];
++  CLzmaProb isRepG0[kNumStates];
++  CLzmaProb isRepG1[kNumStates];
++  CLzmaProb isRepG2[kNumStates];
++  CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX];
++
++  CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits];
++  CLzmaProb posEncoders[kNumFullDistances - kEndPosModelIndex];
++  CLzmaProb posAlignEncoder[1 << kNumAlignBits];
++
++  CLenPriceEnc lenEnc;
++  CLenPriceEnc repLenEnc;
++
++  unsigned lclp;
++
++  Bool fastMode;
++
++  CRangeEnc rc;
++
++  Bool writeEndMark;
++  UInt64 nowPos64;
++  UInt32 matchPriceCount;
++  Bool finished;
++  Bool multiThread;
++
++  SRes result;
++  UInt32 dictSize;
++  UInt32 matchFinderCycles;
++
++  int needInit;
++
++  CSaveState saveState;
++} CLzmaEnc;
++
++void LzmaEnc_SaveState(CLzmaEncHandle pp)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  CSaveState *dest = &p->saveState;
++  int i;
++  dest->lenEnc = p->lenEnc;
++  dest->repLenEnc = p->repLenEnc;
++  dest->state = p->state;
++
++  for (i = 0; i < kNumStates; i++)
++  {
++    memcpy(dest->isMatch[i], p->isMatch[i], sizeof(p->isMatch[i]));
++    memcpy(dest->isRep0Long[i], p->isRep0Long[i], sizeof(p->isRep0Long[i]));
++  }
++  for (i = 0; i < kNumLenToPosStates; i++)
++    memcpy(dest->posSlotEncoder[i], p->posSlotEncoder[i], sizeof(p->posSlotEncoder[i]));
++  memcpy(dest->isRep, p->isRep, sizeof(p->isRep));
++  memcpy(dest->isRepG0, p->isRepG0, sizeof(p->isRepG0));
++  memcpy(dest->isRepG1, p->isRepG1, sizeof(p->isRepG1));
++  memcpy(dest->isRepG2, p->isRepG2, sizeof(p->isRepG2));
++  memcpy(dest->posEncoders, p->posEncoders, sizeof(p->posEncoders));
++  memcpy(dest->posAlignEncoder, p->posAlignEncoder, sizeof(p->posAlignEncoder));
++  memcpy(dest->reps, p->reps, sizeof(p->reps));
++  memcpy(dest->litProbs, p->litProbs, (0x300 << p->lclp) * sizeof(CLzmaProb));
++}
++
++void LzmaEnc_RestoreState(CLzmaEncHandle pp)
++{
++  CLzmaEnc *dest = (CLzmaEnc *)pp;
++  const CSaveState *p = &dest->saveState;
++  int i;
++  dest->lenEnc = p->lenEnc;
++  dest->repLenEnc = p->repLenEnc;
++  dest->state = p->state;
++
++  for (i = 0; i < kNumStates; i++)
++  {
++    memcpy(dest->isMatch[i], p->isMatch[i], sizeof(p->isMatch[i]));
++    memcpy(dest->isRep0Long[i], p->isRep0Long[i], sizeof(p->isRep0Long[i]));
++  }
++  for (i = 0; i < kNumLenToPosStates; i++)
++    memcpy(dest->posSlotEncoder[i], p->posSlotEncoder[i], sizeof(p->posSlotEncoder[i]));
++  memcpy(dest->isRep, p->isRep, sizeof(p->isRep));
++  memcpy(dest->isRepG0, p->isRepG0, sizeof(p->isRepG0));
++  memcpy(dest->isRepG1, p->isRepG1, sizeof(p->isRepG1));
++  memcpy(dest->isRepG2, p->isRepG2, sizeof(p->isRepG2));
++  memcpy(dest->posEncoders, p->posEncoders, sizeof(p->posEncoders));
++  memcpy(dest->posAlignEncoder, p->posAlignEncoder, sizeof(p->posAlignEncoder));
++  memcpy(dest->reps, p->reps, sizeof(p->reps));
++  memcpy(dest->litProbs, p->litProbs, (0x300 << dest->lclp) * sizeof(CLzmaProb));
++}
++
++SRes LzmaEnc_SetProps(CLzmaEncHandle pp, const CLzmaEncProps *props2)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  CLzmaEncProps props = *props2;
++  LzmaEncProps_Normalize(&props);
++
++  if (props.lc > LZMA_LC_MAX || props.lp > LZMA_LP_MAX || props.pb > LZMA_PB_MAX ||
++      props.dictSize > (1 << kDicLogSizeMaxCompress) || props.dictSize > (1 << 30))
++    return SZ_ERROR_PARAM;
++  p->dictSize = props.dictSize;
++  p->matchFinderCycles = props.mc;
++  {
++    unsigned fb = props.fb;
++    if (fb < 5)
++      fb = 5;
++    if (fb > LZMA_MATCH_LEN_MAX)
++      fb = LZMA_MATCH_LEN_MAX;
++    p->numFastBytes = fb;
++  }
++  p->lc = props.lc;
++  p->lp = props.lp;
++  p->pb = props.pb;
++  p->fastMode = (props.algo == 0);
++  p->matchFinderBase.btMode = props.btMode;
++  {
++    UInt32 numHashBytes = 4;
++    if (props.btMode)
++    {
++      if (props.numHashBytes < 2)
++        numHashBytes = 2;
++      else if (props.numHashBytes < 4)
++        numHashBytes = props.numHashBytes;
++    }
++    p->matchFinderBase.numHashBytes = numHashBytes;
++  }
++
++  p->matchFinderBase.cutValue = props.mc;
++
++  p->writeEndMark = props.writeEndMark;
++
++  #ifndef _7ZIP_ST
++  /*
++  if (newMultiThread != _multiThread)
++  {
++    ReleaseMatchFinder();
++    _multiThread = newMultiThread;
++  }
++  */
++  p->multiThread = (props.numThreads > 1);
++  #endif
++
++  return SZ_OK;
++}
++
++static const int kLiteralNextStates[kNumStates] = {0, 0, 0, 0, 1, 2, 3, 4,  5,  6,   4, 5};
++static const int kMatchNextStates[kNumStates]   = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10};
++static const int kRepNextStates[kNumStates]     = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11};
++static const int kShortRepNextStates[kNumStates]= {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11};
++
++#define IsCharState(s) ((s) < 7)
++
++#define GetLenToPosState(len) (((len) < kNumLenToPosStates + 1) ? (len) - 2 : kNumLenToPosStates - 1)
++
++#define kInfinityPrice (1 << 30)
++
++static void RangeEnc_Construct(CRangeEnc *p)
++{
++  p->outStream = 0;
++  p->bufBase = 0;
++}
++
++#define RangeEnc_GetProcessed(p) ((p)->processed + ((p)->buf - (p)->bufBase) + (p)->cacheSize)
++
++#define RC_BUF_SIZE (1 << 16)
++static int RangeEnc_Alloc(CRangeEnc *p, ISzAlloc *alloc)
++{
++  if (p->bufBase == 0)
++  {
++    p->bufBase = (Byte *)alloc->Alloc(alloc, RC_BUF_SIZE);
++    if (p->bufBase == 0)
++      return 0;
++    p->bufLim = p->bufBase + RC_BUF_SIZE;
++  }
++  return 1;
++}
++
++static void RangeEnc_Free(CRangeEnc *p, ISzAlloc *alloc)
++{
++  alloc->Free(alloc, p->bufBase);
++  p->bufBase = 0;
++}
++
++static void RangeEnc_Init(CRangeEnc *p)
++{
++  /* Stream.Init(); */
++  p->low = 0;
++  p->range = 0xFFFFFFFF;
++  p->cacheSize = 1;
++  p->cache = 0;
++
++  p->buf = p->bufBase;
++
++  p->processed = 0;
++  p->res = SZ_OK;
++}
++
++static void RangeEnc_FlushStream(CRangeEnc *p)
++{
++  size_t num;
++  if (p->res != SZ_OK)
++    return;
++  num = p->buf - p->bufBase;
++  if (num != p->outStream->Write(p->outStream, p->bufBase, num))
++    p->res = SZ_ERROR_WRITE;
++  p->processed += num;
++  p->buf = p->bufBase;
++}
++
++static void MY_FAST_CALL RangeEnc_ShiftLow(CRangeEnc *p)
++{
++  if ((UInt32)p->low < (UInt32)0xFF000000 || (int)(p->low >> 32) != 0)
++  {
++    Byte temp = p->cache;
++    do
++    {
++      Byte *buf = p->buf;
++      *buf++ = (Byte)(temp + (Byte)(p->low >> 32));
++      p->buf = buf;
++      if (buf == p->bufLim)
++        RangeEnc_FlushStream(p);
++      temp = 0xFF;
++    }
++    while (--p->cacheSize != 0);
++    p->cache = (Byte)((UInt32)p->low >> 24);
++  }
++  p->cacheSize++;
++  p->low = (UInt32)p->low << 8;
++}
++
++static void RangeEnc_FlushData(CRangeEnc *p)
++{
++  int i;
++  for (i = 0; i < 5; i++)
++    RangeEnc_ShiftLow(p);
++}
++
++static void RangeEnc_EncodeDirectBits(CRangeEnc *p, UInt32 value, int numBits)
++{
++  do
++  {
++    p->range >>= 1;
++    p->low += p->range & (0 - ((value >> --numBits) & 1));
++    if (p->range < kTopValue)
++    {
++      p->range <<= 8;
++      RangeEnc_ShiftLow(p);
++    }
++  }
++  while (numBits != 0);
++}
++
++static void RangeEnc_EncodeBit(CRangeEnc *p, CLzmaProb *prob, UInt32 symbol)
++{
++  UInt32 ttt = *prob;
++  UInt32 newBound = (p->range >> kNumBitModelTotalBits) * ttt;
++  if (symbol == 0)
++  {
++    p->range = newBound;
++    ttt += (kBitModelTotal - ttt) >> kNumMoveBits;
++  }
++  else
++  {
++    p->low += newBound;
++    p->range -= newBound;
++    ttt -= ttt >> kNumMoveBits;
++  }
++  *prob = (CLzmaProb)ttt;
++  if (p->range < kTopValue)
++  {
++    p->range <<= 8;
++    RangeEnc_ShiftLow(p);
++  }
++}
++
++static void LitEnc_Encode(CRangeEnc *p, CLzmaProb *probs, UInt32 symbol)
++{
++  symbol |= 0x100;
++  do
++  {
++    RangeEnc_EncodeBit(p, probs + (symbol >> 8), (symbol >> 7) & 1);
++    symbol <<= 1;
++  }
++  while (symbol < 0x10000);
++}
++
++static void LitEnc_EncodeMatched(CRangeEnc *p, CLzmaProb *probs, UInt32 symbol, UInt32 matchByte)
++{
++  UInt32 offs = 0x100;
++  symbol |= 0x100;
++  do
++  {
++    matchByte <<= 1;
++    RangeEnc_EncodeBit(p, probs + (offs + (matchByte & offs) + (symbol >> 8)), (symbol >> 7) & 1);
++    symbol <<= 1;
++    offs &= ~(matchByte ^ symbol);
++  }
++  while (symbol < 0x10000);
++}
++
++void LzmaEnc_InitPriceTables(UInt32 *ProbPrices)
++{
++  UInt32 i;
++  for (i = (1 << kNumMoveReducingBits) / 2; i < kBitModelTotal; i += (1 << kNumMoveReducingBits))
++  {
++    const int kCyclesBits = kNumBitPriceShiftBits;
++    UInt32 w = i;
++    UInt32 bitCount = 0;
++    int j;
++    for (j = 0; j < kCyclesBits; j++)
++    {
++      w = w * w;
++      bitCount <<= 1;
++      while (w >= ((UInt32)1 << 16))
++      {
++        w >>= 1;
++        bitCount++;
++      }
++    }
++    ProbPrices[i >> kNumMoveReducingBits] = ((kNumBitModelTotalBits << kCyclesBits) - 15 - bitCount);
++  }
++}
++
++
++#define GET_PRICE(prob, symbol) \
++  p->ProbPrices[((prob) ^ (((-(int)(symbol))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits];
++
++#define GET_PRICEa(prob, symbol) \
++  ProbPrices[((prob) ^ ((-((int)(symbol))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits];
++
++#define GET_PRICE_0(prob) p->ProbPrices[(prob) >> kNumMoveReducingBits]
++#define GET_PRICE_1(prob) p->ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits]
++
++#define GET_PRICE_0a(prob) ProbPrices[(prob) >> kNumMoveReducingBits]
++#define GET_PRICE_1a(prob) ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits]
++
++static UInt32 LitEnc_GetPrice(const CLzmaProb *probs, UInt32 symbol, UInt32 *ProbPrices)
++{
++  UInt32 price = 0;
++  symbol |= 0x100;
++  do
++  {
++    price += GET_PRICEa(probs[symbol >> 8], (symbol >> 7) & 1);
++    symbol <<= 1;
++  }
++  while (symbol < 0x10000);
++  return price;
++}
++
++static UInt32 LitEnc_GetPriceMatched(const CLzmaProb *probs, UInt32 symbol, UInt32 matchByte, UInt32 *ProbPrices)
++{
++  UInt32 price = 0;
++  UInt32 offs = 0x100;
++  symbol |= 0x100;
++  do
++  {
++    matchByte <<= 1;
++    price += GET_PRICEa(probs[offs + (matchByte & offs) + (symbol >> 8)], (symbol >> 7) & 1);
++    symbol <<= 1;
++    offs &= ~(matchByte ^ symbol);
++  }
++  while (symbol < 0x10000);
++  return price;
++}
++
++
++static void RcTree_Encode(CRangeEnc *rc, CLzmaProb *probs, int numBitLevels, UInt32 symbol)
++{
++  UInt32 m = 1;
++  int i;
++  for (i = numBitLevels; i != 0;)
++  {
++    UInt32 bit;
++    i--;
++    bit = (symbol >> i) & 1;
++    RangeEnc_EncodeBit(rc, probs + m, bit);
++    m = (m << 1) | bit;
++  }
++}
++
++static void RcTree_ReverseEncode(CRangeEnc *rc, CLzmaProb *probs, int numBitLevels, UInt32 symbol)
++{
++  UInt32 m = 1;
++  int i;
++  for (i = 0; i < numBitLevels; i++)
++  {
++    UInt32 bit = symbol & 1;
++    RangeEnc_EncodeBit(rc, probs + m, bit);
++    m = (m << 1) | bit;
++    symbol >>= 1;
++  }
++}
++
++static UInt32 RcTree_GetPrice(const CLzmaProb *probs, int numBitLevels, UInt32 symbol, UInt32 *ProbPrices)
++{
++  UInt32 price = 0;
++  symbol |= (1 << numBitLevels);
++  while (symbol != 1)
++  {
++    price += GET_PRICEa(probs[symbol >> 1], symbol & 1);
++    symbol >>= 1;
++  }
++  return price;
++}
++
++static UInt32 RcTree_ReverseGetPrice(const CLzmaProb *probs, int numBitLevels, UInt32 symbol, UInt32 *ProbPrices)
++{
++  UInt32 price = 0;
++  UInt32 m = 1;
++  int i;
++  for (i = numBitLevels; i != 0; i--)
++  {
++    UInt32 bit = symbol & 1;
++    symbol >>= 1;
++    price += GET_PRICEa(probs[m], bit);
++    m = (m << 1) | bit;
++  }
++  return price;
++}
++
++
++static void LenEnc_Init(CLenEnc *p)
++{
++  unsigned i;
++  p->choice = p->choice2 = kProbInitValue;
++  for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << kLenNumLowBits); i++)
++    p->low[i] = kProbInitValue;
++  for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << kLenNumMidBits); i++)
++    p->mid[i] = kProbInitValue;
++  for (i = 0; i < kLenNumHighSymbols; i++)
++    p->high[i] = kProbInitValue;
++}
++
++static void LenEnc_Encode(CLenEnc *p, CRangeEnc *rc, UInt32 symbol, UInt32 posState)
++{
++  if (symbol < kLenNumLowSymbols)
++  {
++    RangeEnc_EncodeBit(rc, &p->choice, 0);
++    RcTree_Encode(rc, p->low + (posState << kLenNumLowBits), kLenNumLowBits, symbol);
++  }
++  else
++  {
++    RangeEnc_EncodeBit(rc, &p->choice, 1);
++    if (symbol < kLenNumLowSymbols + kLenNumMidSymbols)
++    {
++      RangeEnc_EncodeBit(rc, &p->choice2, 0);
++      RcTree_Encode(rc, p->mid + (posState << kLenNumMidBits), kLenNumMidBits, symbol - kLenNumLowSymbols);
++    }
++    else
++    {
++      RangeEnc_EncodeBit(rc, &p->choice2, 1);
++      RcTree_Encode(rc, p->high, kLenNumHighBits, symbol - kLenNumLowSymbols - kLenNumMidSymbols);
++    }
++  }
++}
++
++static void LenEnc_SetPrices(CLenEnc *p, UInt32 posState, UInt32 numSymbols, UInt32 *prices, UInt32 *ProbPrices)
++{
++  UInt32 a0 = GET_PRICE_0a(p->choice);
++  UInt32 a1 = GET_PRICE_1a(p->choice);
++  UInt32 b0 = a1 + GET_PRICE_0a(p->choice2);
++  UInt32 b1 = a1 + GET_PRICE_1a(p->choice2);
++  UInt32 i = 0;
++  for (i = 0; i < kLenNumLowSymbols; i++)
++  {
++    if (i >= numSymbols)
++      return;
++    prices[i] = a0 + RcTree_GetPrice(p->low + (posState << kLenNumLowBits), kLenNumLowBits, i, ProbPrices);
++  }
++  for (; i < kLenNumLowSymbols + kLenNumMidSymbols; i++)
++  {
++    if (i >= numSymbols)
++      return;
++    prices[i] = b0 + RcTree_GetPrice(p->mid + (posState << kLenNumMidBits), kLenNumMidBits, i - kLenNumLowSymbols, ProbPrices);
++  }
++  for (; i < numSymbols; i++)
++    prices[i] = b1 + RcTree_GetPrice(p->high, kLenNumHighBits, i - kLenNumLowSymbols - kLenNumMidSymbols, ProbPrices);
++}
++
++static void MY_FAST_CALL LenPriceEnc_UpdateTable(CLenPriceEnc *p, UInt32 posState, UInt32 *ProbPrices)
++{
++  LenEnc_SetPrices(&p->p, posState, p->tableSize, p->prices[posState], ProbPrices);
++  p->counters[posState] = p->tableSize;
++}
++
++static void LenPriceEnc_UpdateTables(CLenPriceEnc *p, UInt32 numPosStates, UInt32 *ProbPrices)
++{
++  UInt32 posState;
++  for (posState = 0; posState < numPosStates; posState++)
++    LenPriceEnc_UpdateTable(p, posState, ProbPrices);
++}
++
++static void LenEnc_Encode2(CLenPriceEnc *p, CRangeEnc *rc, UInt32 symbol, UInt32 posState, Bool updatePrice, UInt32 *ProbPrices)
++{
++  LenEnc_Encode(&p->p, rc, symbol, posState);
++  if (updatePrice)
++    if (--p->counters[posState] == 0)
++      LenPriceEnc_UpdateTable(p, posState, ProbPrices);
++}
++
++
++
++
++static void MovePos(CLzmaEnc *p, UInt32 num)
++{
++  #ifdef SHOW_STAT
++  ttt += num;
++  printf("\n MovePos %d", num);
++  #endif
++  if (num != 0)
++  {
++    p->additionalOffset += num;
++    p->matchFinder.Skip(p->matchFinderObj, num);
++  }
++}
++
++static UInt32 ReadMatchDistances(CLzmaEnc *p, UInt32 *numDistancePairsRes)
++{
++  UInt32 lenRes = 0, numPairs;
++  p->numAvail = p->matchFinder.GetNumAvailableBytes(p->matchFinderObj);
++  numPairs = p->matchFinder.GetMatches(p->matchFinderObj, p->matches);
++  #ifdef SHOW_STAT
++  printf("\n i = %d numPairs = %d    ", ttt, numPairs / 2);
++  ttt++;
++  {
++    UInt32 i;
++    for (i = 0; i < numPairs; i += 2)
++      printf("%2d %6d   | ", p->matches[i], p->matches[i + 1]);
++  }
++  #endif
++  if (numPairs > 0)
++  {
++    lenRes = p->matches[numPairs - 2];
++    if (lenRes == p->numFastBytes)
++    {
++      const Byte *pby = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
++      UInt32 distance = p->matches[numPairs - 1] + 1;
++      UInt32 numAvail = p->numAvail;
++      if (numAvail > LZMA_MATCH_LEN_MAX)
++        numAvail = LZMA_MATCH_LEN_MAX;
++      {
++        const Byte *pby2 = pby - distance;
++        for (; lenRes < numAvail && pby[lenRes] == pby2[lenRes]; lenRes++);
++      }
++    }
++  }
++  p->additionalOffset++;
++  *numDistancePairsRes = numPairs;
++  return lenRes;
++}
++
++
++#define MakeAsChar(p) (p)->backPrev = (UInt32)(-1); (p)->prev1IsChar = False;
++#define MakeAsShortRep(p) (p)->backPrev = 0; (p)->prev1IsChar = False;
++#define IsShortRep(p) ((p)->backPrev == 0)
++
++static UInt32 GetRepLen1Price(CLzmaEnc *p, UInt32 state, UInt32 posState)
++{
++  return
++    GET_PRICE_0(p->isRepG0[state]) +
++    GET_PRICE_0(p->isRep0Long[state][posState]);
++}
++
++static UInt32 GetPureRepPrice(CLzmaEnc *p, UInt32 repIndex, UInt32 state, UInt32 posState)
++{
++  UInt32 price;
++  if (repIndex == 0)
++  {
++    price = GET_PRICE_0(p->isRepG0[state]);
++    price += GET_PRICE_1(p->isRep0Long[state][posState]);
++  }
++  else
++  {
++    price = GET_PRICE_1(p->isRepG0[state]);
++    if (repIndex == 1)
++      price += GET_PRICE_0(p->isRepG1[state]);
++    else
++    {
++      price += GET_PRICE_1(p->isRepG1[state]);
++      price += GET_PRICE(p->isRepG2[state], repIndex - 2);
++    }
++  }
++  return price;
++}
++
++static UInt32 GetRepPrice(CLzmaEnc *p, UInt32 repIndex, UInt32 len, UInt32 state, UInt32 posState)
++{
++  return p->repLenEnc.prices[posState][len - LZMA_MATCH_LEN_MIN] +
++    GetPureRepPrice(p, repIndex, state, posState);
++}
++
++static UInt32 Backward(CLzmaEnc *p, UInt32 *backRes, UInt32 cur)
++{
++  UInt32 posMem = p->opt[cur].posPrev;
++  UInt32 backMem = p->opt[cur].backPrev;
++  p->optimumEndIndex = cur;
++  do
++  {
++    if (p->opt[cur].prev1IsChar)
++    {
++      MakeAsChar(&p->opt[posMem])
++      p->opt[posMem].posPrev = posMem - 1;
++      if (p->opt[cur].prev2)
++      {
++        p->opt[posMem - 1].prev1IsChar = False;
++        p->opt[posMem - 1].posPrev = p->opt[cur].posPrev2;
++        p->opt[posMem - 1].backPrev = p->opt[cur].backPrev2;
++      }
++    }
++    {
++      UInt32 posPrev = posMem;
++      UInt32 backCur = backMem;
++
++      backMem = p->opt[posPrev].backPrev;
++      posMem = p->opt[posPrev].posPrev;
++
++      p->opt[posPrev].backPrev = backCur;
++      p->opt[posPrev].posPrev = cur;
++      cur = posPrev;
++    }
++  }
++  while (cur != 0);
++  *backRes = p->opt[0].backPrev;
++  p->optimumCurrentIndex  = p->opt[0].posPrev;
++  return p->optimumCurrentIndex;
++}
++
++#define LIT_PROBS(pos, prevByte) (p->litProbs + ((((pos) & p->lpMask) << p->lc) + ((prevByte) >> (8 - p->lc))) * 0x300)
++
++static UInt32 GetOptimum(CLzmaEnc *p, UInt32 position, UInt32 *backRes)
++{
++  UInt32 numAvail, mainLen, numPairs, repMaxIndex, i, posState, lenEnd, len, cur;
++  UInt32 matchPrice, repMatchPrice, normalMatchPrice;
++  UInt32 reps[LZMA_NUM_REPS], repLens[LZMA_NUM_REPS];
++  UInt32 *matches;
++  const Byte *data;
++  Byte curByte, matchByte;
++  if (p->optimumEndIndex != p->optimumCurrentIndex)
++  {
++    const COptimal *opt = &p->opt[p->optimumCurrentIndex];
++    UInt32 lenRes = opt->posPrev - p->optimumCurrentIndex;
++    *backRes = opt->backPrev;
++    p->optimumCurrentIndex = opt->posPrev;
++    return lenRes;
++  }
++  p->optimumCurrentIndex = p->optimumEndIndex = 0;
++
++  if (p->additionalOffset == 0)
++    mainLen = ReadMatchDistances(p, &numPairs);
++  else
++  {
++    mainLen = p->longestMatchLength;
++    numPairs = p->numPairs;
++  }
++
++  numAvail = p->numAvail;
++  if (numAvail < 2)
++  {
++    *backRes = (UInt32)(-1);
++    return 1;
++  }
++  if (numAvail > LZMA_MATCH_LEN_MAX)
++    numAvail = LZMA_MATCH_LEN_MAX;
++
++  data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
++  repMaxIndex = 0;
++  for (i = 0; i < LZMA_NUM_REPS; i++)
++  {
++    UInt32 lenTest;
++    const Byte *data2;
++    reps[i] = p->reps[i];
++    data2 = data - (reps[i] + 1);
++    if (data[0] != data2[0] || data[1] != data2[1])
++    {
++      repLens[i] = 0;
++      continue;
++    }
++    for (lenTest = 2; lenTest < numAvail && data[lenTest] == data2[lenTest]; lenTest++);
++    repLens[i] = lenTest;
++    if (lenTest > repLens[repMaxIndex])
++      repMaxIndex = i;
++  }
++  if (repLens[repMaxIndex] >= p->numFastBytes)
++  {
++    UInt32 lenRes;
++    *backRes = repMaxIndex;
++    lenRes = repLens[repMaxIndex];
++    MovePos(p, lenRes - 1);
++    return lenRes;
++  }
++
++  matches = p->matches;
++  if (mainLen >= p->numFastBytes)
++  {
++    *backRes = matches[numPairs - 1] + LZMA_NUM_REPS;
++    MovePos(p, mainLen - 1);
++    return mainLen;
++  }
++  curByte = *data;
++  matchByte = *(data - (reps[0] + 1));
++
++  if (mainLen < 2 && curByte != matchByte && repLens[repMaxIndex] < 2)
++  {
++    *backRes = (UInt32)-1;
++    return 1;
++  }
++
++  p->opt[0].state = (CState)p->state;
++
++  posState = (position & p->pbMask);
++
++  {
++    const CLzmaProb *probs = LIT_PROBS(position, *(data - 1));
++    p->opt[1].price = GET_PRICE_0(p->isMatch[p->state][posState]) +
++        (!IsCharState(p->state) ?
++          LitEnc_GetPriceMatched(probs, curByte, matchByte, p->ProbPrices) :
++          LitEnc_GetPrice(probs, curByte, p->ProbPrices));
++  }
++
++  MakeAsChar(&p->opt[1]);
++
++  matchPrice = GET_PRICE_1(p->isMatch[p->state][posState]);
++  repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[p->state]);
++
++  if (matchByte == curByte)
++  {
++    UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(p, p->state, posState);
++    if (shortRepPrice < p->opt[1].price)
++    {
++      p->opt[1].price = shortRepPrice;
++      MakeAsShortRep(&p->opt[1]);
++    }
++  }
++  lenEnd = ((mainLen >= repLens[repMaxIndex]) ? mainLen : repLens[repMaxIndex]);
++
++  if (lenEnd < 2)
++  {
++    *backRes = p->opt[1].backPrev;
++    return 1;
++  }
++
++  p->opt[1].posPrev = 0;
++  for (i = 0; i < LZMA_NUM_REPS; i++)
++    p->opt[0].backs[i] = reps[i];
++
++  len = lenEnd;
++  do
++    p->opt[len--].price = kInfinityPrice;
++  while (len >= 2);
++
++  for (i = 0; i < LZMA_NUM_REPS; i++)
++  {
++    UInt32 repLen = repLens[i];
++    UInt32 price;
++    if (repLen < 2)
++      continue;
++    price = repMatchPrice + GetPureRepPrice(p, i, p->state, posState);
++    do
++    {
++      UInt32 curAndLenPrice = price + p->repLenEnc.prices[posState][repLen - 2];
++      COptimal *opt = &p->opt[repLen];
++      if (curAndLenPrice < opt->price)
++      {
++        opt->price = curAndLenPrice;
++        opt->posPrev = 0;
++        opt->backPrev = i;
++        opt->prev1IsChar = False;
++      }
++    }
++    while (--repLen >= 2);
++  }
++
++  normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[p->state]);
++
++  len = ((repLens[0] >= 2) ? repLens[0] + 1 : 2);
++  if (len <= mainLen)
++  {
++    UInt32 offs = 0;
++    while (len > matches[offs])
++      offs += 2;
++    for (; ; len++)
++    {
++      COptimal *opt;
++      UInt32 distance = matches[offs + 1];
++
++      UInt32 curAndLenPrice = normalMatchPrice + p->lenEnc.prices[posState][len - LZMA_MATCH_LEN_MIN];
++      UInt32 lenToPosState = GetLenToPosState(len);
++      if (distance < kNumFullDistances)
++        curAndLenPrice += p->distancesPrices[lenToPosState][distance];
++      else
++      {
++        UInt32 slot;
++        GetPosSlot2(distance, slot);
++        curAndLenPrice += p->alignPrices[distance & kAlignMask] + p->posSlotPrices[lenToPosState][slot];
++      }
++      opt = &p->opt[len];
++      if (curAndLenPrice < opt->price)
++      {
++        opt->price = curAndLenPrice;
++        opt->posPrev = 0;
++        opt->backPrev = distance + LZMA_NUM_REPS;
++        opt->prev1IsChar = False;
++      }
++      if (len == matches[offs])
++      {
++        offs += 2;
++        if (offs == numPairs)
++          break;
++      }
++    }
++  }
++
++  cur = 0;
++
++    #ifdef SHOW_STAT2
++    if (position >= 0)
++    {
++      unsigned i;
++      printf("\n pos = %4X", position);
++      for (i = cur; i <= lenEnd; i++)
++      printf("\nprice[%4X] = %d", position - cur + i, p->opt[i].price);
++    }
++    #endif
++
++  for (;;)
++  {
++    UInt32 numAvailFull, newLen, numPairs, posPrev, state, posState, startLen;
++    UInt32 curPrice, curAnd1Price, matchPrice, repMatchPrice;
++    Bool nextIsChar;
++    Byte curByte, matchByte;
++    const Byte *data;
++    COptimal *curOpt;
++    COptimal *nextOpt;
++
++    cur++;
++    if (cur == lenEnd)
++      return Backward(p, backRes, cur);
++
++    newLen = ReadMatchDistances(p, &numPairs);
++    if (newLen >= p->numFastBytes)
++    {
++      p->numPairs = numPairs;
++      p->longestMatchLength = newLen;
++      return Backward(p, backRes, cur);
++    }
++    position++;
++    curOpt = &p->opt[cur];
++    posPrev = curOpt->posPrev;
++    if (curOpt->prev1IsChar)
++    {
++      posPrev--;
++      if (curOpt->prev2)
++      {
++        state = p->opt[curOpt->posPrev2].state;
++        if (curOpt->backPrev2 < LZMA_NUM_REPS)
++          state = kRepNextStates[state];
++        else
++          state = kMatchNextStates[state];
++      }
++      else
++        state = p->opt[posPrev].state;
++      state = kLiteralNextStates[state];
++    }
++    else
++      state = p->opt[posPrev].state;
++    if (posPrev == cur - 1)
++    {
++      if (IsShortRep(curOpt))
++        state = kShortRepNextStates[state];
++      else
++        state = kLiteralNextStates[state];
++    }
++    else
++    {
++      UInt32 pos;
++      const COptimal *prevOpt;
++      if (curOpt->prev1IsChar && curOpt->prev2)
++      {
++        posPrev = curOpt->posPrev2;
++        pos = curOpt->backPrev2;
++        state = kRepNextStates[state];
++      }
++      else
++      {
++        pos = curOpt->backPrev;
++        if (pos < LZMA_NUM_REPS)
++          state = kRepNextStates[state];
++        else
++          state = kMatchNextStates[state];
++      }
++      prevOpt = &p->opt[posPrev];
++      if (pos < LZMA_NUM_REPS)
++      {
++        UInt32 i;
++        reps[0] = prevOpt->backs[pos];
++        for (i = 1; i <= pos; i++)
++          reps[i] = prevOpt->backs[i - 1];
++        for (; i < LZMA_NUM_REPS; i++)
++          reps[i] = prevOpt->backs[i];
++      }
++      else
++      {
++        UInt32 i;
++        reps[0] = (pos - LZMA_NUM_REPS);
++        for (i = 1; i < LZMA_NUM_REPS; i++)
++          reps[i] = prevOpt->backs[i - 1];
++      }
++    }
++    curOpt->state = (CState)state;
++
++    curOpt->backs[0] = reps[0];
++    curOpt->backs[1] = reps[1];
++    curOpt->backs[2] = reps[2];
++    curOpt->backs[3] = reps[3];
++
++    curPrice = curOpt->price;
++    nextIsChar = False;
++    data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
++    curByte = *data;
++    matchByte = *(data - (reps[0] + 1));
++
++    posState = (position & p->pbMask);
++
++    curAnd1Price = curPrice + GET_PRICE_0(p->isMatch[state][posState]);
++    {
++      const CLzmaProb *probs = LIT_PROBS(position, *(data - 1));
++      curAnd1Price +=
++        (!IsCharState(state) ?
++          LitEnc_GetPriceMatched(probs, curByte, matchByte, p->ProbPrices) :
++          LitEnc_GetPrice(probs, curByte, p->ProbPrices));
++    }
++
++    nextOpt = &p->opt[cur + 1];
++
++    if (curAnd1Price < nextOpt->price)
++    {
++      nextOpt->price = curAnd1Price;
++      nextOpt->posPrev = cur;
++      MakeAsChar(nextOpt);
++      nextIsChar = True;
++    }
++
++    matchPrice = curPrice + GET_PRICE_1(p->isMatch[state][posState]);
++    repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[state]);
++
++    if (matchByte == curByte && !(nextOpt->posPrev < cur && nextOpt->backPrev == 0))
++    {
++      UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(p, state, posState);
++      if (shortRepPrice <= nextOpt->price)
++      {
++        nextOpt->price = shortRepPrice;
++        nextOpt->posPrev = cur;
++        MakeAsShortRep(nextOpt);
++        nextIsChar = True;
++      }
++    }
++    numAvailFull = p->numAvail;
++    {
++      UInt32 temp = kNumOpts - 1 - cur;
++      if (temp < numAvailFull)
++        numAvailFull = temp;
++    }
++
++    if (numAvailFull < 2)
++      continue;
++    numAvail = (numAvailFull <= p->numFastBytes ? numAvailFull : p->numFastBytes);
++
++    if (!nextIsChar && matchByte != curByte) /* speed optimization */
++    {
++      /* try Literal + rep0 */
++      UInt32 temp;
++      UInt32 lenTest2;
++      const Byte *data2 = data - (reps[0] + 1);
++      UInt32 limit = p->numFastBytes + 1;
++      if (limit > numAvailFull)
++        limit = numAvailFull;
++
++      for (temp = 1; temp < limit && data[temp] == data2[temp]; temp++);
++      lenTest2 = temp - 1;
++      if (lenTest2 >= 2)
++      {
++        UInt32 state2 = kLiteralNextStates[state];
++        UInt32 posStateNext = (position + 1) & p->pbMask;
++        UInt32 nextRepMatchPrice = curAnd1Price +
++            GET_PRICE_1(p->isMatch[state2][posStateNext]) +
++            GET_PRICE_1(p->isRep[state2]);
++        /* for (; lenTest2 >= 2; lenTest2--) */
++        {
++          UInt32 curAndLenPrice;
++          COptimal *opt;
++          UInt32 offset = cur + 1 + lenTest2;
++          while (lenEnd < offset)
++            p->opt[++lenEnd].price = kInfinityPrice;
++          curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext);
++          opt = &p->opt[offset];
++          if (curAndLenPrice < opt->price)
++          {
++            opt->price = curAndLenPrice;
++            opt->posPrev = cur + 1;
++            opt->backPrev = 0;
++            opt->prev1IsChar = True;
++            opt->prev2 = False;
++          }
++        }
++      }
++    }
++
++    startLen = 2; /* speed optimization */
++    {
++    UInt32 repIndex;
++    for (repIndex = 0; repIndex < LZMA_NUM_REPS; repIndex++)
++    {
++      UInt32 lenTest;
++      UInt32 lenTestTemp;
++      UInt32 price;
++      const Byte *data2 = data - (reps[repIndex] + 1);
++      if (data[0] != data2[0] || data[1] != data2[1])
++        continue;
++      for (lenTest = 2; lenTest < numAvail && data[lenTest] == data2[lenTest]; lenTest++);
++      while (lenEnd < cur + lenTest)
++        p->opt[++lenEnd].price = kInfinityPrice;
++      lenTestTemp = lenTest;
++      price = repMatchPrice + GetPureRepPrice(p, repIndex, state, posState);
++      do
++      {
++        UInt32 curAndLenPrice = price + p->repLenEnc.prices[posState][lenTest - 2];
++        COptimal *opt = &p->opt[cur + lenTest];
++        if (curAndLenPrice < opt->price)
++        {
++          opt->price = curAndLenPrice;
++          opt->posPrev = cur;
++          opt->backPrev = repIndex;
++          opt->prev1IsChar = False;
++        }
++      }
++      while (--lenTest >= 2);
++      lenTest = lenTestTemp;
++
++      if (repIndex == 0)
++        startLen = lenTest + 1;
++
++      /* if (_maxMode) */
++        {
++          UInt32 lenTest2 = lenTest + 1;
++          UInt32 limit = lenTest2 + p->numFastBytes;
++          UInt32 nextRepMatchPrice;
++          if (limit > numAvailFull)
++            limit = numAvailFull;
++          for (; lenTest2 < limit && data[lenTest2] == data2[lenTest2]; lenTest2++);
++          lenTest2 -= lenTest + 1;
++          if (lenTest2 >= 2)
++          {
++            UInt32 state2 = kRepNextStates[state];
++            UInt32 posStateNext = (position + lenTest) & p->pbMask;
++            UInt32 curAndLenCharPrice =
++                price + p->repLenEnc.prices[posState][lenTest - 2] +
++                GET_PRICE_0(p->isMatch[state2][posStateNext]) +
++                LitEnc_GetPriceMatched(LIT_PROBS(position + lenTest, data[lenTest - 1]),
++                    data[lenTest], data2[lenTest], p->ProbPrices);
++            state2 = kLiteralNextStates[state2];
++            posStateNext = (position + lenTest + 1) & p->pbMask;
++            nextRepMatchPrice = curAndLenCharPrice +
++                GET_PRICE_1(p->isMatch[state2][posStateNext]) +
++                GET_PRICE_1(p->isRep[state2]);
++
++            /* for (; lenTest2 >= 2; lenTest2--) */
++            {
++              UInt32 curAndLenPrice;
++              COptimal *opt;
++              UInt32 offset = cur + lenTest + 1 + lenTest2;
++              while (lenEnd < offset)
++                p->opt[++lenEnd].price = kInfinityPrice;
++              curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext);
++              opt = &p->opt[offset];
++              if (curAndLenPrice < opt->price)
++              {
++                opt->price = curAndLenPrice;
++                opt->posPrev = cur + lenTest + 1;
++                opt->backPrev = 0;
++                opt->prev1IsChar = True;
++                opt->prev2 = True;
++                opt->posPrev2 = cur;
++                opt->backPrev2 = repIndex;
++              }
++            }
++          }
++        }
++    }
++    }
++    /* for (UInt32 lenTest = 2; lenTest <= newLen; lenTest++) */
++    if (newLen > numAvail)
++    {
++      newLen = numAvail;
++      for (numPairs = 0; newLen > matches[numPairs]; numPairs += 2);
++      matches[numPairs] = newLen;
++      numPairs += 2;
++    }
++    if (newLen >= startLen)
++    {
++      UInt32 normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[state]);
++      UInt32 offs, curBack, posSlot;
++      UInt32 lenTest;
++      while (lenEnd < cur + newLen)
++        p->opt[++lenEnd].price = kInfinityPrice;
++
++      offs = 0;
++      while (startLen > matches[offs])
++        offs += 2;
++      curBack = matches[offs + 1];
++      GetPosSlot2(curBack, posSlot);
++      for (lenTest = /*2*/ startLen; ; lenTest++)
++      {
++        UInt32 curAndLenPrice = normalMatchPrice + p->lenEnc.prices[posState][lenTest - LZMA_MATCH_LEN_MIN];
++        UInt32 lenToPosState = GetLenToPosState(lenTest);
++        COptimal *opt;
++        if (curBack < kNumFullDistances)
++          curAndLenPrice += p->distancesPrices[lenToPosState][curBack];
++        else
++          curAndLenPrice += p->posSlotPrices[lenToPosState][posSlot] + p->alignPrices[curBack & kAlignMask];
++
++        opt = &p->opt[cur + lenTest];
++        if (curAndLenPrice < opt->price)
++        {
++          opt->price = curAndLenPrice;
++          opt->posPrev = cur;
++          opt->backPrev = curBack + LZMA_NUM_REPS;
++          opt->prev1IsChar = False;
++        }
++
++        if (/*_maxMode && */lenTest == matches[offs])
++        {
++          /* Try Match + Literal + Rep0 */
++          const Byte *data2 = data - (curBack + 1);
++          UInt32 lenTest2 = lenTest + 1;
++          UInt32 limit = lenTest2 + p->numFastBytes;
++          UInt32 nextRepMatchPrice;
++          if (limit > numAvailFull)
++            limit = numAvailFull;
++          for (; lenTest2 < limit && data[lenTest2] == data2[lenTest2]; lenTest2++);
++          lenTest2 -= lenTest + 1;
++          if (lenTest2 >= 2)
++          {
++            UInt32 state2 = kMatchNextStates[state];
++            UInt32 posStateNext = (position + lenTest) & p->pbMask;
++            UInt32 curAndLenCharPrice = curAndLenPrice +
++                GET_PRICE_0(p->isMatch[state2][posStateNext]) +
++                LitEnc_GetPriceMatched(LIT_PROBS(position + lenTest, data[lenTest - 1]),
++                    data[lenTest], data2[lenTest], p->ProbPrices);
++            state2 = kLiteralNextStates[state2];
++            posStateNext = (posStateNext + 1) & p->pbMask;
++            nextRepMatchPrice = curAndLenCharPrice +
++                GET_PRICE_1(p->isMatch[state2][posStateNext]) +
++                GET_PRICE_1(p->isRep[state2]);
++
++            /* for (; lenTest2 >= 2; lenTest2--) */
++            {
++              UInt32 offset = cur + lenTest + 1 + lenTest2;
++              UInt32 curAndLenPrice;
++              COptimal *opt;
++              while (lenEnd < offset)
++                p->opt[++lenEnd].price = kInfinityPrice;
++              curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext);
++              opt = &p->opt[offset];
++              if (curAndLenPrice < opt->price)
++              {
++                opt->price = curAndLenPrice;
++                opt->posPrev = cur + lenTest + 1;
++                opt->backPrev = 0;
++                opt->prev1IsChar = True;
++                opt->prev2 = True;
++                opt->posPrev2 = cur;
++                opt->backPrev2 = curBack + LZMA_NUM_REPS;
++              }
++            }
++          }
++          offs += 2;
++          if (offs == numPairs)
++            break;
++          curBack = matches[offs + 1];
++          if (curBack >= kNumFullDistances)
++            GetPosSlot2(curBack, posSlot);
++        }
++      }
++    }
++  }
++}
++
++#define ChangePair(smallDist, bigDist) (((bigDist) >> 7) > (smallDist))
++
++static UInt32 GetOptimumFast(CLzmaEnc *p, UInt32 *backRes)
++{
++  UInt32 numAvail, mainLen, mainDist, numPairs, repIndex, repLen, i;
++  const Byte *data;
++  const UInt32 *matches;
++
++  if (p->additionalOffset == 0)
++    mainLen = ReadMatchDistances(p, &numPairs);
++  else
++  {
++    mainLen = p->longestMatchLength;
++    numPairs = p->numPairs;
++  }
++
++  numAvail = p->numAvail;
++  *backRes = (UInt32)-1;
++  if (numAvail < 2)
++    return 1;
++  if (numAvail > LZMA_MATCH_LEN_MAX)
++    numAvail = LZMA_MATCH_LEN_MAX;
++  data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
++
++  repLen = repIndex = 0;
++  for (i = 0; i < LZMA_NUM_REPS; i++)
++  {
++    UInt32 len;
++    const Byte *data2 = data - (p->reps[i] + 1);
++    if (data[0] != data2[0] || data[1] != data2[1])
++      continue;
++    for (len = 2; len < numAvail && data[len] == data2[len]; len++);
++    if (len >= p->numFastBytes)
++    {
++      *backRes = i;
++      MovePos(p, len - 1);
++      return len;
++    }
++    if (len > repLen)
++    {
++      repIndex = i;
++      repLen = len;
++    }
++  }
++
++  matches = p->matches;
++  if (mainLen >= p->numFastBytes)
++  {
++    *backRes = matches[numPairs - 1] + LZMA_NUM_REPS;
++    MovePos(p, mainLen - 1);
++    return mainLen;
++  }
++
++  mainDist = 0; /* for GCC */
++  if (mainLen >= 2)
++  {
++    mainDist = matches[numPairs - 1];
++    while (numPairs > 2 && mainLen == matches[numPairs - 4] + 1)
++    {
++      if (!ChangePair(matches[numPairs - 3], mainDist))
++        break;
++      numPairs -= 2;
++      mainLen = matches[numPairs - 2];
++      mainDist = matches[numPairs - 1];
++    }
++    if (mainLen == 2 && mainDist >= 0x80)
++      mainLen = 1;
++  }
++
++  if (repLen >= 2 && (
++        (repLen + 1 >= mainLen) ||
++        (repLen + 2 >= mainLen && mainDist >= (1 << 9)) ||
++        (repLen + 3 >= mainLen && mainDist >= (1 << 15))))
++  {
++    *backRes = repIndex;
++    MovePos(p, repLen - 1);
++    return repLen;
++  }
++
++  if (mainLen < 2 || numAvail <= 2)
++    return 1;
++
++  p->longestMatchLength = ReadMatchDistances(p, &p->numPairs);
++  if (p->longestMatchLength >= 2)
++  {
++    UInt32 newDistance = matches[p->numPairs - 1];
++    if ((p->longestMatchLength >= mainLen && newDistance < mainDist) ||
++        (p->longestMatchLength == mainLen + 1 && !ChangePair(mainDist, newDistance)) ||
++        (p->longestMatchLength > mainLen + 1) ||
++        (p->longestMatchLength + 1 >= mainLen && mainLen >= 3 && ChangePair(newDistance, mainDist)))
++      return 1;
++  }
++
++  data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
++  for (i = 0; i < LZMA_NUM_REPS; i++)
++  {
++    UInt32 len, limit;
++    const Byte *data2 = data - (p->reps[i] + 1);
++    if (data[0] != data2[0] || data[1] != data2[1])
++      continue;
++    limit = mainLen - 1;
++    for (len = 2; len < limit && data[len] == data2[len]; len++);
++    if (len >= limit)
++      return 1;
++  }
++  *backRes = mainDist + LZMA_NUM_REPS;
++  MovePos(p, mainLen - 2);
++  return mainLen;
++}
++
++static void WriteEndMarker(CLzmaEnc *p, UInt32 posState)
++{
++  UInt32 len;
++  RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 1);
++  RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 0);
++  p->state = kMatchNextStates[p->state];
++  len = LZMA_MATCH_LEN_MIN;
++  LenEnc_Encode2(&p->lenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices);
++  RcTree_Encode(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], kNumPosSlotBits, (1 << kNumPosSlotBits) - 1);
++  RangeEnc_EncodeDirectBits(&p->rc, (((UInt32)1 << 30) - 1) >> kNumAlignBits, 30 - kNumAlignBits);
++  RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, kAlignMask);
++}
++
++static SRes CheckErrors(CLzmaEnc *p)
++{
++  if (p->result != SZ_OK)
++    return p->result;
++  if (p->rc.res != SZ_OK)
++    p->result = SZ_ERROR_WRITE;
++  if (p->matchFinderBase.result != SZ_OK)
++    p->result = SZ_ERROR_READ;
++  if (p->result != SZ_OK)
++    p->finished = True;
++  return p->result;
++}
++
++static SRes Flush(CLzmaEnc *p, UInt32 nowPos)
++{
++  /* ReleaseMFStream(); */
++  p->finished = True;
++  if (p->writeEndMark)
++    WriteEndMarker(p, nowPos & p->pbMask);
++  RangeEnc_FlushData(&p->rc);
++  RangeEnc_FlushStream(&p->rc);
++  return CheckErrors(p);
++}
++
++static void FillAlignPrices(CLzmaEnc *p)
++{
++  UInt32 i;
++  for (i = 0; i < kAlignTableSize; i++)
++    p->alignPrices[i] = RcTree_ReverseGetPrice(p->posAlignEncoder, kNumAlignBits, i, p->ProbPrices);
++  p->alignPriceCount = 0;
++}
++
++static void FillDistancesPrices(CLzmaEnc *p)
++{
++  UInt32 tempPrices[kNumFullDistances];
++  UInt32 i, lenToPosState;
++  for (i = kStartPosModelIndex; i < kNumFullDistances; i++)
++  {
++    UInt32 posSlot = GetPosSlot1(i);
++    UInt32 footerBits = ((posSlot >> 1) - 1);
++    UInt32 base = ((2 | (posSlot & 1)) << footerBits);
++    tempPrices[i] = RcTree_ReverseGetPrice(p->posEncoders + base - posSlot - 1, footerBits, i - base, p->ProbPrices);
++  }
++
++  for (lenToPosState = 0; lenToPosState < kNumLenToPosStates; lenToPosState++)
++  {
++    UInt32 posSlot;
++    const CLzmaProb *encoder = p->posSlotEncoder[lenToPosState];
++    UInt32 *posSlotPrices = p->posSlotPrices[lenToPosState];
++    for (posSlot = 0; posSlot < p->distTableSize; posSlot++)
++      posSlotPrices[posSlot] = RcTree_GetPrice(encoder, kNumPosSlotBits, posSlot, p->ProbPrices);
++    for (posSlot = kEndPosModelIndex; posSlot < p->distTableSize; posSlot++)
++      posSlotPrices[posSlot] += ((((posSlot >> 1) - 1) - kNumAlignBits) << kNumBitPriceShiftBits);
++
++    {
++      UInt32 *distancesPrices = p->distancesPrices[lenToPosState];
++      UInt32 i;
++      for (i = 0; i < kStartPosModelIndex; i++)
++        distancesPrices[i] = posSlotPrices[i];
++      for (; i < kNumFullDistances; i++)
++        distancesPrices[i] = posSlotPrices[GetPosSlot1(i)] + tempPrices[i];
++    }
++  }
++  p->matchPriceCount = 0;
++}
++
++void LzmaEnc_Construct(CLzmaEnc *p)
++{
++  RangeEnc_Construct(&p->rc);
++  MatchFinder_Construct(&p->matchFinderBase);
++  #ifndef _7ZIP_ST
++  MatchFinderMt_Construct(&p->matchFinderMt);
++  p->matchFinderMt.MatchFinder = &p->matchFinderBase;
++  #endif
++
++  {
++    CLzmaEncProps props;
++    LzmaEncProps_Init(&props);
++    LzmaEnc_SetProps(p, &props);
++  }
++
++  #ifndef LZMA_LOG_BSR
++  LzmaEnc_FastPosInit(p->g_FastPos);
++  #endif
++
++  LzmaEnc_InitPriceTables(p->ProbPrices);
++  p->litProbs = 0;
++  p->saveState.litProbs = 0;
++}
++
++CLzmaEncHandle LzmaEnc_Create(ISzAlloc *alloc)
++{
++  void *p;
++  p = alloc->Alloc(alloc, sizeof(CLzmaEnc));
++  if (p != 0)
++    LzmaEnc_Construct((CLzmaEnc *)p);
++  return p;
++}
++
++void LzmaEnc_FreeLits(CLzmaEnc *p, ISzAlloc *alloc)
++{
++  alloc->Free(alloc, p->litProbs);
++  alloc->Free(alloc, p->saveState.litProbs);
++  p->litProbs = 0;
++  p->saveState.litProbs = 0;
++}
++
++void LzmaEnc_Destruct(CLzmaEnc *p, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  #ifndef _7ZIP_ST
++  MatchFinderMt_Destruct(&p->matchFinderMt, allocBig);
++  #endif
++  MatchFinder_Free(&p->matchFinderBase, allocBig);
++  LzmaEnc_FreeLits(p, alloc);
++  RangeEnc_Free(&p->rc, alloc);
++}
++
++void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  LzmaEnc_Destruct((CLzmaEnc *)p, alloc, allocBig);
++  alloc->Free(alloc, p);
++}
++
++static SRes LzmaEnc_CodeOneBlock(CLzmaEnc *p, Bool useLimits, UInt32 maxPackSize, UInt32 maxUnpackSize)
++{
++  UInt32 nowPos32, startPos32;
++  if (p->needInit)
++  {
++    p->matchFinder.Init(p->matchFinderObj);
++    p->needInit = 0;
++  }
++
++  if (p->finished)
++    return p->result;
++  RINOK(CheckErrors(p));
++
++  nowPos32 = (UInt32)p->nowPos64;
++  startPos32 = nowPos32;
++
++  if (p->nowPos64 == 0)
++  {
++    UInt32 numPairs;
++    Byte curByte;
++    if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0)
++      return Flush(p, nowPos32);
++    ReadMatchDistances(p, &numPairs);
++    RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][0], 0);
++    p->state = kLiteralNextStates[p->state];
++    curByte = p->matchFinder.GetIndexByte(p->matchFinderObj, 0 - p->additionalOffset);
++    LitEnc_Encode(&p->rc, p->litProbs, curByte);
++    p->additionalOffset--;
++    nowPos32++;
++  }
++
++  if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) != 0)
++  for (;;)
++  {
++    UInt32 pos, len, posState;
++
++    if (p->fastMode)
++      len = GetOptimumFast(p, &pos);
++    else
++      len = GetOptimum(p, nowPos32, &pos);
++
++    #ifdef SHOW_STAT2
++    printf("\n pos = %4X,   len = %d   pos = %d", nowPos32, len, pos);
++    #endif
++
++    posState = nowPos32 & p->pbMask;
++    if (len == 1 && pos == (UInt32)-1)
++    {
++      Byte curByte;
++      CLzmaProb *probs;
++      const Byte *data;
++
++      RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 0);
++      data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset;
++      curByte = *data;
++      probs = LIT_PROBS(nowPos32, *(data - 1));
++      if (IsCharState(p->state))
++        LitEnc_Encode(&p->rc, probs, curByte);
++      else
++        LitEnc_EncodeMatched(&p->rc, probs, curByte, *(data - p->reps[0] - 1));
++      p->state = kLiteralNextStates[p->state];
++    }
++    else
++    {
++      RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 1);
++      if (pos < LZMA_NUM_REPS)
++      {
++        RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 1);
++        if (pos == 0)
++        {
++          RangeEnc_EncodeBit(&p->rc, &p->isRepG0[p->state], 0);
++          RangeEnc_EncodeBit(&p->rc, &p->isRep0Long[p->state][posState], ((len == 1) ? 0 : 1));
++        }
++        else
++        {
++          UInt32 distance = p->reps[pos];
++          RangeEnc_EncodeBit(&p->rc, &p->isRepG0[p->state], 1);
++          if (pos == 1)
++            RangeEnc_EncodeBit(&p->rc, &p->isRepG1[p->state], 0);
++          else
++          {
++            RangeEnc_EncodeBit(&p->rc, &p->isRepG1[p->state], 1);
++            RangeEnc_EncodeBit(&p->rc, &p->isRepG2[p->state], pos - 2);
++            if (pos == 3)
++              p->reps[3] = p->reps[2];
++            p->reps[2] = p->reps[1];
++          }
++          p->reps[1] = p->reps[0];
++          p->reps[0] = distance;
++        }
++        if (len == 1)
++          p->state = kShortRepNextStates[p->state];
++        else
++        {
++          LenEnc_Encode2(&p->repLenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices);
++          p->state = kRepNextStates[p->state];
++        }
++      }
++      else
++      {
++        UInt32 posSlot;
++        RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 0);
++        p->state = kMatchNextStates[p->state];
++        LenEnc_Encode2(&p->lenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices);
++        pos -= LZMA_NUM_REPS;
++        GetPosSlot(pos, posSlot);
++        RcTree_Encode(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], kNumPosSlotBits, posSlot);
++
++        if (posSlot >= kStartPosModelIndex)
++        {
++          UInt32 footerBits = ((posSlot >> 1) - 1);
++          UInt32 base = ((2 | (posSlot & 1)) << footerBits);
++          UInt32 posReduced = pos - base;
++
++          if (posSlot < kEndPosModelIndex)
++            RcTree_ReverseEncode(&p->rc, p->posEncoders + base - posSlot - 1, footerBits, posReduced);
++          else
++          {
++            RangeEnc_EncodeDirectBits(&p->rc, posReduced >> kNumAlignBits, footerBits - kNumAlignBits);
++            RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, posReduced & kAlignMask);
++            p->alignPriceCount++;
++          }
++        }
++        p->reps[3] = p->reps[2];
++        p->reps[2] = p->reps[1];
++        p->reps[1] = p->reps[0];
++        p->reps[0] = pos;
++        p->matchPriceCount++;
++      }
++    }
++    p->additionalOffset -= len;
++    nowPos32 += len;
++    if (p->additionalOffset == 0)
++    {
++      UInt32 processed;
++      if (!p->fastMode)
++      {
++        if (p->matchPriceCount >= (1 << 7))
++          FillDistancesPrices(p);
++        if (p->alignPriceCount >= kAlignTableSize)
++          FillAlignPrices(p);
++      }
++      if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0)
++        break;
++      processed = nowPos32 - startPos32;
++      if (useLimits)
++      {
++        if (processed + kNumOpts + 300 >= maxUnpackSize ||
++            RangeEnc_GetProcessed(&p->rc) + kNumOpts * 2 >= maxPackSize)
++          break;
++      }
++      else if (processed >= (1 << 15))
++      {
++        p->nowPos64 += nowPos32 - startPos32;
++        return CheckErrors(p);
++      }
++    }
++  }
++  p->nowPos64 += nowPos32 - startPos32;
++  return Flush(p, nowPos32);
++}
++
++#define kBigHashDicLimit ((UInt32)1 << 24)
++
++static SRes LzmaEnc_Alloc(CLzmaEnc *p, UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  UInt32 beforeSize = kNumOpts;
++  Bool btMode;
++  if (!RangeEnc_Alloc(&p->rc, alloc))
++    return SZ_ERROR_MEM;
++  btMode = (p->matchFinderBase.btMode != 0);
++  #ifndef _7ZIP_ST
++  p->mtMode = (p->multiThread && !p->fastMode && btMode);
++  #endif
++
++  {
++    unsigned lclp = p->lc + p->lp;
++    if (p->litProbs == 0 || p->saveState.litProbs == 0 || p->lclp != lclp)
++    {
++      LzmaEnc_FreeLits(p, alloc);
++      p->litProbs = (CLzmaProb *)alloc->Alloc(alloc, (0x300 << lclp) * sizeof(CLzmaProb));
++      p->saveState.litProbs = (CLzmaProb *)alloc->Alloc(alloc, (0x300 << lclp) * sizeof(CLzmaProb));
++      if (p->litProbs == 0 || p->saveState.litProbs == 0)
++      {
++        LzmaEnc_FreeLits(p, alloc);
++        return SZ_ERROR_MEM;
++      }
++      p->lclp = lclp;
++    }
++  }
++
++  p->matchFinderBase.bigHash = (p->dictSize > kBigHashDicLimit);
++
++  if (beforeSize + p->dictSize < keepWindowSize)
++    beforeSize = keepWindowSize - p->dictSize;
++
++  #ifndef _7ZIP_ST
++  if (p->mtMode)
++  {
++    RINOK(MatchFinderMt_Create(&p->matchFinderMt, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig));
++    p->matchFinderObj = &p->matchFinderMt;
++    MatchFinderMt_CreateVTable(&p->matchFinderMt, &p->matchFinder);
++  }
++  else
++  #endif
++  {
++    if (!MatchFinder_Create(&p->matchFinderBase, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig))
++      return SZ_ERROR_MEM;
++    p->matchFinderObj = &p->matchFinderBase;
++    MatchFinder_CreateVTable(&p->matchFinderBase, &p->matchFinder);
++  }
++  return SZ_OK;
++}
++
++void LzmaEnc_Init(CLzmaEnc *p)
++{
++  UInt32 i;
++  p->state = 0;
++  for (i = 0 ; i < LZMA_NUM_REPS; i++)
++    p->reps[i] = 0;
++
++  RangeEnc_Init(&p->rc);
++
++
++  for (i = 0; i < kNumStates; i++)
++  {
++    UInt32 j;
++    for (j = 0; j < LZMA_NUM_PB_STATES_MAX; j++)
++    {
++      p->isMatch[i][j] = kProbInitValue;
++      p->isRep0Long[i][j] = kProbInitValue;
++    }
++    p->isRep[i] = kProbInitValue;
++    p->isRepG0[i] = kProbInitValue;
++    p->isRepG1[i] = kProbInitValue;
++    p->isRepG2[i] = kProbInitValue;
++  }
++
++  {
++    UInt32 num = 0x300 << (p->lp + p->lc);
++    for (i = 0; i < num; i++)
++      p->litProbs[i] = kProbInitValue;
++  }
++
++  {
++    for (i = 0; i < kNumLenToPosStates; i++)
++    {
++      CLzmaProb *probs = p->posSlotEncoder[i];
++      UInt32 j;
++      for (j = 0; j < (1 << kNumPosSlotBits); j++)
++        probs[j] = kProbInitValue;
++    }
++  }
++  {
++    for (i = 0; i < kNumFullDistances - kEndPosModelIndex; i++)
++      p->posEncoders[i] = kProbInitValue;
++  }
++
++  LenEnc_Init(&p->lenEnc.p);
++  LenEnc_Init(&p->repLenEnc.p);
++
++  for (i = 0; i < (1 << kNumAlignBits); i++)
++    p->posAlignEncoder[i] = kProbInitValue;
++
++  p->optimumEndIndex = 0;
++  p->optimumCurrentIndex = 0;
++  p->additionalOffset = 0;
++
++  p->pbMask = (1 << p->pb) - 1;
++  p->lpMask = (1 << p->lp) - 1;
++}
++
++void LzmaEnc_InitPrices(CLzmaEnc *p)
++{
++  if (!p->fastMode)
++  {
++    FillDistancesPrices(p);
++    FillAlignPrices(p);
++  }
++
++  p->lenEnc.tableSize =
++  p->repLenEnc.tableSize =
++      p->numFastBytes + 1 - LZMA_MATCH_LEN_MIN;
++  LenPriceEnc_UpdateTables(&p->lenEnc, 1 << p->pb, p->ProbPrices);
++  LenPriceEnc_UpdateTables(&p->repLenEnc, 1 << p->pb, p->ProbPrices);
++}
++
++static SRes LzmaEnc_AllocAndInit(CLzmaEnc *p, UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  UInt32 i;
++  for (i = 0; i < (UInt32)kDicLogSizeMaxCompress; i++)
++    if (p->dictSize <= ((UInt32)1 << i))
++      break;
++  p->distTableSize = i * 2;
++
++  p->finished = False;
++  p->result = SZ_OK;
++  RINOK(LzmaEnc_Alloc(p, keepWindowSize, alloc, allocBig));
++  LzmaEnc_Init(p);
++  LzmaEnc_InitPrices(p);
++  p->nowPos64 = 0;
++  return SZ_OK;
++}
++
++static SRes LzmaEnc_Prepare(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream,
++    ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  p->matchFinderBase.stream = inStream;
++  p->needInit = 1;
++  p->rc.outStream = outStream;
++  return LzmaEnc_AllocAndInit(p, 0, alloc, allocBig);
++}
++
++SRes LzmaEnc_PrepareForLzma2(CLzmaEncHandle pp,
++    ISeqInStream *inStream, UInt32 keepWindowSize,
++    ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  p->matchFinderBase.stream = inStream;
++  p->needInit = 1;
++  return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig);
++}
++
++static void LzmaEnc_SetInputBuf(CLzmaEnc *p, const Byte *src, SizeT srcLen)
++{
++  p->matchFinderBase.directInput = 1;
++  p->matchFinderBase.bufferBase = (Byte *)src;
++  p->matchFinderBase.directInputRem = srcLen;
++}
++
++SRes LzmaEnc_MemPrepare(CLzmaEncHandle pp, const Byte *src, SizeT srcLen,
++    UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  LzmaEnc_SetInputBuf(p, src, srcLen);
++  p->needInit = 1;
++
++  return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig);
++}
++
++void LzmaEnc_Finish(CLzmaEncHandle pp)
++{
++  #ifndef _7ZIP_ST
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  if (p->mtMode)
++    MatchFinderMt_ReleaseStream(&p->matchFinderMt);
++  #else
++  pp = pp;
++  #endif
++}
++
++typedef struct
++{
++  ISeqOutStream funcTable;
++  Byte *data;
++  SizeT rem;
++  Bool overflow;
++} CSeqOutStreamBuf;
++
++static size_t MyWrite(void *pp, const void *data, size_t size)
++{
++  CSeqOutStreamBuf *p = (CSeqOutStreamBuf *)pp;
++  if (p->rem < size)
++  {
++    size = p->rem;
++    p->overflow = True;
++  }
++  memcpy(p->data, data, size);
++  p->rem -= size;
++  p->data += size;
++  return size;
++}
++
++
++UInt32 LzmaEnc_GetNumAvailableBytes(CLzmaEncHandle pp)
++{
++  const CLzmaEnc *p = (CLzmaEnc *)pp;
++  return p->matchFinder.GetNumAvailableBytes(p->matchFinderObj);
++}
++
++const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle pp)
++{
++  const CLzmaEnc *p = (CLzmaEnc *)pp;
++  return p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset;
++}
++
++SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, Bool reInit,
++    Byte *dest, size_t *destLen, UInt32 desiredPackSize, UInt32 *unpackSize)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  UInt64 nowPos64;
++  SRes res;
++  CSeqOutStreamBuf outStream;
++
++  outStream.funcTable.Write = MyWrite;
++  outStream.data = dest;
++  outStream.rem = *destLen;
++  outStream.overflow = False;
++
++  p->writeEndMark = False;
++  p->finished = False;
++  p->result = SZ_OK;
++
++  if (reInit)
++    LzmaEnc_Init(p);
++  LzmaEnc_InitPrices(p);
++  nowPos64 = p->nowPos64;
++  RangeEnc_Init(&p->rc);
++  p->rc.outStream = &outStream.funcTable;
++
++  res = LzmaEnc_CodeOneBlock(p, True, desiredPackSize, *unpackSize);
++
++  *unpackSize = (UInt32)(p->nowPos64 - nowPos64);
++  *destLen -= outStream.rem;
++  if (outStream.overflow)
++    return SZ_ERROR_OUTPUT_EOF;
++
++  return res;
++}
++
++static SRes LzmaEnc_Encode2(CLzmaEnc *p, ICompressProgress *progress)
++{
++  SRes res = SZ_OK;
++
++  #ifndef _7ZIP_ST
++  Byte allocaDummy[0x300];
++  int i = 0;
++  for (i = 0; i < 16; i++)
++    allocaDummy[i] = (Byte)i;
++  #endif
++
++  for (;;)
++  {
++    res = LzmaEnc_CodeOneBlock(p, False, 0, 0);
++    if (res != SZ_OK || p->finished != 0)
++      break;
++    if (progress != 0)
++    {
++      res = progress->Progress(progress, p->nowPos64, RangeEnc_GetProcessed(&p->rc));
++      if (res != SZ_OK)
++      {
++        res = SZ_ERROR_PROGRESS;
++        break;
++      }
++    }
++  }
++  LzmaEnc_Finish(p);
++  return res;
++}
++
++SRes LzmaEnc_Encode(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream, ICompressProgress *progress,
++    ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  RINOK(LzmaEnc_Prepare(pp, outStream, inStream, alloc, allocBig));
++  return LzmaEnc_Encode2((CLzmaEnc *)pp, progress);
++}
++
++SRes LzmaEnc_WriteProperties(CLzmaEncHandle pp, Byte *props, SizeT *size)
++{
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++  int i;
++  UInt32 dictSize = p->dictSize;
++  if (*size < LZMA_PROPS_SIZE)
++    return SZ_ERROR_PARAM;
++  *size = LZMA_PROPS_SIZE;
++  props[0] = (Byte)((p->pb * 5 + p->lp) * 9 + p->lc);
++
++  for (i = 11; i <= 30; i++)
++  {
++    if (dictSize <= ((UInt32)2 << i))
++    {
++      dictSize = (2 << i);
++      break;
++    }
++    if (dictSize <= ((UInt32)3 << i))
++    {
++      dictSize = (3 << i);
++      break;
++    }
++  }
++
++  for (i = 0; i < 4; i++)
++    props[1 + i] = (Byte)(dictSize >> (8 * i));
++  return SZ_OK;
++}
++
++SRes LzmaEnc_MemEncode(CLzmaEncHandle pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen,
++    int writeEndMark, ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  SRes res;
++  CLzmaEnc *p = (CLzmaEnc *)pp;
++
++  CSeqOutStreamBuf outStream;
++
++  LzmaEnc_SetInputBuf(p, src, srcLen);
++
++  outStream.funcTable.Write = MyWrite;
++  outStream.data = dest;
++  outStream.rem = *destLen;
++  outStream.overflow = False;
++
++  p->writeEndMark = writeEndMark;
++
++  p->rc.outStream = &outStream.funcTable;
++  res = LzmaEnc_MemPrepare(pp, src, srcLen, 0, alloc, allocBig);
++  if (res == SZ_OK)
++    res = LzmaEnc_Encode2(p, progress);
++
++  *destLen -= outStream.rem;
++  if (outStream.overflow)
++    return SZ_ERROR_OUTPUT_EOF;
++  return res;
++}
++
++SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen,
++    const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark,
++    ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig)
++{
++  CLzmaEnc *p = (CLzmaEnc *)LzmaEnc_Create(alloc);
++  SRes res;
++  if (p == 0)
++    return SZ_ERROR_MEM;
++
++  res = LzmaEnc_SetProps(p, props);
++  if (res == SZ_OK)
++  {
++    res = LzmaEnc_WriteProperties(p, propsEncoded, propsSize);
++    if (res == SZ_OK)
++      res = LzmaEnc_MemEncode(p, dest, destLen, src, srcLen,
++          writeEndMark, progress, alloc, allocBig);
++  }
++
++  LzmaEnc_Destroy(p, alloc, allocBig);
++  return res;
++}
+diff --git a/lib/lzma/Makefile b/lib/lzma/Makefile
+new file mode 100644
+index 000000000000..02e799c99381
+--- /dev/null
++++ b/lib/lzma/Makefile
+@@ -0,0 +1,7 @@
++lzma_compress-objs := LzFind.o LzmaEnc.o
++lzma_decompress-objs := LzmaDec.o
++
++obj-$(CONFIG_LZMA_COMPRESS) += lzma_compress.o
++obj-$(CONFIG_LZMA_DECOMPRESS) += lzma_decompress.o
++
++EXTRA_CFLAGS += -Iinclude/linux -Iinclude/linux/lzma -include types.h
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0059-fs-jffs2-EOF-marker.patch b/nixos/modules/kernel-patches/0059-fs-jffs2-EOF-marker.patch
new file mode 100644
index 0000000..f3c3fa4
--- /dev/null
+++ b/nixos/modules/kernel-patches/0059-fs-jffs2-EOF-marker.patch
@@ -0,0 +1,74 @@
+From c83ff8f115ec664a51c53201dea7573e2fb4ce17 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 27 Sep 2022 16:22:04 +0200
+Subject: [PATCH 59/96] fs: jffs2: EOF marker
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ fs/jffs2/build.c | 10 ++++++++++
+ fs/jffs2/scan.c  | 21 +++++++++++++++++++--
+ 2 files changed, 29 insertions(+), 2 deletions(-)
+
+diff --git a/fs/jffs2/build.c b/fs/jffs2/build.c
+index 837cd55fd4c5..e32b958eb990 100644
+--- a/fs/jffs2/build.c
++++ b/fs/jffs2/build.c
+@@ -117,6 +117,16 @@ static int jffs2_build_filesystem(struct jffs2_sb_info *c)
+ 	dbg_fsbuild("scanned flash completely\n");
+ 	jffs2_dbg_dump_block_lists_nolock(c);
+ 
++	if (c->flags & (1 << 7)) {
++		printk("%s(): unlocking the mtd device... ", __func__);
++		mtd_unlock(c->mtd, 0, c->mtd->size);
++		printk("done.\n");
++
++		printk("%s(): erasing all blocks after the end marker... ", __func__);
++		jffs2_erase_pending_blocks(c, -1);
++		printk("done.\n");
++	}
++
+ 	dbg_fsbuild("pass 1 starting\n");
+ 	c->flags |= JFFS2_SB_FLAG_BUILDING;
+ 	/* Now scan the directory tree, increasing nlink according to every dirent found. */
+diff --git a/fs/jffs2/scan.c b/fs/jffs2/scan.c
+index 29671e33a171..893092159113 100644
+--- a/fs/jffs2/scan.c
++++ b/fs/jffs2/scan.c
+@@ -148,8 +148,14 @@ int jffs2_scan_medium(struct jffs2_sb_info *c)
+ 		/* reset summary info for next eraseblock scan */
+ 		jffs2_sum_reset_collected(s);
+ 
+-		ret = jffs2_scan_eraseblock(c, jeb, buf_size?flashbuf:(flashbuf+jeb->offset),
+-						buf_size, s);
++		if (c->flags & (1 << 7)) {
++			if (mtd_block_isbad(c->mtd, jeb->offset))
++				ret = BLK_STATE_BADBLOCK;
++			else
++				ret = BLK_STATE_ALLFF;
++		} else
++			ret = jffs2_scan_eraseblock(c, jeb, buf_size?flashbuf:(flashbuf+jeb->offset),
++							buf_size, s);
+ 
+ 		if (ret < 0)
+ 			goto out;
+@@ -567,6 +573,17 @@ static int jffs2_scan_eraseblock (struct jffs2_sb_info *c, struct jffs2_eraseblo
+ 			return err;
+ 	}
+ 
++	if ((buf[0] == 0xde) &&
++		(buf[1] == 0xad) &&
++		(buf[2] == 0xc0) &&
++		(buf[3] == 0xde)) {
++		/* end of filesystem. erase everything after this point */
++		printk("%s(): End of filesystem marker found at 0x%x\n", __func__, jeb->offset);
++		c->flags |= (1 << 7);
++
++		return BLK_STATE_ALLFF;
++	}
++
+ 	/* We temporarily use 'ofs' as a pointer into the buffer/jeb */
+ 	ofs = 0;
+ 	max_ofs = EMPTY_SCAN_SIZE(c->sector_size);
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0060-netfilter-add-support-for-flushing-conntrack-via-pro.patch b/nixos/modules/kernel-patches/0060-netfilter-add-support-for-flushing-conntrack-via-pro.patch
new file mode 100644
index 0000000..76e1590
--- /dev/null
+++ b/nixos/modules/kernel-patches/0060-netfilter-add-support-for-flushing-conntrack-via-pro.patch
@@ -0,0 +1,95 @@
+From ec5167f51145842fe3aee19df68dc0fcebcd54e7 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 27 Sep 2022 16:22:04 +0200
+Subject: [PATCH 60/96] netfilter: add support for flushing conntrack via /proc
+
+lede-commit 8193bbe59a74d34d6a26d4a8cb857b1952905314
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ net/netfilter/nf_conntrack_standalone.c | 56 ++++++++++++++++++++++++-
+ 1 file changed, 54 insertions(+), 2 deletions(-)
+
+diff --git a/net/netfilter/nf_conntrack_standalone.c b/net/netfilter/nf_conntrack_standalone.c
+index 05895878610c..8d3be4cc340e 100644
+--- a/net/netfilter/nf_conntrack_standalone.c
++++ b/net/netfilter/nf_conntrack_standalone.c
+@@ -9,6 +9,7 @@
+ #include <linux/percpu.h>
+ #include <linux/netdevice.h>
+ #include <linux/security.h>
++#include <linux/inet.h>
+ #include <net/net_namespace.h>
+ #ifdef CONFIG_SYSCTL
+ #include <linux/sysctl.h>
+@@ -465,6 +466,56 @@ static int ct_cpu_seq_show(struct seq_file *seq, void *v)
+ 	return 0;
+ }
+ 
++struct kill_request {
++	u16 family;
++	union nf_inet_addr addr;
++};
++
++static int kill_matching(struct nf_conn *i, void *data)
++{
++	struct kill_request *kr = data;
++	struct nf_conntrack_tuple *t1 = &i->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
++	struct nf_conntrack_tuple *t2 = &i->tuplehash[IP_CT_DIR_REPLY].tuple;
++
++	if (!kr->family)
++		return 1;
++
++	if (t1->src.l3num != kr->family)
++		return 0;
++
++	return (nf_inet_addr_cmp(&kr->addr, &t1->src.u3) ||
++	        nf_inet_addr_cmp(&kr->addr, &t1->dst.u3) ||
++	        nf_inet_addr_cmp(&kr->addr, &t2->src.u3) ||
++	        nf_inet_addr_cmp(&kr->addr, &t2->dst.u3));
++}
++
++static int ct_file_write(struct file *file, char *buf, size_t count)
++{
++	struct seq_file *seq = file->private_data;
++	struct net *net = seq_file_net(seq);
++	struct kill_request kr = { };
++
++	if (count == 0)
++		return 0;
++
++	if (count >= INET6_ADDRSTRLEN)
++		count = INET6_ADDRSTRLEN - 1;
++
++	if (strnchr(buf, count, ':')) {
++		kr.family = AF_INET6;
++		if (!in6_pton(buf, count, (void *)&kr.addr, '\n', NULL))
++			return -EINVAL;
++	} else if (strnchr(buf, count, '.')) {
++		kr.family = AF_INET;
++		if (!in4_pton(buf, count, (void *)&kr.addr, '\n', NULL))
++			return -EINVAL;
++	}
++
++	nf_ct_iterate_cleanup_net(net, kill_matching, &kr, 0, 0);
++
++	return 0;
++}
++
+ static const struct seq_operations ct_cpu_seq_ops = {
+ 	.start	= ct_cpu_seq_start,
+ 	.next	= ct_cpu_seq_next,
+@@ -478,8 +529,9 @@ static int nf_conntrack_standalone_init_proc(struct net *net)
+ 	kuid_t root_uid;
+ 	kgid_t root_gid;
+ 
+-	pde = proc_create_net("nf_conntrack", 0440, net->proc_net, &ct_seq_ops,
+-			sizeof(struct ct_iter_state));
++	pde = proc_create_net_data_write("nf_conntrack", 0440, net->proc_net,
++					 &ct_seq_ops, &ct_file_write,
++					 sizeof(struct ct_iter_state), NULL);
+ 	if (!pde)
+ 		goto out_nf_conntrack;
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0061-kernel-add-a-new-version-of-my-netfilter-speedup-pat.patch b/nixos/modules/kernel-patches/0061-kernel-add-a-new-version-of-my-netfilter-speedup-pat.patch
new file mode 100644
index 0000000..8a8c759
--- /dev/null
+++ b/nixos/modules/kernel-patches/0061-kernel-add-a-new-version-of-my-netfilter-speedup-pat.patch
@@ -0,0 +1,120 @@
+From fe64c15c96dfe5b5cbf99c89b5377b056790267c Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 27 Sep 2022 16:22:04 +0200
+Subject: [PATCH 61/96] kernel: add a new version of my netfilter speedup
+ patches for linux 2.6.39 and 3.0
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ include/uapi/linux/netfilter_ipv4/ip_tables.h |  1 +
+ net/ipv4/netfilter/ip_tables.c                | 42 ++++++++++++++++++-
+ 2 files changed, 42 insertions(+), 1 deletion(-)
+
+diff --git a/include/uapi/linux/netfilter_ipv4/ip_tables.h b/include/uapi/linux/netfilter_ipv4/ip_tables.h
+index 50c7fee625ae..c31564b13ba3 100644
+--- a/include/uapi/linux/netfilter_ipv4/ip_tables.h
++++ b/include/uapi/linux/netfilter_ipv4/ip_tables.h
+@@ -89,6 +89,7 @@ struct ipt_ip {
+ #define IPT_F_FRAG		0x01	/* Set if rule is a fragment rule */
+ #define IPT_F_GOTO		0x02	/* Set if jump is a goto */
+ #define IPT_F_MASK		0x03	/* All possible flag bits mask. */
++#define IPT_F_NO_DEF_MATCH	0x80	/* Internal: no default match rules present */
+ 
+ /* Values for "inv" field in struct ipt_ip. */
+ #define IPT_INV_VIA_IN		0x01	/* Invert the sense of IN IFACE. */
+diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
+index 2ed7c58b471a..76c01661c4e4 100644
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -50,6 +50,9 @@ ip_packet_match(const struct iphdr *ip,
+ {
+ 	unsigned long ret;
+ 
++	if (ipinfo->flags & IPT_F_NO_DEF_MATCH)
++		return true;
++
+ 	if (NF_INVF(ipinfo, IPT_INV_SRCIP,
+ 		    (ip->saddr & ipinfo->smsk.s_addr) != ipinfo->src.s_addr) ||
+ 	    NF_INVF(ipinfo, IPT_INV_DSTIP,
+@@ -80,6 +83,29 @@ ip_packet_match(const struct iphdr *ip,
+ 	return true;
+ }
+ 
++static void
++ip_checkdefault(struct ipt_ip *ip)
++{
++	static const char iface_mask[IFNAMSIZ] = {};
++
++	if (ip->invflags || ip->flags & IPT_F_FRAG)
++		return;
++
++	if (memcmp(ip->iniface_mask, iface_mask, IFNAMSIZ) != 0)
++		return;
++
++	if (memcmp(ip->outiface_mask, iface_mask, IFNAMSIZ) != 0)
++		return;
++
++	if (ip->smsk.s_addr || ip->dmsk.s_addr)
++		return;
++
++	if (ip->proto)
++		return;
++
++	ip->flags |= IPT_F_NO_DEF_MATCH;
++}
++
+ static bool
+ ip_checkentry(const struct ipt_ip *ip)
+ {
+@@ -525,6 +551,8 @@ find_check_entry(struct ipt_entry *e, struct net *net, const char *name,
+ 	struct xt_mtchk_param mtpar;
+ 	struct xt_entry_match *ematch;
+ 
++	ip_checkdefault(&e->ip);
++
+ 	if (!xt_percpu_counter_alloc(alloc_state, &e->counters))
+ 		return -ENOMEM;
+ 
+@@ -819,6 +847,7 @@ copy_entries_to_user(unsigned int total_size,
+ 	const struct xt_table_info *private = table->private;
+ 	int ret = 0;
+ 	const void *loc_cpu_entry;
++	u8 flags;
+ 
+ 	counters = alloc_counters(table);
+ 	if (IS_ERR(counters))
+@@ -846,6 +875,14 @@ copy_entries_to_user(unsigned int total_size,
+ 			goto free_counters;
+ 		}
+ 
++		flags = e->ip.flags & IPT_F_MASK;
++		if (copy_to_user(userptr + off
++				 + offsetof(struct ipt_entry, ip.flags),
++				 &flags, sizeof(flags)) != 0) {
++			ret = -EFAULT;
++			goto free_counters;
++		}
++
+ 		for (i = sizeof(struct ipt_entry);
+ 		     i < e->target_offset;
+ 		     i += m->u.match_size) {
+@@ -1224,12 +1261,15 @@ compat_copy_entry_to_user(struct ipt_entry *e, void __user **dstptr,
+ 	compat_uint_t origsize;
+ 	const struct xt_entry_match *ematch;
+ 	int ret = 0;
++	u8 flags = e->ip.flags & IPT_F_MASK;
+ 
+ 	origsize = *size;
+ 	ce = *dstptr;
+ 	if (copy_to_user(ce, e, sizeof(struct ipt_entry)) != 0 ||
+ 	    copy_to_user(&ce->counters, &counters[i],
+-	    sizeof(counters[i])) != 0)
++	    sizeof(counters[i])) != 0 ||
++	    copy_to_user(&ce->ip.flags, &flags,
++	    sizeof(flags)) != 0)
+ 		return -EFAULT;
+ 
+ 	*dstptr += sizeof(struct compat_ipt_entry);
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0062-netfilter-reduce-match-memory-access.patch b/nixos/modules/kernel-patches/0062-netfilter-reduce-match-memory-access.patch
new file mode 100644
index 0000000..543dd43
--- /dev/null
+++ b/nixos/modules/kernel-patches/0062-netfilter-reduce-match-memory-access.patch
@@ -0,0 +1,29 @@
+From 6fda2e62b3a2ab9acf5ac27b3d20745f3def32ae Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 27 Sep 2022 16:22:05 +0200
+Subject: [PATCH 62/96] netfilter: reduce match memory access
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ net/ipv4/netfilter/ip_tables.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c
+index 76c01661c4e4..e58342c5354a 100644
+--- a/net/ipv4/netfilter/ip_tables.c
++++ b/net/ipv4/netfilter/ip_tables.c
+@@ -53,9 +53,9 @@ ip_packet_match(const struct iphdr *ip,
+ 	if (ipinfo->flags & IPT_F_NO_DEF_MATCH)
+ 		return true;
+ 
+-	if (NF_INVF(ipinfo, IPT_INV_SRCIP,
++	if (NF_INVF(ipinfo, IPT_INV_SRCIP, ipinfo->smsk.s_addr &&
+ 		    (ip->saddr & ipinfo->smsk.s_addr) != ipinfo->src.s_addr) ||
+-	    NF_INVF(ipinfo, IPT_INV_DSTIP,
++	    NF_INVF(ipinfo, IPT_INV_DSTIP, ipinfo->dmsk.s_addr &&
+ 		    (ip->daddr & ipinfo->dmsk.s_addr) != ipinfo->dst.s_addr))
+ 		return false;
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0063-netfilter-optional-tcp-window-check.patch b/nixos/modules/kernel-patches/0063-netfilter-optional-tcp-window-check.patch
new file mode 100644
index 0000000..f0c2423
--- /dev/null
+++ b/nixos/modules/kernel-patches/0063-netfilter-optional-tcp-window-check.patch
@@ -0,0 +1,96 @@
+From 5433bb138a6a3499b05cb8722c8365cf56047aa1 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 27 Sep 2022 16:22:06 +0200
+Subject: [PATCH 63/96] netfilter: optional tcp window check
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+Signed-off-by: Christian 'Ansuel' Marangi <ansuelsmth@gmail.com>
+---
+ include/net/netns/conntrack.h           |  1 +
+ net/netfilter/nf_conntrack_proto_tcp.c  |  8 +++++++-
+ net/netfilter/nf_conntrack_standalone.c | 10 ++++++++++
+ 3 files changed, 18 insertions(+), 1 deletion(-)
+
+diff --git a/include/net/netns/conntrack.h b/include/net/netns/conntrack.h
+index c396a3862e80..82598d767cc3 100644
+--- a/include/net/netns/conntrack.h
++++ b/include/net/netns/conntrack.h
+@@ -26,6 +26,7 @@ struct nf_tcp_net {
+ 	unsigned int timeouts[TCP_CONNTRACK_TIMEOUT_MAX];
+ 	u8 tcp_loose;
+ 	u8 tcp_be_liberal;
++	u8 tcp_no_window_check;
+ 	u8 tcp_max_retrans;
+ 	u8 tcp_ignore_invalid_rst;
+ #if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
+diff --git a/net/netfilter/nf_conntrack_proto_tcp.c b/net/netfilter/nf_conntrack_proto_tcp.c
+index a634c72b1ffc..8bbc8010170d 100644
+--- a/net/netfilter/nf_conntrack_proto_tcp.c
++++ b/net/netfilter/nf_conntrack_proto_tcp.c
+@@ -490,6 +490,9 @@ static bool tcp_in_window(struct nf_conn *ct,
+ 	s32 receiver_offset;
+ 	bool res, in_recv_win;
+ 
++	if (tn->tcp_no_window_check)
++		return true;
++
+ 	/*
+ 	 * Get the required data from the packet.
+ 	 */
+@@ -1161,7 +1164,7 @@ int nf_conntrack_tcp_packet(struct nf_conn *ct,
+ 		 IP_CT_TCP_FLAG_DATA_UNACKNOWLEDGED &&
+ 		 timeouts[new_state] > timeouts[TCP_CONNTRACK_UNACK])
+ 		timeout = timeouts[TCP_CONNTRACK_UNACK];
+-	else if (ct->proto.tcp.last_win == 0 &&
++	else if (!tn->tcp_no_window_check && ct->proto.tcp.last_win == 0 &&
+ 		 timeouts[new_state] > timeouts[TCP_CONNTRACK_RETRANS])
+ 		timeout = timeouts[TCP_CONNTRACK_RETRANS];
+ 	else
+@@ -1477,6 +1480,9 @@ void nf_conntrack_tcp_init_net(struct net *net)
+ 	 */
+ 	tn->tcp_be_liberal = 0;
+ 
++	/* Skip Windows Check */
++	tn->tcp_no_window_check = 0;
++
+ 	/* If it's non-zero, we turn off RST sequence number check */
+ 	tn->tcp_ignore_invalid_rst = 0;
+ 
+diff --git a/net/netfilter/nf_conntrack_standalone.c b/net/netfilter/nf_conntrack_standalone.c
+index 8d3be4cc340e..73a1b6c1737e 100644
+--- a/net/netfilter/nf_conntrack_standalone.c
++++ b/net/netfilter/nf_conntrack_standalone.c
+@@ -636,6 +636,7 @@ enum nf_ct_sysctl_index {
+ #endif
+ 	NF_SYSCTL_CT_PROTO_TCP_LOOSE,
+ 	NF_SYSCTL_CT_PROTO_TCP_LIBERAL,
++	NF_SYSCTL_CT_PROTO_TCP_NO_WINDOW_CHECK,
+ 	NF_SYSCTL_CT_PROTO_TCP_IGNORE_INVALID_RST,
+ 	NF_SYSCTL_CT_PROTO_TCP_MAX_RETRANS,
+ 	NF_SYSCTL_CT_PROTO_TIMEOUT_UDP,
+@@ -852,6 +853,14 @@ static struct ctl_table nf_ct_sysctl_table[] = {
+ 		.extra1 	= SYSCTL_ZERO,
+ 		.extra2 	= SYSCTL_ONE,
+ 	},
++	[NF_SYSCTL_CT_PROTO_TCP_NO_WINDOW_CHECK] = {
++		.procname       = "nf_conntrack_tcp_no_window_check",
++		.maxlen         = sizeof(u8),
++		.mode           = 0644,
++		.proc_handler	= proc_dou8vec_minmax,
++		.extra1 	= SYSCTL_ZERO,
++		.extra2 	= SYSCTL_ONE,
++	},
+ 	[NF_SYSCTL_CT_PROTO_TCP_IGNORE_INVALID_RST] = {
+ 		.procname	= "nf_conntrack_tcp_ignore_invalid_rst",
+ 		.maxlen		= sizeof(u8),
+@@ -1068,6 +1077,7 @@ static void nf_conntrack_standalone_init_tcp_sysctl(struct net *net,
+ 
+ 	XASSIGN(LOOSE, &tn->tcp_loose);
+ 	XASSIGN(LIBERAL, &tn->tcp_be_liberal);
++	XASSIGN(NO_WINDOW_CHECK, &tn->tcp_no_window_check);
+ 	XASSIGN(MAX_RETRANS, &tn->tcp_max_retrans);
+ 	XASSIGN(IGNORE_INVALID_RST, &tn->tcp_ignore_invalid_rst);
+ #undef XASSIGN
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0064-net_sched-codel-do-not-defer-queue-length-update.patch b/nixos/modules/kernel-patches/0064-net_sched-codel-do-not-defer-queue-length-update.patch
new file mode 100644
index 0000000..8c57508
--- /dev/null
+++ b/nixos/modules/kernel-patches/0064-net_sched-codel-do-not-defer-queue-length-update.patch
@@ -0,0 +1,97 @@
+From 9eee7822b071e5a658a176300336feeedd2b6205 Mon Sep 17 00:00:00 2001
+From: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
+Date: Mon, 21 Aug 2017 11:14:14 +0300
+Subject: [PATCH 64/96] net_sched/codel: do not defer queue length update
+
+When codel wants to drop last packet in ->dequeue() it cannot call
+qdisc_tree_reduce_backlog() right away - it will notify parent qdisc
+about zero qlen and HTB/HFSC will deactivate class. The same class will
+be deactivated second time by caller of ->dequeue(). Currently codel and
+fq_codel defer update. This triggers warning in HFSC when it's qlen != 0
+but there is no active classes.
+
+This patch update parent queue length immediately: just temporary increase
+qlen around qdisc_tree_reduce_backlog() to prevent first class deactivation
+if we have skb to return.
+
+This might open another problem in HFSC - now operation peek could fail and
+deactivate parent class.
+
+Signed-off-by: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
+Link: https://bugzilla.kernel.org/show_bug.cgi?id=109581
+---
+ net/sched/sch_codel.c    | 14 ++++++++++----
+ net/sched/sch_fq_codel.c | 24 +++++++++++++++---------
+ 2 files changed, 25 insertions(+), 13 deletions(-)
+
+diff --git a/net/sched/sch_codel.c b/net/sched/sch_codel.c
+index 30169b3adbbb..a2b81a5b2dfb 100644
+--- a/net/sched/sch_codel.c
++++ b/net/sched/sch_codel.c
+@@ -95,11 +95,17 @@ static struct sk_buff *codel_qdisc_dequeue(struct Qdisc *sch)
+ 			    &q->stats, qdisc_pkt_len, codel_get_enqueue_time,
+ 			    drop_func, dequeue_func);
+ 
+-	/* We cant call qdisc_tree_reduce_backlog() if our qlen is 0,
+-	 * or HTB crashes. Defer it for next round.
++	/* If our qlen is 0 qdisc_tree_reduce_backlog() will deactivate
++	 * parent class, dequeue in parent qdisc will do the same if we
++	 * return skb. Temporary increment qlen if we have skb.
+ 	 */
+-	if (q->stats.drop_count && sch->q.qlen) {
+-		qdisc_tree_reduce_backlog(sch, q->stats.drop_count, q->stats.drop_len);
++	if (q->stats.drop_count) {
++		if (skb)
++			sch->q.qlen++;
++		qdisc_tree_reduce_backlog(sch, q->stats.drop_count,
++					  q->stats.drop_len);
++		if (skb)
++			sch->q.qlen--;
+ 		q->stats.drop_count = 0;
+ 		q->stats.drop_len = 0;
+ 	}
+diff --git a/net/sched/sch_fq_codel.c b/net/sched/sch_fq_codel.c
+index 839e1235db05..fd2620da04db 100644
+--- a/net/sched/sch_fq_codel.c
++++ b/net/sched/sch_fq_codel.c
+@@ -304,6 +304,21 @@ static struct sk_buff *fq_codel_dequeue(struct Qdisc *sch)
+ 			    &flow->cvars, &q->cstats, qdisc_pkt_len,
+ 			    codel_get_enqueue_time, drop_func, dequeue_func);
+ 
++	/* If our qlen is 0 qdisc_tree_reduce_backlog() will deactivate
++	 * parent class, dequeue in parent qdisc will do the same if we
++	 * return skb. Temporary increment qlen if we have skb.
++	 */
++	if (q->cstats.drop_count) {
++		if (skb)
++			sch->q.qlen++;
++		qdisc_tree_reduce_backlog(sch, q->cstats.drop_count,
++					  q->cstats.drop_len);
++		if (skb)
++			sch->q.qlen--;
++		q->cstats.drop_count = 0;
++		q->cstats.drop_len = 0;
++	}
++
+ 	if (!skb) {
+ 		/* force a pass through old_flows to prevent starvation */
+ 		if ((head == &q->new_flows) && !list_empty(&q->old_flows))
+@@ -314,15 +329,6 @@ static struct sk_buff *fq_codel_dequeue(struct Qdisc *sch)
+ 	}
+ 	qdisc_bstats_update(sch, skb);
+ 	flow->deficit -= qdisc_pkt_len(skb);
+-	/* We cant call qdisc_tree_reduce_backlog() if our qlen is 0,
+-	 * or HTB crashes. Defer it for next round.
+-	 */
+-	if (q->cstats.drop_count && sch->q.qlen) {
+-		qdisc_tree_reduce_backlog(sch, q->cstats.drop_count,
+-					  q->cstats.drop_len);
+-		q->cstats.drop_count = 0;
+-		q->cstats.drop_len = 0;
+-	}
+ 	return skb;
+ }
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0065-net-add-an-optimization-for-dealing-with-raw-sockets.patch b/nixos/modules/kernel-patches/0065-net-add-an-optimization-for-dealing-with-raw-sockets.patch
new file mode 100644
index 0000000..23cc5a4
--- /dev/null
+++ b/nixos/modules/kernel-patches/0065-net-add-an-optimization-for-dealing-with-raw-sockets.patch
@@ -0,0 +1,149 @@
+From 439a16fc3f6ea86b301b4885c1a27b4579a3e7d1 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 27 Sep 2022 16:22:06 +0200
+Subject: [PATCH 65/96] net: add an optimization for dealing with raw sockets
+
+lede-commit: 4898039703d7315f0f3431c860123338ec3be0f6
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ include/uapi/linux/if_packet.h |  3 +++
+ net/packet/af_packet.c         | 34 +++++++++++++++++++++++++++-------
+ net/packet/internal.h          |  1 +
+ 3 files changed, 31 insertions(+), 7 deletions(-)
+
+diff --git a/include/uapi/linux/if_packet.h b/include/uapi/linux/if_packet.h
+index c07caf7b40db..baa5581b1fa9 100644
+--- a/include/uapi/linux/if_packet.h
++++ b/include/uapi/linux/if_packet.h
+@@ -33,6 +33,8 @@ struct sockaddr_ll {
+ #define PACKET_KERNEL		7		/* To kernel space	*/
+ /* Unused, PACKET_FASTROUTE and PACKET_LOOPBACK are invisible to user space */
+ #define PACKET_FASTROUTE	6		/* Fastrouted frame	*/
++#define PACKET_MASK_ANY		0xffffffff	/* mask for packet type bits */
++
+ 
+ /* Packet socket options */
+ 
+@@ -59,6 +61,7 @@ struct sockaddr_ll {
+ #define PACKET_ROLLOVER_STATS		21
+ #define PACKET_FANOUT_DATA		22
+ #define PACKET_IGNORE_OUTGOING		23
++#define PACKET_RECV_TYPE		24
+ 
+ #define PACKET_FANOUT_HASH		0
+ #define PACKET_FANOUT_LB		1
+diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
+index 492bd35cccc0..e8746e713a25 100644
+--- a/net/packet/af_packet.c
++++ b/net/packet/af_packet.c
+@@ -1861,6 +1861,7 @@ static int packet_rcv_spkt(struct sk_buff *skb, struct net_device *dev,
+ {
+ 	struct sock *sk;
+ 	struct sockaddr_pkt *spkt;
++	struct packet_sock *po;
+ 
+ 	/*
+ 	 *	When we registered the protocol we saved the socket in the data
+@@ -1868,6 +1869,7 @@ static int packet_rcv_spkt(struct sk_buff *skb, struct net_device *dev,
+ 	 */
+ 
+ 	sk = pt->af_packet_priv;
++	po = pkt_sk(sk);
+ 
+ 	/*
+ 	 *	Yank back the headers [hope the device set this
+@@ -1880,7 +1882,7 @@ static int packet_rcv_spkt(struct sk_buff *skb, struct net_device *dev,
+ 	 *	so that this procedure is noop.
+ 	 */
+ 
+-	if (skb->pkt_type == PACKET_LOOPBACK)
++	if (!(po->pkt_type & (1 << skb->pkt_type)))
+ 		goto out;
+ 
+ 	if (!net_eq(dev_net(dev), sock_net(sk)))
+@@ -2128,12 +2130,12 @@ static int packet_rcv(struct sk_buff *skb, struct net_device *dev,
+ 	unsigned int snaplen, res;
+ 	bool is_drop_n_account = false;
+ 
+-	if (skb->pkt_type == PACKET_LOOPBACK)
+-		goto drop;
+-
+ 	sk = pt->af_packet_priv;
+ 	po = pkt_sk(sk);
+ 
++	if (!(po->pkt_type & (1 << skb->pkt_type)))
++		goto drop;
++
+ 	if (!net_eq(dev_net(dev), sock_net(sk)))
+ 		goto drop;
+ 
+@@ -2260,12 +2262,12 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev,
+ 	BUILD_BUG_ON(TPACKET_ALIGN(sizeof(*h.h2)) != 32);
+ 	BUILD_BUG_ON(TPACKET_ALIGN(sizeof(*h.h3)) != 48);
+ 
+-	if (skb->pkt_type == PACKET_LOOPBACK)
+-		goto drop;
+-
+ 	sk = pt->af_packet_priv;
+ 	po = pkt_sk(sk);
+ 
++	if (!(po->pkt_type & (1 << skb->pkt_type)))
++		goto drop;
++
+ 	if (!net_eq(dev_net(dev), sock_net(sk)))
+ 		goto drop;
+ 
+@@ -3372,6 +3374,7 @@ static int packet_create(struct net *net, struct socket *sock, int protocol,
+ 	mutex_init(&po->pg_vec_lock);
+ 	po->rollover = NULL;
+ 	po->prot_hook.func = packet_rcv;
++	po->pkt_type = PACKET_MASK_ANY & ~(1 << PACKET_LOOPBACK);
+ 
+ 	if (sock->type == SOCK_PACKET)
+ 		po->prot_hook.func = packet_rcv_spkt;
+@@ -4011,6 +4014,16 @@ packet_setsockopt(struct socket *sock, int level, int optname, sockptr_t optval,
+ 		po->xmit = val ? packet_direct_xmit : dev_queue_xmit;
+ 		return 0;
+ 	}
++	case PACKET_RECV_TYPE:
++	{
++		unsigned int val;
++		if (optlen != sizeof(val))
++			return -EINVAL;
++		if (copy_from_sockptr(&val, optval, sizeof(val)))
++			return -EFAULT;
++		po->pkt_type = val & ~BIT(PACKET_LOOPBACK);
++		return 0;
++	}
+ 	default:
+ 		return -ENOPROTOOPT;
+ 	}
+@@ -4067,6 +4080,13 @@ static int packet_getsockopt(struct socket *sock, int level, int optname,
+ 	case PACKET_VNET_HDR:
+ 		val = po->has_vnet_hdr;
+ 		break;
++	case PACKET_RECV_TYPE:
++		if (len > sizeof(unsigned int))
++			len = sizeof(unsigned int);
++		val = po->pkt_type;
++
++		data = &val;
++		break;
+ 	case PACKET_VERSION:
+ 		val = po->tp_version;
+ 		break;
+diff --git a/net/packet/internal.h b/net/packet/internal.h
+index 48af35b1aed2..1361d2974453 100644
+--- a/net/packet/internal.h
++++ b/net/packet/internal.h
+@@ -137,6 +137,7 @@ struct packet_sock {
+ 	int			(*xmit)(struct sk_buff *skb);
+ 	struct packet_type	prot_hook ____cacheline_aligned_in_smp;
+ 	atomic_t		tp_drops ____cacheline_aligned_in_smp;
++	unsigned int		pkt_type;
+ };
+ 
+ static inline struct packet_sock *pkt_sk(struct sock *sk)
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0066-kernel-add-a-few-patches-for-avoiding-unnecessary-sk.patch b/nixos/modules/kernel-patches/0066-kernel-add-a-few-patches-for-avoiding-unnecessary-sk.patch
new file mode 100644
index 0000000..4bb9f5f
--- /dev/null
+++ b/nixos/modules/kernel-patches/0066-kernel-add-a-few-patches-for-avoiding-unnecessary-sk.patch
@@ -0,0 +1,28 @@
+From 5a62944a6801f0da0a67b986002981442766614c Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 27 Sep 2022 16:22:06 +0200
+Subject: [PATCH 66/96] kernel: add a few patches for avoiding unnecessary skb
+ reallocations - significantly improves ethernet<->wireless performance
+
+lede-commit: 6f89cffc9add6939d44a6b54cf9a5e77849aa7fd
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ include/linux/skbuff.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
+index 63d0a21b6316..ec3d3246c7a7 100644
+--- a/include/linux/skbuff.h
++++ b/include/linux/skbuff.h
+@@ -3123,7 +3123,7 @@ static inline int pskb_network_may_pull(struct sk_buff *skb, unsigned int len)
+  * NET_IP_ALIGN(2) + ethernet_header(14) + IP_header(20/40) + ports(8)
+  */
+ #ifndef NET_SKB_PAD
+-#define NET_SKB_PAD	max(32, L1_CACHE_BYTES)
++#define NET_SKB_PAD	max(64, L1_CACHE_BYTES)
+ #endif
+ 
+ int ___pskb_trim(struct sk_buff *skb, unsigned int len);
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0067-Add-support-for-MAP-E-FMRs-mesh-mode.patch b/nixos/modules/kernel-patches/0067-Add-support-for-MAP-E-FMRs-mesh-mode.patch
new file mode 100644
index 0000000..508a84c
--- /dev/null
+++ b/nixos/modules/kernel-patches/0067-Add-support-for-MAP-E-FMRs-mesh-mode.patch
@@ -0,0 +1,522 @@
+From 2419f533e814e322a5b61ca4f2a63f84cc60a5a9 Mon Sep 17 00:00:00 2001
+From: Steven Barth <steven@midlink.org>
+Date: Tue, 27 Sep 2022 16:22:06 +0200
+Subject: [PATCH 67/96] Add support for MAP-E FMRs (mesh mode)
+
+MAP-E FMRs (draft-ietf-softwire-map-10) are rules for IPv4-communication
+between MAP CEs (mesh mode) without the need to forward such data to a
+border relay. This is similar to how 6rd works but for IPv4 over IPv6.
+
+Signed-off-by: Steven Barth <cyrus@openwrt.org>
+---
+ include/net/ip6_tunnel.h       |  13 ++
+ include/uapi/linux/if_tunnel.h |  13 ++
+ net/ipv6/ip6_tunnel.c          | 281 ++++++++++++++++++++++++++++++++-
+ 3 files changed, 299 insertions(+), 8 deletions(-)
+
+diff --git a/include/net/ip6_tunnel.h b/include/net/ip6_tunnel.h
+index 74b369bddf49..0d1df6f12c33 100644
+--- a/include/net/ip6_tunnel.h
++++ b/include/net/ip6_tunnel.h
+@@ -18,6 +18,18 @@
+ /* determine capability on a per-packet basis */
+ #define IP6_TNL_F_CAP_PER_PACKET 0x40000
+ 
++/* IPv6 tunnel FMR */
++struct __ip6_tnl_fmr {
++	struct __ip6_tnl_fmr *next; /* next fmr in list */
++	struct in6_addr ip6_prefix;
++	struct in_addr ip4_prefix;
++
++	__u8 ip6_prefix_len;
++	__u8 ip4_prefix_len;
++	__u8 ea_len;
++	__u8 offset;
++};
++
+ struct __ip6_tnl_parm {
+ 	char name[IFNAMSIZ];	/* name of tunnel device */
+ 	int link;		/* ifindex of underlying L2 interface */
+@@ -29,6 +41,7 @@ struct __ip6_tnl_parm {
+ 	__u32 flags;		/* tunnel flags */
+ 	struct in6_addr laddr;	/* local tunnel end-point address */
+ 	struct in6_addr raddr;	/* remote tunnel end-point address */
++	struct __ip6_tnl_fmr *fmrs;	/* FMRs */
+ 
+ 	__be16			i_flags;
+ 	__be16			o_flags;
+diff --git a/include/uapi/linux/if_tunnel.h b/include/uapi/linux/if_tunnel.h
+index 102119628ff5..420dec6ebb7e 100644
+--- a/include/uapi/linux/if_tunnel.h
++++ b/include/uapi/linux/if_tunnel.h
+@@ -77,10 +77,23 @@ enum {
+ 	IFLA_IPTUN_ENCAP_DPORT,
+ 	IFLA_IPTUN_COLLECT_METADATA,
+ 	IFLA_IPTUN_FWMARK,
++	IFLA_IPTUN_FMRS,
+ 	__IFLA_IPTUN_MAX,
+ };
+ #define IFLA_IPTUN_MAX	(__IFLA_IPTUN_MAX - 1)
+ 
++enum {
++	IFLA_IPTUN_FMR_UNSPEC,
++	IFLA_IPTUN_FMR_IP6_PREFIX,
++	IFLA_IPTUN_FMR_IP4_PREFIX,
++	IFLA_IPTUN_FMR_IP6_PREFIX_LEN,
++	IFLA_IPTUN_FMR_IP4_PREFIX_LEN,
++	IFLA_IPTUN_FMR_EA_LEN,
++	IFLA_IPTUN_FMR_OFFSET,
++	__IFLA_IPTUN_FMR_MAX,
++};
++#define IFLA_IPTUN_FMR_MAX (__IFLA_IPTUN_FMR_MAX - 1)
++
+ enum tunnel_encap_types {
+ 	TUNNEL_ENCAP_NONE,
+ 	TUNNEL_ENCAP_FOU,
+diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
+index 19325b7600bb..a53746fdafe5 100644
+--- a/net/ipv6/ip6_tunnel.c
++++ b/net/ipv6/ip6_tunnel.c
+@@ -11,6 +11,9 @@
+  *      linux/net/ipv6/sit.c and linux/net/ipv4/ipip.c
+  *
+  *      RFC 2473
++ *
++ *      Changes:
++ *      Steven Barth <cyrus@openwrt.org>:           MAP-E FMR support
+  */
+ 
+ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+@@ -67,9 +70,9 @@ static bool log_ecn_error = true;
+ module_param(log_ecn_error, bool, 0644);
+ MODULE_PARM_DESC(log_ecn_error, "Log packets received with corrupted ECN");
+ 
+-static u32 HASH(const struct in6_addr *addr1, const struct in6_addr *addr2)
++static u32 HASH(const struct in6_addr *addr)
+ {
+-	u32 hash = ipv6_addr_hash(addr1) ^ ipv6_addr_hash(addr2);
++	u32 hash = ipv6_addr_hash(addr);
+ 
+ 	return hash_32(hash, IP6_TUNNEL_HASH_SIZE_SHIFT);
+ }
+@@ -114,17 +117,33 @@ static struct ip6_tnl *
+ ip6_tnl_lookup(struct net *net, int link,
+ 	       const struct in6_addr *remote, const struct in6_addr *local)
+ {
+-	unsigned int hash = HASH(remote, local);
++	unsigned int hash = HASH(local);
+ 	struct ip6_tnl *t, *cand = NULL;
+ 	struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id);
+ 	struct in6_addr any;
+ 
+ 	for_each_ip6_tunnel_rcu(ip6n->tnls_r_l[hash]) {
+ 		if (!ipv6_addr_equal(local, &t->parms.laddr) ||
+-		    !ipv6_addr_equal(remote, &t->parms.raddr) ||
+ 		    !(t->dev->flags & IFF_UP))
+ 			continue;
+ 
++		if (!ipv6_addr_equal(remote, &t->parms.raddr)) {
++			struct __ip6_tnl_fmr *fmr;
++			bool found = false;
++
++			for (fmr = t->parms.fmrs; fmr; fmr = fmr->next) {
++				if (!ipv6_prefix_equal(remote, &fmr->ip6_prefix,
++						       fmr->ip6_prefix_len))
++					continue;
++
++				found = true;
++				break;
++			}
++
++			if (!found)
++				continue;
++		}
++
+ 		if (link == t->parms.link)
+ 			return t;
+ 		else
+@@ -132,7 +151,7 @@ ip6_tnl_lookup(struct net *net, int link,
+ 	}
+ 
+ 	memset(&any, 0, sizeof(any));
+-	hash = HASH(&any, local);
++	hash = HASH(local);
+ 	for_each_ip6_tunnel_rcu(ip6n->tnls_r_l[hash]) {
+ 		if (!ipv6_addr_equal(local, &t->parms.laddr) ||
+ 		    !ipv6_addr_any(&t->parms.raddr) ||
+@@ -145,7 +164,7 @@ ip6_tnl_lookup(struct net *net, int link,
+ 			cand = t;
+ 	}
+ 
+-	hash = HASH(remote, &any);
++	hash = HASH(&any);
+ 	for_each_ip6_tunnel_rcu(ip6n->tnls_r_l[hash]) {
+ 		if (!ipv6_addr_equal(remote, &t->parms.raddr) ||
+ 		    !ipv6_addr_any(&t->parms.laddr) ||
+@@ -194,7 +213,7 @@ ip6_tnl_bucket(struct ip6_tnl_net *ip6n, const struct __ip6_tnl_parm *p)
+ 
+ 	if (!ipv6_addr_any(remote) || !ipv6_addr_any(local)) {
+ 		prio = 1;
+-		h = HASH(remote, local);
++		h = HASH(local);
+ 	}
+ 	return &ip6n->tnls[prio][h];
+ }
+@@ -376,6 +395,12 @@ ip6_tnl_dev_uninit(struct net_device *dev)
+ 	struct net *net = t->net;
+ 	struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id);
+ 
++	while (t->parms.fmrs) {
++		struct __ip6_tnl_fmr *next = t->parms.fmrs->next;
++		kfree(t->parms.fmrs);
++		t->parms.fmrs = next;
++	}
++
+ 	if (dev == ip6n->fb_tnl_dev)
+ 		RCU_INIT_POINTER(ip6n->tnls_wc[0], NULL);
+ 	else
+@@ -788,6 +813,107 @@ int ip6_tnl_rcv_ctl(struct ip6_tnl *t,
+ }
+ EXPORT_SYMBOL_GPL(ip6_tnl_rcv_ctl);
+ 
++/**
++ * ip4ip6_fmr_calc - calculate target / source IPv6-address based on FMR
++ *   @dest: destination IPv6 address buffer
++ *   @skb: received socket buffer
++ *   @fmr: MAP FMR
++ *   @xmit: Calculate for xmit or rcv
++ **/
++static void ip4ip6_fmr_calc(struct in6_addr *dest,
++		const struct iphdr *iph, const uint8_t *end,
++		const struct __ip6_tnl_fmr *fmr, bool xmit)
++{
++	int psidlen = fmr->ea_len - (32 - fmr->ip4_prefix_len);
++	u8 *portp = NULL;
++	bool use_dest_addr;
++	const struct iphdr *dsth = iph;
++
++	if ((u8*)dsth >= end)
++		return;
++
++	/* find significant IP header */
++	if (iph->protocol == IPPROTO_ICMP) {
++		struct icmphdr *ih = (struct icmphdr*)(((u8*)dsth) + dsth->ihl * 4);
++		if (ih && ((u8*)&ih[1]) <= end && (
++			ih->type == ICMP_DEST_UNREACH ||
++			ih->type == ICMP_SOURCE_QUENCH ||
++			ih->type == ICMP_TIME_EXCEEDED ||
++			ih->type == ICMP_PARAMETERPROB ||
++			ih->type == ICMP_REDIRECT))
++				dsth = (const struct iphdr*)&ih[1];
++	}
++
++	/* in xmit-path use dest port by default and source port only if
++		this is an ICMP reply to something else; vice versa in rcv-path */
++	use_dest_addr = (xmit && dsth == iph) || (!xmit && dsth != iph);
++
++	/* get dst port */
++	if (((u8*)&dsth[1]) <= end && (
++		dsth->protocol == IPPROTO_UDP ||
++		dsth->protocol == IPPROTO_TCP ||
++		dsth->protocol == IPPROTO_SCTP ||
++		dsth->protocol == IPPROTO_DCCP)) {
++			/* for UDP, TCP, SCTP and DCCP source and dest port
++			follow IPv4 header directly */
++			portp = ((u8*)dsth) + dsth->ihl * 4;
++
++			if (use_dest_addr)
++				portp += sizeof(u16);
++	} else if (iph->protocol == IPPROTO_ICMP) {
++		struct icmphdr *ih = (struct icmphdr*)(((u8*)dsth) + dsth->ihl * 4);
++
++		/* use icmp identifier as port */
++		if (((u8*)&ih) <= end && (
++		    (use_dest_addr && (
++		    ih->type == ICMP_ECHOREPLY ||
++			ih->type == ICMP_TIMESTAMPREPLY ||
++			ih->type == ICMP_INFO_REPLY ||
++			ih->type == ICMP_ADDRESSREPLY)) ||
++			(!use_dest_addr && (
++			ih->type == ICMP_ECHO ||
++			ih->type == ICMP_TIMESTAMP ||
++			ih->type == ICMP_INFO_REQUEST ||
++			ih->type == ICMP_ADDRESS)
++			)))
++				portp = (u8*)&ih->un.echo.id;
++	}
++
++	if ((portp && &portp[2] <= end) || psidlen == 0) {
++		int frombyte = fmr->ip6_prefix_len / 8;
++		int fromrem = fmr->ip6_prefix_len % 8;
++		int bytes = sizeof(struct in6_addr) - frombyte;
++		const u32 *addr = (use_dest_addr) ? &iph->daddr : &iph->saddr;
++		u64 eabits = ((u64)ntohl(*addr)) << (32 + fmr->ip4_prefix_len);
++		u64 t = 0;
++
++		/* extract PSID from port and add it to eabits */
++		u16 psidbits = 0;
++		if (psidlen > 0) {
++			psidbits = ((u16)portp[0]) << 8 | ((u16)portp[1]);
++			psidbits >>= 16 - psidlen - fmr->offset;
++			psidbits = (u16)(psidbits << (16 - psidlen));
++			eabits |= ((u64)psidbits) << (48 - (fmr->ea_len - psidlen));
++		}
++
++		/* rewrite destination address */
++		*dest = fmr->ip6_prefix;
++		memcpy(&dest->s6_addr[10], addr, sizeof(*addr));
++		dest->s6_addr16[7] = htons(psidbits >> (16 - psidlen));
++
++		if (bytes > sizeof(u64))
++			bytes = sizeof(u64);
++
++		/* insert eabits */
++		memcpy(&t, &dest->s6_addr[frombyte], bytes);
++		t = be64_to_cpu(t) & ~(((((u64)1) << fmr->ea_len) - 1)
++			<< (64 - fmr->ea_len - fromrem));
++		t = cpu_to_be64(t | (eabits >> fromrem));
++		memcpy(&dest->s6_addr[frombyte], &t, bytes);
++	}
++}
++
++
+ static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,
+ 			 const struct tnl_ptk_info *tpi,
+ 			 struct metadata_dst *tun_dst,
+@@ -841,6 +967,27 @@ static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,
+ 	skb_reset_network_header(skb);
+ 	memset(skb->cb, 0, sizeof(struct inet6_skb_parm));
+ 
++	if (tpi->proto == htons(ETH_P_IP) && tunnel->parms.fmrs &&
++		!ipv6_addr_equal(&ipv6h->saddr, &tunnel->parms.raddr)) {
++			/* Packet didn't come from BR, so lookup FMR */
++			struct __ip6_tnl_fmr *fmr;
++			struct in6_addr expected = tunnel->parms.raddr;
++			for (fmr = tunnel->parms.fmrs; fmr; fmr = fmr->next)
++				if (ipv6_prefix_equal(&ipv6h->saddr,
++					&fmr->ip6_prefix, fmr->ip6_prefix_len))
++						break;
++
++			/* Check that IPv6 matches IPv4 source to prevent spoofing */
++			if (fmr)
++				ip4ip6_fmr_calc(&expected, ip_hdr(skb),
++						skb_tail_pointer(skb), fmr, false);
++
++			if (!ipv6_addr_equal(&ipv6h->saddr, &expected)) {
++				rcu_read_unlock();
++				goto drop;
++			}
++	}
++
+ 	__skb_tunnel_rx(skb, tunnel->dev, tunnel->net);
+ 
+ 	err = dscp_ecn_decapsulate(tunnel, ipv6h, skb);
+@@ -992,6 +1139,7 @@ static void init_tel_txopt(struct ipv6_tel_txoption *opt, __u8 encap_limit)
+ 	opt->ops.opt_nflen = 8;
+ }
+ 
++
+ /**
+  * ip6_tnl_addr_conflict - compare packet addresses to tunnel's own
+  *   @t: the outgoing tunnel device
+@@ -1280,6 +1428,7 @@ ipxip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev,
+ 		u8 protocol)
+ {
+ 	struct ip6_tnl *t = netdev_priv(dev);
++	struct __ip6_tnl_fmr *fmr;
+ 	struct ipv6hdr *ipv6h;
+ 	const struct iphdr  *iph;
+ 	int encap_limit = -1;
+@@ -1379,6 +1528,18 @@ ipxip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev,
+ 	fl6.flowi6_uid = sock_net_uid(dev_net(dev), NULL);
+ 	dsfield = INET_ECN_encapsulate(dsfield, orig_dsfield);
+ 
++	/* try to find matching FMR */
++	for (fmr = t->parms.fmrs; fmr; fmr = fmr->next) {
++		unsigned mshift = 32 - fmr->ip4_prefix_len;
++		if (ntohl(fmr->ip4_prefix.s_addr) >> mshift ==
++				ntohl(ip_hdr(skb)->daddr) >> mshift)
++			break;
++	}
++
++	/* change dstaddr according to FMR */
++	if (fmr)
++		ip4ip6_fmr_calc(&fl6.daddr, ip_hdr(skb), skb_tail_pointer(skb), fmr, true);
++
+ 	if (iptunnel_handle_offloads(skb, SKB_GSO_IPXIP6))
+ 		return -1;
+ 
+@@ -1531,6 +1692,14 @@ ip6_tnl_change(struct ip6_tnl *t, const struct __ip6_tnl_parm *p)
+ 	t->parms.link = p->link;
+ 	t->parms.proto = p->proto;
+ 	t->parms.fwmark = p->fwmark;
++
++	while (t->parms.fmrs) {
++		struct __ip6_tnl_fmr *next = t->parms.fmrs->next;
++		kfree(t->parms.fmrs);
++		t->parms.fmrs = next;
++	}
++	t->parms.fmrs = p->fmrs;
++
+ 	dst_cache_reset(&t->dst_cache);
+ 	ip6_tnl_link_config(t);
+ 	return 0;
+@@ -1569,6 +1738,7 @@ ip6_tnl_parm_from_user(struct __ip6_tnl_parm *p, const struct ip6_tnl_parm *u)
+ 	p->flowinfo = u->flowinfo;
+ 	p->link = u->link;
+ 	p->proto = u->proto;
++	p->fmrs = NULL;
+ 	memcpy(p->name, u->name, sizeof(u->name));
+ }
+ 
+@@ -1955,6 +2125,15 @@ static int ip6_tnl_validate(struct nlattr *tb[], struct nlattr *data[],
+ 	return 0;
+ }
+ 
++static const struct nla_policy ip6_tnl_fmr_policy[IFLA_IPTUN_FMR_MAX + 1] = {
++	[IFLA_IPTUN_FMR_IP6_PREFIX] = { .len = sizeof(struct in6_addr) },
++	[IFLA_IPTUN_FMR_IP4_PREFIX] = { .len = sizeof(struct in_addr) },
++	[IFLA_IPTUN_FMR_IP6_PREFIX_LEN] = { .type = NLA_U8 },
++	[IFLA_IPTUN_FMR_IP4_PREFIX_LEN] = { .type = NLA_U8 },
++	[IFLA_IPTUN_FMR_EA_LEN] = { .type = NLA_U8 },
++	[IFLA_IPTUN_FMR_OFFSET] = { .type = NLA_U8 }
++};
++
+ static void ip6_tnl_netlink_parms(struct nlattr *data[],
+ 				  struct __ip6_tnl_parm *parms)
+ {
+@@ -1992,6 +2171,46 @@ static void ip6_tnl_netlink_parms(struct nlattr *data[],
+ 
+ 	if (data[IFLA_IPTUN_FWMARK])
+ 		parms->fwmark = nla_get_u32(data[IFLA_IPTUN_FWMARK]);
++
++	if (data[IFLA_IPTUN_FMRS]) {
++		unsigned rem;
++		struct nlattr *fmr;
++		nla_for_each_nested(fmr, data[IFLA_IPTUN_FMRS], rem) {
++			struct nlattr *fmrd[IFLA_IPTUN_FMR_MAX + 1], *c;
++			struct __ip6_tnl_fmr *nfmr;
++
++			nla_parse_nested(fmrd, IFLA_IPTUN_FMR_MAX,
++				fmr, ip6_tnl_fmr_policy, NULL);
++
++			if (!(nfmr = kzalloc(sizeof(*nfmr), GFP_KERNEL)))
++				continue;
++
++			nfmr->offset = 6;
++
++			if ((c = fmrd[IFLA_IPTUN_FMR_IP6_PREFIX]))
++				nla_memcpy(&nfmr->ip6_prefix, fmrd[IFLA_IPTUN_FMR_IP6_PREFIX],
++					sizeof(nfmr->ip6_prefix));
++
++			if ((c = fmrd[IFLA_IPTUN_FMR_IP4_PREFIX]))
++				nla_memcpy(&nfmr->ip4_prefix, fmrd[IFLA_IPTUN_FMR_IP4_PREFIX],
++					sizeof(nfmr->ip4_prefix));
++
++			if ((c = fmrd[IFLA_IPTUN_FMR_IP6_PREFIX_LEN]))
++				nfmr->ip6_prefix_len = nla_get_u8(c);
++
++			if ((c = fmrd[IFLA_IPTUN_FMR_IP4_PREFIX_LEN]))
++				nfmr->ip4_prefix_len = nla_get_u8(c);
++
++			if ((c = fmrd[IFLA_IPTUN_FMR_EA_LEN]))
++				nfmr->ea_len = nla_get_u8(c);
++
++			if ((c = fmrd[IFLA_IPTUN_FMR_OFFSET]))
++				nfmr->offset = nla_get_u8(c);
++
++			nfmr->next = parms->fmrs;
++			parms->fmrs = nfmr;
++		}
++	}
+ }
+ 
+ static bool ip6_tnl_netlink_encap_parms(struct nlattr *data[],
+@@ -2107,6 +2326,12 @@ static void ip6_tnl_dellink(struct net_device *dev, struct list_head *head)
+ 
+ static size_t ip6_tnl_get_size(const struct net_device *dev)
+ {
++	const struct ip6_tnl *t = netdev_priv(dev);
++	struct __ip6_tnl_fmr *c;
++	int fmrs = 0;
++	for (c = t->parms.fmrs; c; c = c->next)
++		++fmrs;
++
+ 	return
+ 		/* IFLA_IPTUN_LINK */
+ 		nla_total_size(4) +
+@@ -2136,6 +2361,24 @@ static size_t ip6_tnl_get_size(const struct net_device *dev)
+ 		nla_total_size(0) +
+ 		/* IFLA_IPTUN_FWMARK */
+ 		nla_total_size(4) +
++		/* IFLA_IPTUN_FMRS */
++		nla_total_size(0) +
++		(
++			/* nest */
++			nla_total_size(0) +
++			/* IFLA_IPTUN_FMR_IP6_PREFIX */
++			nla_total_size(sizeof(struct in6_addr)) +
++			/* IFLA_IPTUN_FMR_IP4_PREFIX */
++			nla_total_size(sizeof(struct in_addr)) +
++			/* IFLA_IPTUN_FMR_EA_LEN */
++			nla_total_size(1) +
++			/* IFLA_IPTUN_FMR_IP6_PREFIX_LEN */
++			nla_total_size(1) +
++			/* IFLA_IPTUN_FMR_IP4_PREFIX_LEN */
++			nla_total_size(1) +
++			/* IFLA_IPTUN_FMR_OFFSET */
++			nla_total_size(1)
++		) * fmrs +
+ 		0;
+ }
+ 
+@@ -2143,6 +2386,9 @@ static int ip6_tnl_fill_info(struct sk_buff *skb, const struct net_device *dev)
+ {
+ 	struct ip6_tnl *tunnel = netdev_priv(dev);
+ 	struct __ip6_tnl_parm *parm = &tunnel->parms;
++	struct __ip6_tnl_fmr *c;
++	int fmrcnt = 0;
++	struct nlattr *fmrs;
+ 
+ 	if (nla_put_u32(skb, IFLA_IPTUN_LINK, parm->link) ||
+ 	    nla_put_in6_addr(skb, IFLA_IPTUN_LOCAL, &parm->laddr) ||
+@@ -2152,9 +2398,27 @@ static int ip6_tnl_fill_info(struct sk_buff *skb, const struct net_device *dev)
+ 	    nla_put_be32(skb, IFLA_IPTUN_FLOWINFO, parm->flowinfo) ||
+ 	    nla_put_u32(skb, IFLA_IPTUN_FLAGS, parm->flags) ||
+ 	    nla_put_u8(skb, IFLA_IPTUN_PROTO, parm->proto) ||
+-	    nla_put_u32(skb, IFLA_IPTUN_FWMARK, parm->fwmark))
++	    nla_put_u32(skb, IFLA_IPTUN_FWMARK, parm->fwmark) ||
++	    !(fmrs = nla_nest_start(skb, IFLA_IPTUN_FMRS)))
+ 		goto nla_put_failure;
+ 
++	for (c = parm->fmrs; c; c = c->next) {
++		struct nlattr *fmr = nla_nest_start(skb, ++fmrcnt);
++		if (!fmr ||
++			nla_put(skb, IFLA_IPTUN_FMR_IP6_PREFIX,
++				sizeof(c->ip6_prefix), &c->ip6_prefix) ||
++			nla_put(skb, IFLA_IPTUN_FMR_IP4_PREFIX,
++				sizeof(c->ip4_prefix), &c->ip4_prefix) ||
++			nla_put_u8(skb, IFLA_IPTUN_FMR_IP6_PREFIX_LEN, c->ip6_prefix_len) ||
++			nla_put_u8(skb, IFLA_IPTUN_FMR_IP4_PREFIX_LEN, c->ip4_prefix_len) ||
++			nla_put_u8(skb, IFLA_IPTUN_FMR_EA_LEN, c->ea_len) ||
++			nla_put_u8(skb, IFLA_IPTUN_FMR_OFFSET, c->offset))
++				goto nla_put_failure;
++
++		nla_nest_end(skb, fmr);
++	}
++	nla_nest_end(skb, fmrs);
++
+ 	if (nla_put_u16(skb, IFLA_IPTUN_ENCAP_TYPE, tunnel->encap.type) ||
+ 	    nla_put_be16(skb, IFLA_IPTUN_ENCAP_SPORT, tunnel->encap.sport) ||
+ 	    nla_put_be16(skb, IFLA_IPTUN_ENCAP_DPORT, tunnel->encap.dport) ||
+@@ -2194,6 +2458,7 @@ static const struct nla_policy ip6_tnl_policy[IFLA_IPTUN_MAX + 1] = {
+ 	[IFLA_IPTUN_ENCAP_DPORT]	= { .type = NLA_U16 },
+ 	[IFLA_IPTUN_COLLECT_METADATA]	= { .type = NLA_FLAG },
+ 	[IFLA_IPTUN_FWMARK]		= { .type = NLA_U32 },
++	[IFLA_IPTUN_FMRS]		= { .type = NLA_NESTED },
+ };
+ 
+ static struct rtnl_link_ops ip6_link_ops __read_mostly = {
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0068-ipv6-allow-rejecting-with-source-address-failed-poli.patch b/nixos/modules/kernel-patches/0068-ipv6-allow-rejecting-with-source-address-failed-poli.patch
new file mode 100644
index 0000000..a05d094
--- /dev/null
+++ b/nixos/modules/kernel-patches/0068-ipv6-allow-rejecting-with-source-address-failed-poli.patch
@@ -0,0 +1,287 @@
+From 24dbd611c9221c07bd1e7f24b472171e2a34f68b Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jogo@openwrt.org>
+Date: Tue, 27 Sep 2022 16:22:06 +0200
+Subject: [PATCH 68/96] ipv6: allow rejecting with "source address failed
+ policy"
+
+RFC6204 L-14 requires rejecting traffic from invalid addresses with
+ICMPv6 Destination Unreachable, Code 5 (Source address failed ingress/
+egress policy) on the LAN side, so add an appropriate rule for that.
+
+Signed-off-by: Jonas Gorski <jogo@openwrt.org>
+---
+ include/net/netns/ipv6.h       |  1 +
+ include/uapi/linux/fib_rules.h |  4 +++
+ include/uapi/linux/rtnetlink.h |  1 +
+ net/ipv4/fib_semantics.c       |  4 +++
+ net/ipv4/fib_trie.c            |  1 +
+ net/ipv4/ipmr.c                |  1 +
+ net/ipv6/fib6_rules.c          |  4 +++
+ net/ipv6/ip6mr.c               |  2 ++
+ net/ipv6/route.c               | 56 ++++++++++++++++++++++++++++++++--
+ 9 files changed, 72 insertions(+), 2 deletions(-)
+
+diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h
+index b4af4837d80b..094872c7df4a 100644
+--- a/include/net/netns/ipv6.h
++++ b/include/net/netns/ipv6.h
+@@ -85,6 +85,7 @@ struct netns_ipv6 {
+ 	unsigned int		fib6_routes_require_src;
+ #endif
+ 	struct rt6_info         *ip6_prohibit_entry;
++	struct rt6_info		*ip6_policy_failed_entry;
+ 	struct rt6_info         *ip6_blk_hole_entry;
+ 	struct fib6_table       *fib6_local_tbl;
+ 	struct fib_rules_ops    *fib6_rules_ops;
+diff --git a/include/uapi/linux/fib_rules.h b/include/uapi/linux/fib_rules.h
+index 232df14e1287..fdc18621ab75 100644
+--- a/include/uapi/linux/fib_rules.h
++++ b/include/uapi/linux/fib_rules.h
+@@ -82,6 +82,10 @@ enum {
+ 	FR_ACT_BLACKHOLE,	/* Drop without notification */
+ 	FR_ACT_UNREACHABLE,	/* Drop with ENETUNREACH */
+ 	FR_ACT_PROHIBIT,	/* Drop with EACCES */
++	FR_ACT_RES9,
++	FR_ACT_RES10,
++	FR_ACT_RES11,
++	FR_ACT_POLICY_FAILED,	/* Drop with EACCES */
+ 	__FR_ACT_MAX,
+ };
+ 
+diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
+index 83849a37db5b..b8e58371c0ef 100644
+--- a/include/uapi/linux/rtnetlink.h
++++ b/include/uapi/linux/rtnetlink.h
+@@ -265,6 +265,7 @@ enum {
+ 	RTN_THROW,		/* Not in this table		*/
+ 	RTN_NAT,		/* Translate this address	*/
+ 	RTN_XRESOLVE,		/* Use external resolver	*/
++	RTN_POLICY_FAILED,	/* Failed ingress/egress policy */
+ 	__RTN_MAX
+ };
+ 
+diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c
+index db7b2503f068..dfeb22580309 100644
+--- a/net/ipv4/fib_semantics.c
++++ b/net/ipv4/fib_semantics.c
+@@ -144,6 +144,10 @@ const struct fib_prop fib_props[RTN_MAX + 1] = {
+ 		.error	= -EINVAL,
+ 		.scope	= RT_SCOPE_NOWHERE,
+ 	},
++	[RTN_POLICY_FAILED] = {
++		.error	= -EACCES,
++		.scope	= RT_SCOPE_UNIVERSE,
++	},
+ };
+ 
+ static void rt_fibinfo_free(struct rtable __rcu **rtp)
+diff --git a/net/ipv4/fib_trie.c b/net/ipv4/fib_trie.c
+index 452ff177e4da..833514126e1a 100644
+--- a/net/ipv4/fib_trie.c
++++ b/net/ipv4/fib_trie.c
+@@ -2776,6 +2776,7 @@ static const char *const rtn_type_names[__RTN_MAX] = {
+ 	[RTN_THROW] = "THROW",
+ 	[RTN_NAT] = "NAT",
+ 	[RTN_XRESOLVE] = "XRESOLVE",
++	[RTN_POLICY_FAILED] = "POLICY_FAILED",
+ };
+ 
+ static inline const char *rtn_type(char *buf, size_t len, unsigned int t)
+diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
+index 13e6329784fb..31ab9eca976b 100644
+--- a/net/ipv4/ipmr.c
++++ b/net/ipv4/ipmr.c
+@@ -175,6 +175,7 @@ static int ipmr_rule_action(struct fib_rule *rule, struct flowi *flp,
+ 	case FR_ACT_UNREACHABLE:
+ 		return -ENETUNREACH;
+ 	case FR_ACT_PROHIBIT:
++	case FR_ACT_POLICY_FAILED:
+ 		return -EACCES;
+ 	case FR_ACT_BLACKHOLE:
+ 	default:
+diff --git a/net/ipv6/fib6_rules.c b/net/ipv6/fib6_rules.c
+index 7c2003833010..4679e25843d6 100644
+--- a/net/ipv6/fib6_rules.c
++++ b/net/ipv6/fib6_rules.c
+@@ -221,6 +221,10 @@ static int __fib6_rule_action(struct fib_rule *rule, struct flowi *flp,
+ 		err = -EACCES;
+ 		rt = net->ipv6.ip6_prohibit_entry;
+ 		goto discard_pkt;
++	case FR_ACT_POLICY_FAILED:
++		err = -EACCES;
++		rt = net->ipv6.ip6_policy_failed_entry;
++		goto discard_pkt;
+ 	}
+ 
+ 	tb_id = fib_rule_get_table(rule, arg);
+diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c
+index 4e74bc61a3db..b51a175e35b0 100644
+--- a/net/ipv6/ip6mr.c
++++ b/net/ipv6/ip6mr.c
+@@ -163,6 +163,8 @@ static int ip6mr_rule_action(struct fib_rule *rule, struct flowi *flp,
+ 		return -ENETUNREACH;
+ 	case FR_ACT_PROHIBIT:
+ 		return -EACCES;
++	case FR_ACT_POLICY_FAILED:
++		return -EACCES;
+ 	case FR_ACT_BLACKHOLE:
+ 	default:
+ 		return -EINVAL;
+diff --git a/net/ipv6/route.c b/net/ipv6/route.c
+index 916417944ec8..43bc02822169 100644
+--- a/net/ipv6/route.c
++++ b/net/ipv6/route.c
+@@ -97,6 +97,8 @@ static int		ip6_pkt_discard(struct sk_buff *skb);
+ static int		ip6_pkt_discard_out(struct net *net, struct sock *sk, struct sk_buff *skb);
+ static int		ip6_pkt_prohibit(struct sk_buff *skb);
+ static int		ip6_pkt_prohibit_out(struct net *net, struct sock *sk, struct sk_buff *skb);
++static int		ip6_pkt_policy_failed(struct sk_buff *skb);
++static int		ip6_pkt_policy_failed_out(struct net *net, struct sock *sk, struct sk_buff *skb);
+ static void		ip6_link_failure(struct sk_buff *skb);
+ static void		ip6_rt_update_pmtu(struct dst_entry *dst, struct sock *sk,
+ 					   struct sk_buff *skb, u32 mtu,
+@@ -317,6 +319,18 @@ static const struct rt6_info ip6_prohibit_entry_template = {
+ 	.rt6i_flags	= (RTF_REJECT | RTF_NONEXTHOP),
+ };
+ 
++static const struct rt6_info ip6_policy_failed_entry_template = {
++	.dst = {
++		.__refcnt	= ATOMIC_INIT(1),
++		.__use		= 1,
++		.obsolete	= DST_OBSOLETE_FORCE_CHK,
++		.error		= -EACCES,
++		.input		= ip6_pkt_policy_failed,
++		.output		= ip6_pkt_policy_failed_out,
++	},
++	.rt6i_flags	= (RTF_REJECT | RTF_NONEXTHOP),
++};
++
+ static const struct rt6_info ip6_blk_hole_entry_template = {
+ 	.dst = {
+ 		.__refcnt	= ATOMIC_INIT(1),
+@@ -1036,6 +1050,7 @@ static const int fib6_prop[RTN_MAX + 1] = {
+ 	[RTN_BLACKHOLE]	= -EINVAL,
+ 	[RTN_UNREACHABLE] = -EHOSTUNREACH,
+ 	[RTN_PROHIBIT]	= -EACCES,
++	[RTN_POLICY_FAILED] = -EACCES,
+ 	[RTN_THROW]	= -EAGAIN,
+ 	[RTN_NAT]	= -EINVAL,
+ 	[RTN_XRESOLVE]	= -EINVAL,
+@@ -1071,6 +1086,10 @@ static void ip6_rt_init_dst_reject(struct rt6_info *rt, u8 fib6_type)
+ 		rt->dst.output = ip6_pkt_prohibit_out;
+ 		rt->dst.input = ip6_pkt_prohibit;
+ 		break;
++	case RTN_POLICY_FAILED:
++		rt->dst.output = ip6_pkt_policy_failed_out;
++		rt->dst.input = ip6_pkt_policy_failed;
++		break;
+ 	case RTN_THROW:
+ 	case RTN_UNREACHABLE:
+ 	default:
+@@ -4539,6 +4558,17 @@ static int ip6_pkt_prohibit_out(struct net *net, struct sock *sk, struct sk_buff
+ 	return ip6_pkt_drop(skb, ICMPV6_ADM_PROHIBITED, IPSTATS_MIB_OUTNOROUTES);
+ }
+ 
++static int ip6_pkt_policy_failed(struct sk_buff *skb)
++{
++	return ip6_pkt_drop(skb, ICMPV6_POLICY_FAIL, IPSTATS_MIB_INNOROUTES);
++}
++
++static int ip6_pkt_policy_failed_out(struct net *net, struct sock *sk, struct sk_buff *skb)
++{
++	skb->dev = skb_dst(skb)->dev;
++	return ip6_pkt_drop(skb, ICMPV6_POLICY_FAIL, IPSTATS_MIB_OUTNOROUTES);
++}
++
+ /*
+  *	Allocate a dst for local (unicast / anycast) address.
+  */
+@@ -5032,7 +5062,8 @@ static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh,
+ 	if (rtm->rtm_type == RTN_UNREACHABLE ||
+ 	    rtm->rtm_type == RTN_BLACKHOLE ||
+ 	    rtm->rtm_type == RTN_PROHIBIT ||
+-	    rtm->rtm_type == RTN_THROW)
++	    rtm->rtm_type == RTN_THROW ||
++	    rtm->rtm_type == RTN_POLICY_FAILED)
+ 		cfg->fc_flags |= RTF_REJECT;
+ 
+ 	if (rtm->rtm_type == RTN_LOCAL)
+@@ -6285,6 +6316,8 @@ static int ip6_route_dev_notify(struct notifier_block *this,
+ #ifdef CONFIG_IPV6_MULTIPLE_TABLES
+ 		net->ipv6.ip6_prohibit_entry->dst.dev = dev;
+ 		net->ipv6.ip6_prohibit_entry->rt6i_idev = in6_dev_get(dev);
++		net->ipv6.ip6_policy_failed_entry->dst.dev = dev;
++		net->ipv6.ip6_policy_failed_entry->rt6i_idev = in6_dev_get(dev);
+ 		net->ipv6.ip6_blk_hole_entry->dst.dev = dev;
+ 		net->ipv6.ip6_blk_hole_entry->rt6i_idev = in6_dev_get(dev);
+ #endif
+@@ -6296,6 +6329,7 @@ static int ip6_route_dev_notify(struct notifier_block *this,
+ 		in6_dev_put_clear(&net->ipv6.ip6_null_entry->rt6i_idev);
+ #ifdef CONFIG_IPV6_MULTIPLE_TABLES
+ 		in6_dev_put_clear(&net->ipv6.ip6_prohibit_entry->rt6i_idev);
++		in6_dev_put_clear(&net->ipv6.ip6_policy_failed_entry->rt6i_idev);
+ 		in6_dev_put_clear(&net->ipv6.ip6_blk_hole_entry->rt6i_idev);
+ #endif
+ 	}
+@@ -6487,6 +6521,8 @@ static int __net_init ip6_route_net_init(struct net *net)
+ 
+ #ifdef CONFIG_IPV6_MULTIPLE_TABLES
+ 	net->ipv6.fib6_has_custom_rules = false;
++
++
+ 	net->ipv6.ip6_prohibit_entry = kmemdup(&ip6_prohibit_entry_template,
+ 					       sizeof(*net->ipv6.ip6_prohibit_entry),
+ 					       GFP_KERNEL);
+@@ -6497,11 +6533,21 @@ static int __net_init ip6_route_net_init(struct net *net)
+ 			 ip6_template_metrics, true);
+ 	INIT_LIST_HEAD(&net->ipv6.ip6_prohibit_entry->rt6i_uncached);
+ 
++	net->ipv6.ip6_policy_failed_entry =
++				kmemdup(&ip6_policy_failed_entry_template,
++				sizeof(*net->ipv6.ip6_policy_failed_entry), GFP_KERNEL);
++	if (!net->ipv6.ip6_policy_failed_entry)
++		goto out_ip6_prohibit_entry;
++	net->ipv6.ip6_policy_failed_entry->dst.ops = &net->ipv6.ip6_dst_ops;
++	dst_init_metrics(&net->ipv6.ip6_policy_failed_entry->dst,
++			 ip6_template_metrics, true);
++	INIT_LIST_HEAD(&net->ipv6.ip6_policy_failed_entry->rt6i_uncached);
++
+ 	net->ipv6.ip6_blk_hole_entry = kmemdup(&ip6_blk_hole_entry_template,
+ 					       sizeof(*net->ipv6.ip6_blk_hole_entry),
+ 					       GFP_KERNEL);
+ 	if (!net->ipv6.ip6_blk_hole_entry)
+-		goto out_ip6_prohibit_entry;
++		goto out_ip6_policy_failed_entry;
+ 	net->ipv6.ip6_blk_hole_entry->dst.ops = &net->ipv6.ip6_dst_ops;
+ 	dst_init_metrics(&net->ipv6.ip6_blk_hole_entry->dst,
+ 			 ip6_template_metrics, true);
+@@ -6528,6 +6574,8 @@ static int __net_init ip6_route_net_init(struct net *net)
+ 	return ret;
+ 
+ #ifdef CONFIG_IPV6_MULTIPLE_TABLES
++out_ip6_policy_failed_entry:
++	kfree(net->ipv6.ip6_policy_failed_entry);
+ out_ip6_prohibit_entry:
+ 	kfree(net->ipv6.ip6_prohibit_entry);
+ out_ip6_null_entry:
+@@ -6547,6 +6595,7 @@ static void __net_exit ip6_route_net_exit(struct net *net)
+ 	kfree(net->ipv6.ip6_null_entry);
+ #ifdef CONFIG_IPV6_MULTIPLE_TABLES
+ 	kfree(net->ipv6.ip6_prohibit_entry);
++	kfree(net->ipv6.ip6_policy_failed_entry);
+ 	kfree(net->ipv6.ip6_blk_hole_entry);
+ #endif
+ 	dst_entries_destroy(&net->ipv6.ip6_dst_ops);
+@@ -6624,6 +6673,9 @@ void __init ip6_route_init_special_entries(void)
+ 	init_net.ipv6.ip6_prohibit_entry->rt6i_idev = in6_dev_get(init_net.loopback_dev);
+ 	init_net.ipv6.ip6_blk_hole_entry->dst.dev = init_net.loopback_dev;
+ 	init_net.ipv6.ip6_blk_hole_entry->rt6i_idev = in6_dev_get(init_net.loopback_dev);
++	init_net.ipv6.ip6_policy_failed_entry->dst.dev = init_net.loopback_dev;
++	init_net.ipv6.ip6_policy_failed_entry->rt6i_idev =
++		in6_dev_get(init_net.loopback_dev);
+   #endif
+ }
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0069-net-provide-defines-for-_POLICY_FAILED-until-all-cod.patch b/nixos/modules/kernel-patches/0069-net-provide-defines-for-_POLICY_FAILED-until-all-cod.patch
new file mode 100644
index 0000000..7d24a5a
--- /dev/null
+++ b/nixos/modules/kernel-patches/0069-net-provide-defines-for-_POLICY_FAILED-until-all-cod.patch
@@ -0,0 +1,62 @@
+From 60d85ef707d77be1a2a1233630c053807c54b596 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jogo@openwrt.org>
+Date: Tue, 27 Sep 2022 16:22:06 +0200
+Subject: [PATCH 69/96] net: provide defines for _POLICY_FAILED until all code
+ is updated
+
+Upstream introduced ICMPV6_POLICY_FAIL for code 5 of destination
+unreachable, conflicting with our name.
+
+Add appropriate defines to allow our code to build with the new
+name until we have updated our local patches for older kernels
+and userspace packages.
+
+Signed-off-by: Jonas Gorski <jogo@openwrt.org>
+---
+ include/uapi/linux/fib_rules.h | 2 ++
+ include/uapi/linux/icmpv6.h    | 2 ++
+ include/uapi/linux/rtnetlink.h | 2 ++
+ 3 files changed, 6 insertions(+)
+
+diff --git a/include/uapi/linux/fib_rules.h b/include/uapi/linux/fib_rules.h
+index fdc18621ab75..9be1de81ab95 100644
+--- a/include/uapi/linux/fib_rules.h
++++ b/include/uapi/linux/fib_rules.h
+@@ -89,6 +89,8 @@ enum {
+ 	__FR_ACT_MAX,
+ };
+ 
++#define FR_ACT_FAILED_POLICY FR_ACT_POLICY_FAILED
++
+ #define FR_ACT_MAX (__FR_ACT_MAX - 1)
+ 
+ #endif
+diff --git a/include/uapi/linux/icmpv6.h b/include/uapi/linux/icmpv6.h
+index ecaece3af38d..f21cb3786bc2 100644
+--- a/include/uapi/linux/icmpv6.h
++++ b/include/uapi/linux/icmpv6.h
+@@ -126,6 +126,8 @@ struct icmp6hdr {
+ #define ICMPV6_POLICY_FAIL		5
+ #define ICMPV6_REJECT_ROUTE		6
+ 
++#define ICMPV6_FAILED_POLICY		ICMPV6_POLICY_FAIL
++
+ /*
+  *	Codes for Time Exceeded
+  */
+diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
+index b8e58371c0ef..2b82311f8035 100644
+--- a/include/uapi/linux/rtnetlink.h
++++ b/include/uapi/linux/rtnetlink.h
+@@ -269,6 +269,8 @@ enum {
+ 	__RTN_MAX
+ };
+ 
++#define RTN_FAILED_POLICY RTN_POLICY_FAILED
++
+ #define RTN_MAX (__RTN_MAX - 1)
+ 
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0070-of_net-add-mac-address-increment-support.patch b/nixos/modules/kernel-patches/0070-of_net-add-mac-address-increment-support.patch
new file mode 100644
index 0000000..d55e650
--- /dev/null
+++ b/nixos/modules/kernel-patches/0070-of_net-add-mac-address-increment-support.patch
@@ -0,0 +1,95 @@
+From 0cf7322f17785fc41d1e38a745a00ccd8fe7b8c0 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Tue, 30 Mar 2021 18:21:14 +0200
+Subject: [PATCH 70/96] of_net: add mac-address-increment support
+
+Lots of embedded devices use the mac-address of other interface
+extracted from nvmem cells and increments it by one or two. Add two
+bindings to integrate this and directly use the right mac-address for
+the interface. Some example are some routers that use the gmac
+mac-address stored in the art partition and increments it by one for the
+wifi. mac-address-increment-byte bindings is used to tell what byte of
+the mac-address has to be increased (if not defined the last byte is
+increased) and mac-address-increment tells how much the byte decided
+early has to be increased.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+---
+ net/core/of_net.c | 43 +++++++++++++++++++++++++++++++++++++++----
+ 1 file changed, 39 insertions(+), 4 deletions(-)
+
+diff --git a/net/core/of_net.c b/net/core/of_net.c
+index f1a9bf7578e7..44c7cb0dbbe7 100644
+--- a/net/core/of_net.c
++++ b/net/core/of_net.c
+@@ -119,28 +119,63 @@ static int of_get_mac_addr_nvmem(struct device_node *np, u8 *addr)
+  * this case, the real MAC is in 'local-mac-address', and 'mac-address' exists
+  * but is all zeros.
+  *
++ * DT can tell the system to increment the mac-address after is extracted by
++ * using:
++ * - mac-address-increment-byte to decide what byte to increase
++ *   (if not defined is increased the last byte)
++ * - mac-address-increment to decide how much to increase. The value WILL
++ *   overflow to other bytes if the increment is over 255 or the total
++ *   increment will exceed 255 of the current byte.
++ *   (example 00:01:02:03:04:ff + 1 == 00:01:02:03:05:00)
++ *   (example 00:01:02:03:04:fe + 5 == 00:01:02:03:05:03)
++ *
+  * Return: 0 on success and errno in case of error.
+ */
+ int of_get_mac_address(struct device_node *np, u8 *addr)
+ {
++	u32 inc_idx, mac_inc, mac_val;
+ 	int ret;
+ 
++	/* Check first if the increment byte is present and valid.
++	 * If not set assume to increment the last byte if found.
++	 */
++	if (of_property_read_u32(np, "mac-address-increment-byte", &inc_idx))
++		inc_idx = 5;
++	if (inc_idx < 3 || inc_idx > 5)
++		return -EINVAL;
++
+ 	if (!np)
+ 		return -ENODEV;
+ 
+ 	ret = of_get_mac_addr(np, "mac-address", addr);
+ 	if (!ret)
+-		return 0;
++		goto found;
+ 
+ 	ret = of_get_mac_addr(np, "local-mac-address", addr);
+ 	if (!ret)
+-		return 0;
++		goto found;
+ 
+ 	ret = of_get_mac_addr(np, "address", addr);
+ 	if (!ret)
+-		return 0;
++		goto found;
+ 
+-	return of_get_mac_addr_nvmem(np, addr);
++	ret = of_get_mac_addr_nvmem(np, addr);
++	if (ret)
++		return ret;
++
++found:
++	if (!of_property_read_u32(np, "mac-address-increment", &mac_inc)) {
++		/* Convert to a contiguous value */
++		mac_val = (addr[3] << 16) + (addr[4] << 8) + addr[5];
++		mac_val += mac_inc << 8 * (5-inc_idx);
++
++		/* Apply the incremented value handling overflow case */
++		addr[3] = (mac_val >> 16) & 0xff;
++		addr[4] = (mac_val >> 8) & 0xff;
++		addr[5] = (mac_val >> 0) & 0xff;
++	}
++
++	return ret;
+ }
+ EXPORT_SYMBOL(of_get_mac_address);
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0071-of-of_net-write-back-netdev-MAC-address-to-device-tr.patch b/nixos/modules/kernel-patches/0071-of-of_net-write-back-netdev-MAC-address-to-device-tr.patch
new file mode 100644
index 0000000..acbd2c9
--- /dev/null
+++ b/nixos/modules/kernel-patches/0071-of-of_net-write-back-netdev-MAC-address-to-device-tr.patch
@@ -0,0 +1,59 @@
+From 351deef2622af5e81dcd714743809ca7720ad0b7 Mon Sep 17 00:00:00 2001
+From: OpenWrt community <openwrt-devel@lists.openwrt.org>
+Date: Wed, 13 Jul 2022 12:22:48 +0200
+Subject: [PATCH 71/96] of/of_net: write back netdev MAC-address to device-tree
+
+The label-mac logic relies on the mac-address property of a netdev
+devices of-node. However, the mac address can also be stored as a
+different property or read from e.g. an mtd device.
+
+Create this node when reading a mac-address from OF if it does not
+already exist and copy the mac-address used for the device to this
+property. This way, the MAC address can be accessed using procfs.
+---
+ net/core/of_net.c | 22 ++++++++++++++++++++++
+ 1 file changed, 22 insertions(+)
+
+diff --git a/net/core/of_net.c b/net/core/of_net.c
+index 44c7cb0dbbe7..f1fa0aac725c 100644
+--- a/net/core/of_net.c
++++ b/net/core/of_net.c
+@@ -95,6 +95,27 @@ static int of_get_mac_addr_nvmem(struct device_node *np, u8 *addr)
+ 	return 0;
+ }
+ 
++static int of_add_mac_address(struct device_node *np, u8* addr)
++{
++	struct property *prop;
++
++	prop = kzalloc(sizeof(*prop), GFP_KERNEL);
++	if (!prop)
++		return -ENOMEM;
++
++	prop->name = "mac-address";
++	prop->length = ETH_ALEN;
++	prop->value = kmemdup(addr, ETH_ALEN, GFP_KERNEL);
++	if (!prop->value || of_update_property(np, prop))
++		goto free;
++
++	return 0;
++free:
++	kfree(prop->value);
++	kfree(prop);
++	return -ENOMEM;
++}
++
+ /**
+  * of_get_mac_address()
+  * @np:		Caller's Device Node
+@@ -175,6 +196,7 @@ int of_get_mac_address(struct device_node *np, u8 *addr)
+ 		addr[5] = (mac_val >> 0) & 0xff;
+ 	}
+ 
++	of_add_mac_address(np, addr);
+ 	return ret;
+ }
+ EXPORT_SYMBOL(of_get_mac_address);
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0072-generic-add-detach-callback-to-struct-phy_driver.patch b/nixos/modules/kernel-patches/0072-generic-add-detach-callback-to-struct-phy_driver.patch
new file mode 100644
index 0000000..55a87f6
--- /dev/null
+++ b/nixos/modules/kernel-patches/0072-generic-add-detach-callback-to-struct-phy_driver.patch
@@ -0,0 +1,47 @@
+From 2c9814f519bd0aea1ff9e9dbdee8f187e7d036bf Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <juhosg@openwrt.org>
+Date: Tue, 27 Sep 2022 16:22:09 +0200
+Subject: [PATCH 72/96] generic: add detach callback to struct phy_driver
+
+lede-commit: fe61fc2d7d0b3fb348b502f68f98243b3ddf5867
+
+Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
+---
+ drivers/net/phy/phy_device.c | 3 +++
+ include/linux/phy.h          | 6 ++++++
+ 2 files changed, 9 insertions(+)
+
+diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
+index f90a21781d8d..c8c6edb8e195 100644
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -1744,6 +1744,9 @@ void phy_detach(struct phy_device *phydev)
+ 	struct module *ndev_owner = NULL;
+ 	struct mii_bus *bus;
+ 
++	if (phydev->drv && phydev->drv->detach)
++		phydev->drv->detach(phydev);
++
+ 	if (phydev->sysfs_links) {
+ 		if (dev)
+ 			sysfs_remove_link(&dev->dev.kobj, "phydev");
+diff --git a/include/linux/phy.h b/include/linux/phy.h
+index b09f7d36cff2..77b853437e7c 100644
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -827,6 +827,12 @@ struct phy_driver {
+ 	/** @handle_interrupt: Override default interrupt handling */
+ 	irqreturn_t (*handle_interrupt)(struct phy_device *phydev);
+ 
++	/*
++	 * Called before an ethernet device is detached
++	 * from the PHY.
++	 */
++	void (*detach)(struct phy_device *phydev);
++
+ 	/** @remove: Clears up any memory if needed */
+ 	void (*remove)(struct phy_device *phydev);
+ 
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0073-net-dsa-tag_mtk-add-padding-for-tx-packets.patch b/nixos/modules/kernel-patches/0073-net-dsa-tag_mtk-add-padding-for-tx-packets.patch
new file mode 100644
index 0000000..2870bf8
--- /dev/null
+++ b/nixos/modules/kernel-patches/0073-net-dsa-tag_mtk-add-padding-for-tx-packets.patch
@@ -0,0 +1,37 @@
+From 4347079c0a187cd344ff33497784e57bac30ff46 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 6 May 2022 21:38:42 +0200
+Subject: [PATCH 73/96] net: dsa: tag_mtk: add padding for tx packets
+
+Padding for transmitted packets needs to account for the special tag.
+With not enough padding, garbage bytes are inserted by the switch at the
+end of small packets.
+
+Fixes: 5cd8985a1909 ("net-next: dsa: add Mediatek tag RX/TX handler")
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ net/dsa/tag_mtk.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/net/dsa/tag_mtk.c b/net/dsa/tag_mtk.c
+index 415d8ece242a..1d1f9dbd9e93 100644
+--- a/net/dsa/tag_mtk.c
++++ b/net/dsa/tag_mtk.c
+@@ -25,6 +25,14 @@ static struct sk_buff *mtk_tag_xmit(struct sk_buff *skb,
+ 	u8 xmit_tpid;
+ 	u8 *mtk_tag;
+ 
++	/* The Ethernet switch we are interfaced with needs packets to be at
++	 * least 64 bytes (including FCS) otherwise their padding might be
++	 * corrupted. With tags enabled, we need to make sure that packets are
++	 * at least 68 bytes (including FCS and tag).
++	 */
++	if (__skb_put_padto(skb, ETH_ZLEN + MTK_HDR_LEN, false))
++		return NULL;
++
+ 	/* Build the special tag after the MAC Source Address. If VLAN header
+ 	 * is present, it's required that VLAN header and special tag is
+ 	 * being combined. Only in this way we can allow the switch can parse
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0074-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-p.patch b/nixos/modules/kernel-patches/0074-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-p.patch
new file mode 100644
index 0000000..e2393ea
--- /dev/null
+++ b/nixos/modules/kernel-patches/0074-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-p.patch
@@ -0,0 +1,33 @@
+From 72f2e130775bcce8e5ddcb79d9991fcf464c0d6e Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz <tobias@waldekranz.com>
+Date: Sat, 16 Jan 2021 02:25:15 +0100
+Subject: [PATCH 74/96] net: dsa: mv88e6xxx: Request assisted learning on CPU
+ port
+
+While the hardware is capable of performing learning on the CPU port,
+it requires alot of additions to the bridge's forwarding path in order
+to handle multi-destination traffic correctly.
+
+Until that is in place, opt for the next best thing and let DSA sync
+the relevant addresses down to the hardware FDB.
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+---
+ drivers/net/dsa/mv88e6xxx/chip.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
+index 74f6db6bb804..241083ecc4e6 100644
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -6915,6 +6915,7 @@ static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
+ 	ds->ops = &mv88e6xxx_switch_ops;
+ 	ds->ageing_time_min = chip->info->age_time_coeff;
+ 	ds->ageing_time_max = chip->info->age_time_coeff * U8_MAX;
++	ds->assisted_learning_on_cpu_port = true;
+ 
+ 	/* Some chips support up to 32, but that requires enabling the
+ 	 * 5-bit port mode, which we do not support. 640k^W16 ought to
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0075-ARM-kirkwood-add-missing-linux-if_ether.h-for-ETH_AL.patch b/nixos/modules/kernel-patches/0075-ARM-kirkwood-add-missing-linux-if_ether.h-for-ETH_AL.patch
new file mode 100644
index 0000000..38b6925
--- /dev/null
+++ b/nixos/modules/kernel-patches/0075-ARM-kirkwood-add-missing-linux-if_ether.h-for-ETH_AL.patch
@@ -0,0 +1,49 @@
+From 07de3a98a89a8c5cfc78e26a45ac93972576efc2 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Thu, 5 Aug 2021 23:23:30 +0100
+Subject: [PATCH 75/96] ARM: kirkwood: add missing <linux/if_ether.h> for
+ ETH_ALEN
+
+After commit 83216e3988cd1 ("of: net: pass the dst buffer to
+of_get_mac_address()") build fails for kirkwood as ETH_ALEN is not
+defined.
+
+arch/arm/mach-mvebu/kirkwood.c: In function 'kirkwood_dt_eth_fixup':
+arch/arm/mach-mvebu/kirkwood.c:87:13: error: 'ETH_ALEN' undeclared (first use in this function); did you mean 'ESTALE'?
+   u8 tmpmac[ETH_ALEN];
+             ^~~~~~~~
+             ESTALE
+arch/arm/mach-mvebu/kirkwood.c:87:13: note: each undeclared identifier is reported only once for each function it appears in
+arch/arm/mach-mvebu/kirkwood.c:87:6: warning: unused variable 'tmpmac' [-Wunused-variable]
+   u8 tmpmac[ETH_ALEN];
+      ^~~~~~
+make[5]: *** [scripts/Makefile.build:262: arch/arm/mach-mvebu/kirkwood.o] Error 1
+make[5]: *** Waiting for unfinished jobs....
+
+Add missing #include <linux/if_ether.h> to fix this.
+
+Cc: David S. Miller <davem@davemloft.net>
+Cc: Andrew Lunn <andrew@lunn.ch>
+Cc: Michael Walle <michael@walle.cc>
+Reported-by: https://buildbot.openwrt.org/master/images/#/builders/56/builds/220/steps/44/logs/stdio
+Fixes: 83216e3988cd1 ("of: net: pass the dst buffer to of_get_mac_address()")
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ arch/arm/mach-mvebu/kirkwood.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/arch/arm/mach-mvebu/kirkwood.c b/arch/arm/mach-mvebu/kirkwood.c
+index 06b1706595f4..a493a896e6ee 100644
+--- a/arch/arm/mach-mvebu/kirkwood.c
++++ b/arch/arm/mach-mvebu/kirkwood.c
+@@ -14,6 +14,7 @@
+ #include <linux/kernel.h>
+ #include <linux/init.h>
+ #include <linux/mbus.h>
++#include <linux/if_ether.h>
+ #include <linux/of.h>
+ #include <linux/of_address.h>
+ #include <linux/of_net.h>
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0076-bcma-get-SoC-device-struct-copy-its-DMA-params-to-th.patch b/nixos/modules/kernel-patches/0076-bcma-get-SoC-device-struct-copy-its-DMA-params-to-th.patch
new file mode 100644
index 0000000..b57de84
--- /dev/null
+++ b/nixos/modules/kernel-patches/0076-bcma-get-SoC-device-struct-copy-its-DMA-params-to-th.patch
@@ -0,0 +1,85 @@
+From 77ef3ddbb02d11c3aac1f4174959f7e3d898070b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= <rafal@milecki.pl>
+Date: Tue, 27 Sep 2022 16:22:10 +0200
+Subject: [PATCH 76/96] bcma: get SoC device struct & copy its DMA params to
+ the subdevices
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+For bus devices to be fully usable it's required to set their DMA
+parameters.
+
+For years it has been missing and remained unnoticed because of
+mips_dma_alloc_coherent() silently handling the empty coherent_dma_mask.
+Kernel 4.19 came with a lot of DMA changes and caused a regression on
+the bcm47xx. Starting with the commit f8c55dc6e828 ("MIPS: use generic
+dma noncoherent ops for simple noncoherent platforms") DMA coherent
+allocations just fail. Example:
+[    1.114914] bgmac_bcma bcma0:2: Allocation of TX ring 0x200 failed
+[    1.121215] bgmac_bcma bcma0:2: Unable to alloc memory for DMA
+[    1.127626] bgmac_bcma: probe of bcma0:2 failed with error -12
+[    1.133838] bgmac_bcma: Broadcom 47xx GBit MAC driver loaded
+
+This change fixes above regression in addition to the MIPS bcm47xx
+commit 321c46b91550 ("MIPS: BCM47XX: Setup struct device for the SoC").
+
+It also fixes another *old* GPIO regression caused by a parent pointing
+to the NULL:
+[    0.157054] missing gpiochip .dev parent pointer
+[    0.157287] bcma: bus0: Error registering GPIO driver: -22
+introduced by the commit 74f4e0cc6108 ("bcma: switch GPIO portions to
+use GPIOLIB_IRQCHIP").
+
+Fixes: f8c55dc6e828 ("MIPS: use generic dma noncoherent ops for simple noncoherent platforms")
+Fixes: 74f4e0cc6108 ("bcma: switch GPIO portions to use GPIOLIB_IRQCHIP")
+Cc: linux-mips@linux-mips.org
+Cc: Christoph Hellwig <hch@lst.de>
+Cc: Linus Walleij <linus.walleij@linaro.org>
+Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
+---
+ drivers/bcma/host_soc.c |  2 ++
+ drivers/bcma/main.c     | 10 +++++++---
+ 2 files changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/bcma/host_soc.c b/drivers/bcma/host_soc.c
+index 90d5bdc12e03..fd2e8ff17c76 100644
+--- a/drivers/bcma/host_soc.c
++++ b/drivers/bcma/host_soc.c
+@@ -191,6 +191,8 @@ int __init bcma_host_soc_init(struct bcma_soc *soc)
+ 	struct bcma_bus *bus = &soc->bus;
+ 	int err;
+ 
++	bus->dev = soc->dev;
++
+ 	/* Scan bus and initialize it */
+ 	err = bcma_bus_early_register(bus);
+ 	if (err)
+diff --git a/drivers/bcma/main.c b/drivers/bcma/main.c
+index 44392b624b20..71e8ce05079a 100644
+--- a/drivers/bcma/main.c
++++ b/drivers/bcma/main.c
+@@ -236,13 +236,17 @@ EXPORT_SYMBOL(bcma_core_irq);
+ 
+ void bcma_prepare_core(struct bcma_bus *bus, struct bcma_device *core)
+ {
+-	device_initialize(&core->dev);
++	struct device *dev = &core->dev;
++
++	device_initialize(dev);
+ 	core->dev.release = bcma_release_core_dev;
+ 	core->dev.bus = &bcma_bus_type;
+-	dev_set_name(&core->dev, "bcma%d:%d", bus->num, core->core_index);
++	dev_set_name(dev, "bcma%d:%d", bus->num, core->core_index);
+ 	core->dev.parent = bus->dev;
+-	if (bus->dev)
++	if (bus->dev) {
+ 		bcma_of_fill_device(bus->dev, core);
++		dma_coerce_mask_and_coherent(dev, bus->dev->coherent_dma_mask);
++	}
+ 
+ 	switch (bus->hosttype) {
+ 	case BCMA_HOSTTYPE_PCI:
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0077-gpio-gpio-cascade-add-generic-GPIO-cascade.patch b/nixos/modules/kernel-patches/0077-gpio-gpio-cascade-add-generic-GPIO-cascade.patch
new file mode 100644
index 0000000..98dfc24
--- /dev/null
+++ b/nixos/modules/kernel-patches/0077-gpio-gpio-cascade-add-generic-GPIO-cascade.patch
@@ -0,0 +1,189 @@
+From f5105edd6729159f332579da5416aa3a26c84ad6 Mon Sep 17 00:00:00 2001
+From: Mauri Sandberg <maukka@ext.kapsi.fi>
+Date: Thu, 25 Mar 2021 11:48:05 +0200
+Subject: [PATCH 77/96] gpio: gpio-cascade: add generic GPIO cascade
+
+Adds support for building cascades of GPIO lines. That is, it allows
+setups when there is one upstream line and multiple cascaded lines, out
+of which one can be chosen at a time. The status of the upstream line
+can be conveyed to the selected cascaded line or, vice versa, the status
+of the cascaded line can be conveyed to the upstream line.
+
+A multiplexer is being used to select, which cascaded GPIO line is being
+used at any given time.
+
+At the moment only input direction is supported. In future it should be
+possible to add support for output direction, too.
+
+Signed-off-by: Mauri Sandberg <maukka@ext.kapsi.fi>
+Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
+Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+---
+ drivers/gpio/Kconfig        |  15 +++++
+ drivers/gpio/Makefile       |   1 +
+ drivers/gpio/gpio-cascade.c | 117 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 133 insertions(+)
+ create mode 100644 drivers/gpio/gpio-cascade.c
+
+diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
+index b01961999ced..b88980948950 100644
+--- a/drivers/gpio/Kconfig
++++ b/drivers/gpio/Kconfig
+@@ -1710,4 +1710,19 @@ config GPIO_SIM
+ 
+ endmenu
+ 
++comment "Other GPIO expanders"
++
++config GPIO_CASCADE
++	tristate "General GPIO cascade"
++	select MULTIPLEXER
++	help
++	  Say yes here to enable support for generic GPIO cascade.
++
++	  This allows building one-to-many cascades of GPIO lines using
++	  different types of multiplexers readily available. At the
++	  moment only input lines are supported.
++
++	  To build the driver as a module choose 'm' and the resulting module
++	  will be called 'gpio-cascade'.
++
+ endif
+diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
+index 14352f6dfe8e..561c060b0d9f 100644
+--- a/drivers/gpio/Makefile
++++ b/drivers/gpio/Makefile
+@@ -44,6 +44,7 @@ obj-$(CONFIG_GPIO_BD9571MWV)		+= gpio-bd9571mwv.o
+ obj-$(CONFIG_GPIO_BRCMSTB)		+= gpio-brcmstb.o
+ obj-$(CONFIG_GPIO_BT8XX)		+= gpio-bt8xx.o
+ obj-$(CONFIG_GPIO_CADENCE)		+= gpio-cadence.o
++obj-$(CONFIG_GPIO_CASCADE)		+= gpio-cascade.o
+ obj-$(CONFIG_GPIO_CLPS711X)		+= gpio-clps711x.o
+ obj-$(CONFIG_GPIO_SNPS_CREG)		+= gpio-creg-snps.o
+ obj-$(CONFIG_GPIO_CRYSTAL_COVE)		+= gpio-crystalcove.o
+diff --git a/drivers/gpio/gpio-cascade.c b/drivers/gpio/gpio-cascade.c
+new file mode 100644
+index 000000000000..5cbda882d79a
+--- /dev/null
++++ b/drivers/gpio/gpio-cascade.c
+@@ -0,0 +1,117 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ *  A generic GPIO cascade driver
++ *
++ *  Copyright (C) 2021 Mauri Sandberg <maukka@ext.kapsi.fi>
++ *
++ * This allows building cascades of GPIO lines in a manner illustrated
++ * below:
++ *
++ *                 /|---- Cascaded GPIO line 0
++ *  Upstream      | |---- Cascaded GPIO line 1
++ *  GPIO line ----+ | .
++ *                | | .
++ *                 \|---- Cascaded GPIO line n
++ *
++ * A multiplexer is being used to select, which cascaded line is being
++ * addressed at any given time.
++ *
++ * At the moment only input mode is supported due to lack of means for
++ * testing output functionality. At least theoretically output should be
++ * possible with open drain constructions.
++ */
++
++#include <linux/module.h>
++#include <linux/slab.h>
++#include <linux/platform_device.h>
++#include <linux/mux/consumer.h>
++
++#include <linux/gpio/consumer.h>
++#include <linux/gpio/driver.h>
++
++struct gpio_cascade {
++	struct gpio_chip	gpio_chip;
++	struct device		*parent;
++	struct mux_control	*mux_control;
++	struct gpio_desc	*upstream_line;
++};
++
++static struct gpio_cascade *chip_to_cascade(struct gpio_chip *gc)
++{
++	return container_of(gc, struct gpio_cascade, gpio_chip);
++}
++
++static int gpio_cascade_get_direction(struct gpio_chip *gc, unsigned int offset)
++{
++	return GPIO_LINE_DIRECTION_IN;
++}
++
++static int gpio_cascade_get_value(struct gpio_chip *gc, unsigned int offset)
++{
++	struct gpio_cascade *cas = chip_to_cascade(gc);
++	int ret;
++
++	ret = mux_control_select(cas->mux_control, offset);
++	if (ret)
++		return ret;
++
++	ret = gpiod_get_value(cas->upstream_line);
++	mux_control_deselect(cas->mux_control);
++	return ret;
++}
++
++static int gpio_cascade_probe(struct platform_device *pdev)
++{
++	struct device *dev = &pdev->dev;
++	struct gpio_cascade *cas;
++	struct mux_control *mc;
++	struct gpio_desc *upstream;
++	struct gpio_chip *gc;
++
++	cas = devm_kzalloc(dev, sizeof(*cas), GFP_KERNEL);
++	if (!cas)
++		return -ENOMEM;
++
++	mc = devm_mux_control_get(dev, NULL);
++	if (IS_ERR(mc))
++		return dev_err_probe(dev, PTR_ERR(mc), "unable to get mux-control\n");
++
++	cas->mux_control = mc;
++	upstream = devm_gpiod_get(dev, "upstream",  GPIOD_IN);
++	if (IS_ERR(upstream))
++		return dev_err_probe(dev, PTR_ERR(upstream), "unable to claim upstream GPIO line\n");
++
++	cas->upstream_line = upstream;
++	cas->parent = dev;
++
++	gc = &cas->gpio_chip;
++	gc->get = gpio_cascade_get_value;
++	gc->get_direction = gpio_cascade_get_direction;
++	gc->base = -1;
++	gc->ngpio = mux_control_states(mc);
++	gc->label = dev_name(cas->parent);
++	gc->parent = cas->parent;
++	gc->owner = THIS_MODULE;
++
++	platform_set_drvdata(pdev, cas);
++	return devm_gpiochip_add_data(dev, &cas->gpio_chip, NULL);
++}
++
++static const struct of_device_id gpio_cascade_id[] = {
++	{ .compatible = "gpio-cascade" },
++	{ /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, gpio_cascade_id);
++
++static struct platform_driver gpio_cascade_driver = {
++	.driver	= {
++		.name		= "gpio-cascade",
++		.of_match_table = gpio_cascade_id,
++	},
++	.probe	= gpio_cascade_probe,
++};
++module_platform_driver(gpio_cascade_driver);
++
++MODULE_AUTHOR("Mauri Sandberg <maukka@ext.kapsi.fi>");
++MODULE_DESCRIPTION("Generic GPIO cascade");
++MODULE_LICENSE("GPL");
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0078-debloat-add-kernel-config-option-to-disabling-common.patch b/nixos/modules/kernel-patches/0078-debloat-add-kernel-config-option-to-disabling-common.patch
new file mode 100644
index 0000000..02d8db0
--- /dev/null
+++ b/nixos/modules/kernel-patches/0078-debloat-add-kernel-config-option-to-disabling-common.patch
@@ -0,0 +1,72 @@
+From 3cc791c22083c2e426fd5a722069c2e6d81f7ee1 Mon Sep 17 00:00:00 2001
+From: Gabor Juhos <juhosg@openwrt.org>
+Date: Tue, 27 Sep 2022 16:22:10 +0200
+Subject: [PATCH 78/96] debloat: add kernel config option to disabling common
+ PCI quirks
+
+Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
+---
+ drivers/pci/Kconfig  | 7 +++++++
+ drivers/pci/quirks.c | 7 +++++++
+ 2 files changed, 14 insertions(+)
+
+diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
+index 133c73207782..70e2d7679199 100644
+--- a/drivers/pci/Kconfig
++++ b/drivers/pci/Kconfig
+@@ -118,6 +118,13 @@ config XEN_PCIDEV_FRONTEND
+ 	  The PCI device frontend driver allows the kernel to import arbitrary
+ 	  PCI devices from a PCI backend to support PCI driver domains.
+ 
++config PCI_DISABLE_COMMON_QUIRKS
++	bool "PCI disable common quirks"
++	depends on PCI
++	help
++	  If you don't know what to do here, say N.
++
++
+ config PCI_ATS
+ 	bool
+ 
+diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
+index 2e68f50bc7ae..df09caf0dc8d 100644
+--- a/drivers/pci/quirks.c
++++ b/drivers/pci/quirks.c
+@@ -207,6 +207,7 @@ static void quirk_mmio_always_on(struct pci_dev *dev)
+ DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_ANY_ID, PCI_ANY_ID,
+ 				PCI_CLASS_BRIDGE_HOST, 8, quirk_mmio_always_on);
+ 
++#ifndef CONFIG_PCI_DISABLE_COMMON_QUIRKS
+ /*
+  * The Mellanox Tavor device gives false positive parity errors.  Disable
+  * parity error reporting.
+@@ -3364,6 +3365,8 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x65f8, quirk_intel_mc_errata);
+ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x65f9, quirk_intel_mc_errata);
+ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x65fa, quirk_intel_mc_errata);
+ 
++#endif /* !CONFIG_PCI_DISABLE_COMMON_QUIRKS */
++
+ /*
+  * Ivytown NTB BAR sizes are misreported by the hardware due to an erratum.
+  * To work around this, query the size it should be configured to by the
+@@ -3389,6 +3392,8 @@ static void quirk_intel_ntb(struct pci_dev *dev)
+ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x0e08, quirk_intel_ntb);
+ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x0e0d, quirk_intel_ntb);
+ 
++#ifndef CONFIG_PCI_DISABLE_COMMON_QUIRKS
++
+ /*
+  * Some BIOS implementations leave the Intel GPU interrupts enabled, even
+  * though no one is handling them (e.g., if the i915 driver is never
+@@ -3427,6 +3432,8 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x0106, disable_igfx_irq);
+ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x010a, disable_igfx_irq);
+ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x0152, disable_igfx_irq);
+ 
++#endif /* !CONFIG_PCI_DISABLE_COMMON_QUIRKS */
++
+ /*
+  * PCI devices which are on Intel chips can skip the 10ms delay
+  * before entering D3 mode.
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0079-debloat-disable-common-USB-quirks.patch b/nixos/modules/kernel-patches/0079-debloat-disable-common-USB-quirks.patch
new file mode 100644
index 0000000..9daac66
--- /dev/null
+++ b/nixos/modules/kernel-patches/0079-debloat-disable-common-USB-quirks.patch
@@ -0,0 +1,126 @@
+From 87b1826cbf97eebbb5874c9b1963f4da7fa09d06 Mon Sep 17 00:00:00 2001
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 27 Sep 2022 16:22:11 +0200
+Subject: [PATCH 79/96] debloat: disable common USB quirks
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ drivers/usb/host/pci-quirks.c | 16 ++++++++++++++++
+ drivers/usb/host/pci-quirks.h | 20 ++++++++++++++++++++
+ include/linux/usb/hcd.h       |  7 +++++++
+ 3 files changed, 43 insertions(+)
+
+diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c
+index ef08d68b9714..48c7475b3f71 100644
+--- a/drivers/usb/host/pci-quirks.c
++++ b/drivers/usb/host/pci-quirks.c
+@@ -128,6 +128,8 @@ struct amd_chipset_type {
+ 	u8 rev;
+ };
+ 
++#ifndef CONFIG_PCI_DISABLE_COMMON_QUIRKS
++
+ static struct amd_chipset_info {
+ 	struct pci_dev	*nb_dev;
+ 	struct pci_dev	*smbus_dev;
+@@ -633,6 +635,10 @@ bool usb_amd_pt_check_port(struct device *device, int port)
+ }
+ EXPORT_SYMBOL_GPL(usb_amd_pt_check_port);
+ 
++#endif /* CONFIG_PCI_DISABLE_COMMON_QUIRKS */
++
++#if IS_ENABLED(CONFIG_USB_UHCI_HCD)
++
+ /*
+  * Make sure the controller is completely inactive, unable to
+  * generate interrupts or do DMA.
+@@ -712,8 +718,17 @@ int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base)
+ 	uhci_reset_hc(pdev, base);
+ 	return 1;
+ }
++#else
++int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base)
++{
++	return 0;
++}
++
++#endif
+ EXPORT_SYMBOL_GPL(uhci_check_and_reset_hc);
+ 
++#ifndef CONFIG_PCI_DISABLE_COMMON_QUIRKS
++
+ static inline int io_type_enabled(struct pci_dev *pdev, unsigned int mask)
+ {
+ 	u16 cmd;
+@@ -1285,3 +1300,4 @@ static void quirk_usb_early_handoff(struct pci_dev *pdev)
+ }
+ DECLARE_PCI_FIXUP_CLASS_FINAL(PCI_ANY_ID, PCI_ANY_ID,
+ 			PCI_CLASS_SERIAL_USB, 8, quirk_usb_early_handoff);
++#endif
+diff --git a/drivers/usb/host/pci-quirks.h b/drivers/usb/host/pci-quirks.h
+index e729de21fad7..1963d3ff63f1 100644
+--- a/drivers/usb/host/pci-quirks.h
++++ b/drivers/usb/host/pci-quirks.h
+@@ -5,6 +5,9 @@
+ #ifdef CONFIG_USB_PCI
+ void uhci_reset_hc(struct pci_dev *pdev, unsigned long base);
+ int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base);
++#endif  /* CONFIG_USB_PCI */
++
++#if defined(CONFIG_USB_PCI) && !defined(CONFIG_PCI_DISABLE_COMMON_QUIRKS)
+ int usb_hcd_amd_remote_wakeup_quirk(struct pci_dev *pdev);
+ bool usb_amd_hang_symptom_quirk(void);
+ bool usb_amd_prefetch_quirk(void);
+@@ -19,6 +22,18 @@ void sb800_prefetch(struct device *dev, int on);
+ bool usb_amd_pt_check_port(struct device *device, int port);
+ #else
+ struct pci_dev;
++static inline int usb_amd_quirk_pll_check(void)
++{
++	return 0;
++}
++static inline bool usb_amd_hang_symptom_quirk(void)
++{
++	return false;
++}
++static inline bool usb_amd_prefetch_quirk(void)
++{
++	return false;
++}
+ static inline void usb_amd_quirk_pll_disable(void) {}
+ static inline void usb_amd_quirk_pll_enable(void) {}
+ static inline void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev) {}
+@@ -29,6 +44,11 @@ static inline bool usb_amd_pt_check_port(struct device *device, int port)
+ {
+ 	return false;
+ }
++static inline void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev) {}
++static inline bool usb_xhci_needs_pci_reset(struct pci_dev *pdev)
++{
++	return false;
++}
+ #endif  /* CONFIG_USB_PCI */
+ 
+ #endif  /*  __LINUX_USB_PCI_QUIRKS_H  */
+diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h
+index 98d1921f02b1..aef3bf66d939 100644
+--- a/include/linux/usb/hcd.h
++++ b/include/linux/usb/hcd.h
+@@ -498,7 +498,14 @@ extern int usb_hcd_pci_probe(struct pci_dev *dev,
+ extern void usb_hcd_pci_remove(struct pci_dev *dev);
+ extern void usb_hcd_pci_shutdown(struct pci_dev *dev);
+ 
++#ifndef CONFIG_PCI_DISABLE_COMMON_QUIRKS
+ extern int usb_hcd_amd_remote_wakeup_quirk(struct pci_dev *dev);
++#else
++static inline int usb_hcd_amd_remote_wakeup_quirk(struct pci_dev *dev)
++{
++	return 0;
++}
++#endif
+ 
+ #ifdef CONFIG_PM
+ extern const struct dev_pm_ops usb_hcd_pci_pm_ops;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0080-w1-gpio-fix-problem-with-platfom-data-in-w1-gpio.patch b/nixos/modules/kernel-patches/0080-w1-gpio-fix-problem-with-platfom-data-in-w1-gpio.patch
new file mode 100644
index 0000000..4cab0e8
--- /dev/null
+++ b/nixos/modules/kernel-patches/0080-w1-gpio-fix-problem-with-platfom-data-in-w1-gpio.patch
@@ -0,0 +1,31 @@
+From a76012fae1d37495766ba8bde926aaa3e861d03a Mon Sep 17 00:00:00 2001
+From: Pawel Dembicki <paweldembicki@gmail.com>
+Date: Sun, 18 Feb 2018 17:08:04 +0100
+Subject: [PATCH 80/96] w1: gpio: fix problem with platfom data in w1-gpio
+
+In devices, where fdt is used, is impossible to apply platform data
+without proper fdt node.
+
+This patch allow to use platform data in devices with fdt.
+
+Signed-off-by: Pawel Dembicki <paweldembicki@gmail.com>
+---
+ drivers/w1/masters/w1-gpio.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c
+index d4632aace402..dc9eb99bf48d 100644
+--- a/drivers/w1/masters/w1-gpio.c
++++ b/drivers/w1/masters/w1-gpio.c
+@@ -76,7 +76,7 @@ static int w1_gpio_probe(struct platform_device *pdev)
+ 	enum gpiod_flags gflags = GPIOD_OUT_LOW_OPEN_DRAIN;
+ 	int err;
+ 
+-	if (of_have_populated_dt()) {
++	if (of_have_populated_dt() && !dev_get_platdata(&pdev->dev)) {
+ 		pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ 		if (!pdata)
+ 			return -ENOMEM;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0081-hwrng-bcm2835-set-quality-to-1000.patch b/nixos/modules/kernel-patches/0081-hwrng-bcm2835-set-quality-to-1000.patch
new file mode 100644
index 0000000..243255c
--- /dev/null
+++ b/nixos/modules/kernel-patches/0081-hwrng-bcm2835-set-quality-to-1000.patch
@@ -0,0 +1,31 @@
+From bd3b6ea1d7d04215943564bb5a9ea0e7baa6da9f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= <noltari@gmail.com>
+Date: Sat, 20 Feb 2021 18:36:38 +0100
+Subject: [PATCH 81/96] hwrng: bcm2835: set quality to 1000
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This allows devices without a high precission timer to reduce boot from >100s
+to <30s.
+
+Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+---
+ drivers/char/hw_random/bcm2835-rng.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/drivers/char/hw_random/bcm2835-rng.c b/drivers/char/hw_random/bcm2835-rng.c
+index e7dd457e9b22..9caf88e614d3 100644
+--- a/drivers/char/hw_random/bcm2835-rng.c
++++ b/drivers/char/hw_random/bcm2835-rng.c
+@@ -170,6 +170,7 @@ static int bcm2835_rng_probe(struct platform_device *pdev)
+ 	priv->rng.init = bcm2835_rng_init;
+ 	priv->rng.read = bcm2835_rng_read;
+ 	priv->rng.cleanup = bcm2835_rng_cleanup;
++	priv->rng.quality = 1000;
+ 
+ 	if (dev_of_node(dev)) {
+ 		rng_id = of_match_node(bcm2835_rng_of_match, dev->of_node);
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0082-PCI-aardvark-Make-main-irq_chip-structure-a-static-d.patch b/nixos/modules/kernel-patches/0082-PCI-aardvark-Make-main-irq_chip-structure-a-static-d.patch
new file mode 100644
index 0000000..0df6293
--- /dev/null
+++ b/nixos/modules/kernel-patches/0082-PCI-aardvark-Make-main-irq_chip-structure-a-static-d.patch
@@ -0,0 +1,107 @@
+From 28a50502f73ca557d714bfd37e9d01bd50177dd1 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Mon, 10 Jan 2022 02:02:00 +0100
+Subject: [PATCH 82/96] PCI: aardvark: Make main irq_chip structure a static
+ driver structure
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Marc Zyngier says [1] that we should use struct irq_chip as a global
+static struct in the driver. Even though the structure currently
+contains a dynamic member (parent_device), Marc says [2] that he plans
+to kill it and make the structure completely static.
+
+We have already converted others irq_chip structures in this driver in
+this way, but we omitted this one because the .name member is
+dynamically created from device's name, and the name is displayed in
+sysfs, so changing it would break sysfs ABI.
+
+The rationale for changing the name (to "advk-INT") in spite of sysfs
+ABI, and thus allowing to convert to a static structure, is that after
+the other changes we made in this series, the IRQ chip is basically
+something different: it no logner generates ERR and PME interrupts (they
+are generated by emulated bridge's rp_irq_chip).
+
+[1] https://lore.kernel.org/linux-pci/877dbcvngf.wl-maz@kernel.org/
+[2] https://lore.kernel.org/linux-pci/874k6gvkhz.wl-maz@kernel.org/
+
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/pci/controller/pci-aardvark.c | 25 +++++++------------------
+ 1 file changed, 7 insertions(+), 18 deletions(-)
+
+diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
+index 93e2e0234df6..a840788d9f1e 100644
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -280,7 +280,6 @@ struct advk_pcie {
+ 	u8 wins_count;
+ 	struct irq_domain *rp_irq_domain;
+ 	struct irq_domain *irq_domain;
+-	struct irq_chip irq_chip;
+ 	raw_spinlock_t irq_lock;
+ 	struct irq_domain *msi_domain;
+ 	struct irq_domain *msi_inner_domain;
+@@ -1496,14 +1495,19 @@ static void advk_pcie_irq_unmask(struct irq_data *d)
+ 	raw_spin_unlock_irqrestore(&pcie->irq_lock, flags);
+ }
+ 
++static struct irq_chip advk_irq_chip = {
++	.name		= "advk-INT",
++	.irq_mask	= advk_pcie_irq_mask,
++	.irq_unmask	= advk_pcie_irq_unmask,
++};
++
+ static int advk_pcie_irq_map(struct irq_domain *h,
+ 			     unsigned int virq, irq_hw_number_t hwirq)
+ {
+ 	struct advk_pcie *pcie = h->host_data;
+ 
+ 	irq_set_status_flags(virq, IRQ_LEVEL);
+-	irq_set_chip_and_handler(virq, &pcie->irq_chip,
+-				 handle_level_irq);
++	irq_set_chip_and_handler(virq, &advk_irq_chip, handle_level_irq);
+ 	irq_set_chip_data(virq, pcie);
+ 
+ 	return 0;
+@@ -1562,7 +1566,6 @@ static int advk_pcie_init_irq_domain(struct advk_pcie *pcie)
+ 	struct device *dev = &pcie->pdev->dev;
+ 	struct device_node *node = dev->of_node;
+ 	struct device_node *pcie_intc_node;
+-	struct irq_chip *irq_chip;
+ 	int ret = 0;
+ 
+ 	raw_spin_lock_init(&pcie->irq_lock);
+@@ -1573,28 +1576,14 @@ static int advk_pcie_init_irq_domain(struct advk_pcie *pcie)
+ 		return -ENODEV;
+ 	}
+ 
+-	irq_chip = &pcie->irq_chip;
+-
+-	irq_chip->name = devm_kasprintf(dev, GFP_KERNEL, "%s-irq",
+-					dev_name(dev));
+-	if (!irq_chip->name) {
+-		ret = -ENOMEM;
+-		goto out_put_node;
+-	}
+-
+-	irq_chip->irq_mask = advk_pcie_irq_mask;
+-	irq_chip->irq_unmask = advk_pcie_irq_unmask;
+-
+ 	pcie->irq_domain =
+ 		irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX,
+ 				      &advk_pcie_irq_domain_ops, pcie);
+ 	if (!pcie->irq_domain) {
+ 		dev_err(dev, "Failed to get a INTx IRQ domain\n");
+ 		ret = -ENOMEM;
+-		goto out_put_node;
+ 	}
+ 
+-out_put_node:
+ 	of_node_put(pcie_intc_node);
+ 	return ret;
+ }
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0083-init-add-CONFIG_MANGLE_BOOTARGS-and-disable-it-by-de.patch b/nixos/modules/kernel-patches/0083-init-add-CONFIG_MANGLE_BOOTARGS-and-disable-it-by-de.patch
new file mode 100644
index 0000000..fe968e2
--- /dev/null
+++ b/nixos/modules/kernel-patches/0083-init-add-CONFIG_MANGLE_BOOTARGS-and-disable-it-by-de.patch
@@ -0,0 +1,81 @@
+From c5e478e7bb7bba5de4f9859d93a7422916aaea28 Mon Sep 17 00:00:00 2001
+From: Imre Kaloz <kaloz@openwrt.org>
+Date: Tue, 27 Sep 2022 16:22:32 +0200
+Subject: [PATCH 83/96] init: add CONFIG_MANGLE_BOOTARGS and disable it by
+ default
+
+Enabling this option renames the bootloader supplied root=
+and rootfstype= variables, which might have to be know but
+would break the automatisms OpenWrt uses.
+
+Signed-off-by: Imre Kaloz <kaloz@openwrt.org>
+---
+ init/Kconfig |  9 +++++++++
+ init/main.c  | 24 ++++++++++++++++++++++++
+ 2 files changed, 33 insertions(+)
+
+diff --git a/init/Kconfig b/init/Kconfig
+index 4d5f1ee66139..04e232e38fe4 100644
+--- a/init/Kconfig
++++ b/init/Kconfig
+@@ -1818,6 +1818,15 @@ config EMBEDDED
+ 	  an embedded system so certain expert options are available
+ 	  for configuration.
+ 
++config MANGLE_BOOTARGS
++	bool "Rename offending bootargs"
++	depends on EXPERT
++	help
++	  Sometimes the bootloader passed bogus root= and rootfstype=
++	  parameters to the kernel, and while you want to ignore them,
++	  you need to know the values f.e. to support dual firmware
++	  layouts on the flash.
++
+ config HAVE_PERF_EVENTS
+ 	bool
+ 	help
+diff --git a/init/main.c b/init/main.c
+index 1fe7942f5d4a..12b821626826 100644
+--- a/init/main.c
++++ b/init/main.c
+@@ -607,6 +607,29 @@ static inline void setup_nr_cpu_ids(void) { }
+ static inline void smp_prepare_cpus(unsigned int maxcpus) { }
+ #endif
+ 
++#ifdef CONFIG_MANGLE_BOOTARGS
++static void __init mangle_bootargs(char *command_line)
++{
++	char *rootdev;
++	char *rootfs;
++
++	rootdev = strstr(command_line, "root=/dev/mtdblock");
++
++	if (rootdev)
++		strncpy(rootdev, "mangled_rootblock=", 18);
++
++	rootfs = strstr(command_line, "rootfstype");
++
++	if (rootfs)
++		strncpy(rootfs, "mangled_fs", 10);
++
++}
++#else
++static void __init mangle_bootargs(char *command_line)
++{
++}
++#endif
++
+ /*
+  * We need to store the untouched command line for future reference.
+  * We also need to store the touched command line since the parameter
+@@ -950,6 +973,7 @@ asmlinkage __visible void __init __no_sanitize_address start_kernel(void)
+ 	pr_notice("%s", linux_banner);
+ 	early_security_init();
+ 	setup_arch(&command_line);
++	mangle_bootargs(command_line);
+ 	setup_boot_config();
+ 	setup_command_line(command_line);
+ 	setup_nr_cpu_ids();
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0084-ARM-dts-armada-385.dtsi-Add-definitions-for-PCIe-err.patch b/nixos/modules/kernel-patches/0084-ARM-dts-armada-385.dtsi-Add-definitions-for-PCIe-err.patch
new file mode 100644
index 0000000..29cccc6
--- /dev/null
+++ b/nixos/modules/kernel-patches/0084-ARM-dts-armada-385.dtsi-Add-definitions-for-PCIe-err.patch
@@ -0,0 +1,71 @@
+From 28d362fecd85d14c996f313af2fba5a6d81fd08d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Mon, 18 Apr 2022 00:40:05 +0200
+Subject: [PATCH 84/96] ARM: dts: armada-385.dtsi: Add definitions for PCIe
+ error interrupts
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+PCIe error interrupt is reported by MPIC SoC Error IRQ controller.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ arch/arm/boot/dts/armada-385.dtsi | 20 ++++++++++++--------
+ 1 file changed, 12 insertions(+), 8 deletions(-)
+
+diff --git a/arch/arm/boot/dts/armada-385.dtsi b/arch/arm/boot/dts/armada-385.dtsi
+index be8d607c59b2..d603de5aa574 100644
+--- a/arch/arm/boot/dts/armada-385.dtsi
++++ b/arch/arm/boot/dts/armada-385.dtsi
+@@ -69,8 +69,9 @@ pcie1: pcie@1,0 {
+ 				reg = <0x0800 0 0 0 0>;
+ 				#address-cells = <3>;
+ 				#size-cells = <2>;
+-				interrupt-names = "intx";
+-				interrupts-extended = <&gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
++				interrupt-names = "intx", "error";
++				interrupts-extended = <&gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>,
++						      <&soc_err 4>;
+ 				#interrupt-cells = <1>;
+ 				ranges = <0x82000000 0 0 0x82000000 0x1 0 1 0
+ 					  0x81000000 0 0 0x81000000 0x1 0 1 0>;
+@@ -97,8 +98,9 @@ pcie2: pcie@2,0 {
+ 				reg = <0x1000 0 0 0 0>;
+ 				#address-cells = <3>;
+ 				#size-cells = <2>;
+-				interrupt-names = "intx";
+-				interrupts-extended = <&gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
++				interrupt-names = "intx", "error";
++				interrupts-extended = <&gic GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>,
++						      <&soc_err 5>;
+ 				#interrupt-cells = <1>;
+ 				ranges = <0x82000000 0 0 0x82000000 0x2 0 1 0
+ 					  0x81000000 0 0 0x81000000 0x2 0 1 0>;
+@@ -125,8 +127,9 @@ pcie3: pcie@3,0 {
+ 				reg = <0x1800 0 0 0 0>;
+ 				#address-cells = <3>;
+ 				#size-cells = <2>;
+-				interrupt-names = "intx";
+-				interrupts-extended = <&gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
++				interrupt-names = "intx", "error";
++				interrupts-extended = <&gic GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>,
++						      <&soc_err 15>;
+ 				#interrupt-cells = <1>;
+ 				ranges = <0x82000000 0 0 0x82000000 0x3 0 1 0
+ 					  0x81000000 0 0 0x81000000 0x3 0 1 0>;
+@@ -156,8 +159,9 @@ pcie4: pcie@4,0 {
+ 				reg = <0x2000 0 0 0 0>;
+ 				#address-cells = <3>;
+ 				#size-cells = <2>;
+-				interrupt-names = "intx";
+-				interrupts-extended = <&gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
++				interrupt-names = "intx", "error";
++				interrupts-extended = <&gic GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>,
++						      <&soc_err 16>;
+ 				#interrupt-cells = <1>;
+ 				ranges = <0x82000000 0 0 0x82000000 0x4 0 1 0
+ 					  0x81000000 0 0 0x81000000 0x4 0 1 0>;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0085-PCI-aardvark-Implement-workaround-for-PCIe-Completio.patch b/nixos/modules/kernel-patches/0085-PCI-aardvark-Implement-workaround-for-PCIe-Completio.patch
new file mode 100644
index 0000000..e645f7d
--- /dev/null
+++ b/nixos/modules/kernel-patches/0085-PCI-aardvark-Implement-workaround-for-PCIe-Completio.patch
@@ -0,0 +1,84 @@
+From 576b43cfbfc54db18bee2ced8c1df1536c2f002d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Wed, 31 Aug 2022 19:44:08 +0200
+Subject: [PATCH 85/96] PCI: aardvark: Implement workaround for PCIe Completion
+ Timeout
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Marvell Armada 3700 Functional Errata, Guidelines, and Restrictions
+document describes in erratum 3.12 PCIe Completion Timeout (Ref #: 251),
+that PCIe IP does not support a strong-ordered model for inbound posted vs.
+outbound completion.
+
+As a workaround for this erratum, DIS_ORD_CHK flag in Debug Mux Control
+register must be set. It disables the ordering check in the core between
+Completions and Posted requests received from the link.
+
+Marvell also suggests to do full memory barrier at the beginning of
+aardvark summary interrupt handler before calling interrupt handlers of
+endpoint drivers in order to minimize the risk for the race condition
+documented in the Erratum between the DMA done status reading and the
+completion of writing to the host memory.
+
+More details about this issue and suggested workarounds are in discussion:
+https://lore.kernel.org/linux-pci/BN9PR18MB425154FE5019DCAF2028A1D5DB8D9@BN9PR18MB4251.namprd18.prod.outlook.com/t/#u
+
+It was reported that enabling this workaround fixes instability issues and
+"Unhandled fault" errors when using 60 GHz WiFi 802.11ad card with Qualcomm
+QCA6335 chip under significant load which were caused by interrupt status
+stuck in the outbound CMPLT queue traced back to this erratum.
+
+This workaround fixes also kernel panic triggered after some minutes of
+usage 5 GHz WiFi 802.11ax card with Mediatek MT7915 chip:
+
+    Internal error: synchronous external abort: 96000210 [#1] SMP
+    Kernel panic - not syncing: Fatal exception in interrupt
+
+Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
+Signed-off-by: Pali Rohár <pali@kernel.org>
+Fixes: 8c39d710363c ("PCI: aardvark: Add Aardvark PCI host controller driver")
+Cc: stable@vger.kernel.org
+---
+ drivers/pci/controller/pci-aardvark.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
+index a840788d9f1e..dfb11622c568 100644
+--- a/drivers/pci/controller/pci-aardvark.c
++++ b/drivers/pci/controller/pci-aardvark.c
+@@ -210,6 +210,8 @@ enum {
+ };
+ 
+ #define VENDOR_ID_REG				(LMI_BASE_ADDR + 0x44)
++#define DEBUG_MUX_CTRL_REG			(LMI_BASE_ADDR + 0x208)
++#define     DIS_ORD_CHK				BIT(30)
+ #define PME_MSG_GEN_CTRL			(LMI_BASE_ADDR + 0x220)
+ #define     SEND_SET_SLOT_POWER_LIMIT		BIT(13)
+ #define     SEND_PME_TURN_OFF			BIT(14)
+@@ -603,6 +605,11 @@ static void advk_pcie_setup_hw(struct advk_pcie *pcie)
+ 		PCIE_CORE_CTRL2_TD_ENABLE;
+ 	advk_writel(pcie, reg, PCIE_CORE_CTRL2_REG);
+ 
++	/* Disable ordering checks, workaround for erratum 3.12 "PCIe completion timeout" */
++	reg = advk_readl(pcie, DEBUG_MUX_CTRL_REG);
++	reg |= DIS_ORD_CHK;
++	advk_writel(pcie, reg, DEBUG_MUX_CTRL_REG);
++
+ 	/* Set lane X1 */
+ 	reg = advk_readl(pcie, PCIE_CORE_CTRL0_REG);
+ 	reg &= ~LANE_CNT_MSK;
+@@ -1783,6 +1790,9 @@ static irqreturn_t advk_pcie_irq_handler(int irq, void *arg)
+ 	struct advk_pcie *pcie = arg;
+ 	u32 status;
+ 
++	/* Full memory barrier (ARM dsb sy), workaround for erratum 3.12 "PCIe completion timeout" */
++	mb();
++
+ 	status = advk_readl(pcie, HOST_CTRL_INT_STATUS_REG);
+ 	if (!(status & PCIE_IRQ_CORE_INT))
+ 		return IRQ_NONE;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0086-ARM-dts-turris-omnia-configure-LED-0-pin-function-to.patch b/nixos/modules/kernel-patches/0086-ARM-dts-turris-omnia-configure-LED-0-pin-function-to.patch
new file mode 100644
index 0000000..08e81e0
--- /dev/null
+++ b/nixos/modules/kernel-patches/0086-ARM-dts-turris-omnia-configure-LED-0-pin-function-to.patch
@@ -0,0 +1,43 @@
+From 4e5edff1191eac21bb3a15eecef0fa1a2e5a0e33 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Mon, 4 Jul 2022 13:36:21 +0200
+Subject: [PATCH 86/96] ARM: dts: turris-omnia: configure LED[0] pin function
+ to link/activity
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The marvell PHY driver changes the LED[0] pin function to "On - 1000
+Mbps Link, Off - Else".
+
+Turris Omnia expects that the function is "On - Link, Blink - Activity,
+Off - No link".
+
+Use the `marvell,reg-init` DT property to change the function.
+
+In the future, once netdev trigger will support HW offloading, we will
+be able to have this configured via the combination of PHY driver and
+leds-turris-omnia driver.
+
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ arch/arm/boot/dts/armada-385-turris-omnia.dts | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/arch/arm/boot/dts/armada-385-turris-omnia.dts b/arch/arm/boot/dts/armada-385-turris-omnia.dts
+index f4878df39753..1583c4b14ae8 100644
+--- a/arch/arm/boot/dts/armada-385-turris-omnia.dts
++++ b/arch/arm/boot/dts/armada-385-turris-omnia.dts
+@@ -397,7 +397,8 @@ &mdio {
+ 	phy1: ethernet-phy@1 {
+ 		compatible = "ethernet-phy-ieee802.3-c22";
+ 		reg = <1>;
+-		marvell,reg-init = <3 18 0 0x4985>;
++		marvell,reg-init = <3 18 0 0x4985>,
++				   <3 16 0xfff0 0x0001>;
+ 
+ 		/* irq is connected to &pcawan pin 7 */
+ 	};
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0087-ARM-dts-turris-omnia-enable-LED-controller-node.patch b/nixos/modules/kernel-patches/0087-ARM-dts-turris-omnia-enable-LED-controller-node.patch
new file mode 100644
index 0000000..c90a022
--- /dev/null
+++ b/nixos/modules/kernel-patches/0087-ARM-dts-turris-omnia-enable-LED-controller-node.patch
@@ -0,0 +1,53 @@
+From a18b8bf331feec7ad09b3d20c5de9fae7a9e210c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Mon, 4 Jul 2022 13:36:22 +0200
+Subject: [PATCH 87/96] ARM: dts: turris-omnia: enable LED controller node
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The LED controller node is disabled because the leds-turris-omnia driver
+does not support setting the LED blinking to be controlled by the MCU.
+
+The patches for that have now been sent [1], so let's enable the node.
+
+[1] https://lore.kernel.org/linux-leds/20220704105955.15474-1-kabel@kernel.org/T/
+
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ arch/arm/boot/dts/armada-385-turris-omnia.dts | 7 ++-----
+ 1 file changed, 2 insertions(+), 5 deletions(-)
+
+diff --git a/arch/arm/boot/dts/armada-385-turris-omnia.dts b/arch/arm/boot/dts/armada-385-turris-omnia.dts
+index 1583c4b14ae8..d1e0db6e5730 100644
+--- a/arch/arm/boot/dts/armada-385-turris-omnia.dts
++++ b/arch/arm/boot/dts/armada-385-turris-omnia.dts
+@@ -191,15 +191,13 @@ led-controller@2b {
+ 				reg = <0x2b>;
+ 				#address-cells = <1>;
+ 				#size-cells = <0>;
++				status = "okay";
+ 
+ 				/*
+ 				 * LEDs are controlled by MCU (STM32F0) at
+ 				 * address 0x2b.
+ 				 *
+-				 * The driver does not support HW control mode
+-				 * for the LEDs yet. Disable the LEDs for now.
+-				 *
+-				 * Also LED functions are not stable yet:
++				 * LED functions are not stable yet:
+ 				 * - there are 3 LEDs connected via MCU to PCIe
+ 				 *   ports. One of these ports supports mSATA.
+ 				 *   There is no mSATA nor PCIe function.
+@@ -210,7 +208,6 @@ led-controller@2b {
+ 				 *   B. Again there is no such function defined.
+ 				 *   For now we use LED_FUNCTION_INDICATOR
+ 				 */
+-				status = "disabled";
+ 
+ 				multi-led@0 {
+ 					reg = <0x0>;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0088-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch b/nixos/modules/kernel-patches/0088-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch
new file mode 100644
index 0000000..e6e8685
--- /dev/null
+++ b/nixos/modules/kernel-patches/0088-leds-turris-omnia-support-HW-controlled-mode-via-pri.patch
@@ -0,0 +1,125 @@
+From d7234c39e0977c8b6a093f88997bd3ed954ae045 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Mon, 4 Jul 2022 12:59:53 +0200
+Subject: [PATCH 88/96] leds: turris-omnia: support HW controlled mode via
+ private trigger
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add support for enabling MCU controlled mode of the Turris Omnia LEDs
+via a LED private trigger called "omnia-mcu".
+
+When in MCU controlled mode, the user can still set LED color, but the
+blinking is done by MCU, which does different things for various LEDs:
+- WAN LED is blinked according to the LED[0] pin of the WAN PHY
+- LAN LEDs are blinked according to the LED[0] output of corresponding
+  port of the LAN switch
+- PCIe LEDs are blinked according to the logical OR of the MiniPCIe port
+  LED pins
+
+For a long time I wanted to actually do this differently: I wanted to
+make the netdev trigger to transparently offload the blinking to the HW
+if user set compatible settings for the netdev trigger.
+There was some work on this, and hopefully we will be able to complete
+it sometime, but since there are various complications, it will probably
+not be soon.
+
+In the meantime let's support HW controlled mode via this private LED
+trigger. If, in the future, we manage to complete the netdev trigger
+offloading, we can still keep this private trigger for backwards
+compatiblity, if needed.
+
+We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps
+control until the user first wants to take over it. If a different
+default trigger is specified in device-tree via the
+`linux,default-trigger` property, LED class will overwrite
+cdev->default_trigger, and so the DT property will be respected.
+
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/leds/Kconfig             |  1 +
+ drivers/leds/leds-turris-omnia.c | 41 ++++++++++++++++++++++++++++++++
+ 2 files changed, 42 insertions(+)
+
+diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
+index a49979f41eee..bc148320416d 100644
+--- a/drivers/leds/Kconfig
++++ b/drivers/leds/Kconfig
+@@ -163,6 +163,7 @@ config LEDS_TURRIS_OMNIA
+ 	depends on I2C
+ 	depends on MACH_ARMADA_38X || COMPILE_TEST
+ 	depends on OF
++	select LEDS_TRIGGERS
+ 	help
+ 	  This option enables basic support for the LEDs found on the front
+ 	  side of CZ.NIC's Turris Omnia router. There are 12 RGB LEDs on the
+diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c
+index 1adfed1c0619..c2dfb22d3065 100644
+--- a/drivers/leds/leds-turris-omnia.c
++++ b/drivers/leds/leds-turris-omnia.c
+@@ -41,6 +41,39 @@ struct omnia_leds {
+ 	struct omnia_led leds[];
+ };
+ 
++static struct led_hw_trigger_type omnia_hw_trigger_type;
++
++static int omnia_hwtrig_activate(struct led_classdev *cdev)
++{
++	struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
++	struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev));
++
++	/* put the LED into MCU controlled mode */
++	return i2c_smbus_write_byte_data(leds->client, CMD_LED_MODE,
++					 CMD_LED_MODE_LED(led->reg));
++}
++
++static void omnia_hwtrig_deactivate(struct led_classdev *cdev)
++{
++	struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
++	struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev));
++	int ret;
++
++	/* put the LED into software mode */
++	ret = i2c_smbus_write_byte_data(leds->client, CMD_LED_MODE,
++					CMD_LED_MODE_LED(led->reg) |
++					CMD_LED_MODE_USER);
++	if (ret < 0)
++		dev_err(cdev->dev, "Cannot put to software mode: %i\n", ret);
++}
++
++static struct led_trigger omnia_hw_trigger = {
++	.name		= "omnia-mcu",
++	.activate	= omnia_hwtrig_activate,
++	.deactivate	= omnia_hwtrig_deactivate,
++	.trigger_type	= &omnia_hw_trigger_type,
++};
++
+ static int omnia_led_brightness_set_blocking(struct led_classdev *cdev,
+ 					     enum led_brightness brightness)
+ {
+@@ -112,6 +145,8 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
+ 	cdev = &led->mc_cdev.led_cdev;
+ 	cdev->max_brightness = 255;
+ 	cdev->brightness_set_blocking = omnia_led_brightness_set_blocking;
++	cdev->trigger_type = &omnia_hw_trigger_type;
++	cdev->default_trigger = omnia_hw_trigger.name;
+ 
+ 	/* put the LED into software mode */
+ 	ret = i2c_smbus_write_byte_data(client, CMD_LED_MODE,
+@@ -228,6 +263,12 @@ static int omnia_leds_probe(struct i2c_client *client,
+ 
+ 	mutex_init(&leds->lock);
+ 
++	ret = devm_led_trigger_register(dev, &omnia_hw_trigger);
++	if (ret < 0) {
++		dev_err(dev, "Cannot register private LED trigger: %d\n", ret);
++		return ret;
++	}
++
+ 	led = &leds->leds[0];
+ 	for_each_available_child_of_node(np, child) {
+ 		ret = omnia_led_register(client, led, child);
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0089-leds-turris-omnia-initialize-multi-intensity-to-full.patch b/nixos/modules/kernel-patches/0089-leds-turris-omnia-initialize-multi-intensity-to-full.patch
new file mode 100644
index 0000000..8ca4501
--- /dev/null
+++ b/nixos/modules/kernel-patches/0089-leds-turris-omnia-initialize-multi-intensity-to-full.patch
@@ -0,0 +1,38 @@
+From 647dfa4c8eb2b4186a46dbc8791b116e826bbcb5 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Mon, 4 Jul 2022 12:59:54 +0200
+Subject: [PATCH 89/96] leds: turris-omnia: initialize multi-intensity to full
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The default color of each LED before driver probe (255, 255, 255).
+Initialize multi_intensity to this value, so that it corresponds to the
+reality.
+
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/leds/leds-turris-omnia.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c
+index c2dfb22d3065..fae155bd119c 100644
+--- a/drivers/leds/leds-turris-omnia.c
++++ b/drivers/leds/leds-turris-omnia.c
+@@ -131,10 +131,13 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
+ 	}
+ 
+ 	led->subled_info[0].color_index = LED_COLOR_ID_RED;
++	led->subled_info[0].intensity = 255;
+ 	led->subled_info[0].channel = 0;
+ 	led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
++	led->subled_info[1].intensity = 255;
+ 	led->subled_info[1].channel = 1;
+ 	led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
++	led->subled_info[2].intensity = 255;
+ 	led->subled_info[2].channel = 2;
+ 
+ 	led->mc_cdev.subled_info = led->subled_info;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0090-leds-turris-omnia-change-max-brightness-from-255-to-.patch b/nixos/modules/kernel-patches/0090-leds-turris-omnia-change-max-brightness-from-255-to-.patch
new file mode 100644
index 0000000..bb50246
--- /dev/null
+++ b/nixos/modules/kernel-patches/0090-leds-turris-omnia-change-max-brightness-from-255-to-.patch
@@ -0,0 +1,36 @@
+From e9d9ea6ea5f698f18a9ec7037a9e4fa80dd5e429 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Mon, 4 Jul 2022 12:59:55 +0200
+Subject: [PATCH 90/96] leds: turris-omnia: change max brightness from 255 to 1
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Using binary brightness makes more sense for this controller, because
+internally in the MCU it works that way: the LED has a color, and a
+state whether it is ON or OFF.
+
+The resulting brightness computation with led_mc_calc_color_components()
+will now always result in either (0, 0, 0) or the multi_intensity value.
+
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/leds/leds-turris-omnia.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c
+index fae155bd119c..f53bdc3f4cea 100644
+--- a/drivers/leds/leds-turris-omnia.c
++++ b/drivers/leds/leds-turris-omnia.c
+@@ -146,7 +146,7 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
+ 	init_data.fwnode = &np->fwnode;
+ 
+ 	cdev = &led->mc_cdev.led_cdev;
+-	cdev->max_brightness = 255;
++	cdev->max_brightness = 1;
+ 	cdev->brightness_set_blocking = omnia_led_brightness_set_blocking;
+ 	cdev->trigger_type = &omnia_hw_trigger_type;
+ 	cdev->default_trigger = omnia_hw_trigger.name;
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0091-generic-Mangle-bootloader-s-kernel-arguments.patch b/nixos/modules/kernel-patches/0091-generic-Mangle-bootloader-s-kernel-arguments.patch
new file mode 100644
index 0000000..9fb798a
--- /dev/null
+++ b/nixos/modules/kernel-patches/0091-generic-Mangle-bootloader-s-kernel-arguments.patch
@@ -0,0 +1,217 @@
+From e3c4d478259db4123ebb809d23371091354b906f Mon Sep 17 00:00:00 2001
+From: Adrian Panella <ianchi74@outlook.com>
+Date: Thu, 9 Mar 2017 09:37:17 +0100
+Subject: [PATCH 91/96] generic: Mangle bootloader's kernel arguments
+
+The command-line arguments provided by the boot loader will be
+appended to a new device tree property: bootloader-args.
+If there is a property "append-rootblock" in DT under /chosen
+and a root= option in bootloaders command line it will be parsed
+and added to DT bootargs with the form: <append-rootblock>XX.
+Only command line ATAG will be processed, the rest of the ATAGs
+sent by bootloader will be ignored.
+This is usefull in dual boot systems, to get the current root partition
+without afecting the rest of the system.
+
+Signed-off-by: Adrian Panella <ianchi74@outlook.com>
+
+This patch has been modified to be mvebu specific. The original patch
+did not pass the bootloader cmdline on if no append-rootblock stanza
+was found, resulting in blank cmdline and failure to boot.
+
+Signed-off-by: Michael Gray <michael.gray@lantisproject.com>
+---
+ arch/arm/Kconfig                        | 11 ++++
+ arch/arm/boot/compressed/atags_to_fdt.c | 85 ++++++++++++++++++++++++-
+ init/main.c                             | 16 +++++
+ 3 files changed, 111 insertions(+), 1 deletion(-)
+
+diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
+index 7630ba9cb6cc..dae040cc2a4f 100644
+--- a/arch/arm/Kconfig
++++ b/arch/arm/Kconfig
+@@ -1638,6 +1638,17 @@ config ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEND
+ 	  The command-line arguments provided by the boot loader will be
+ 	  appended to the the device tree bootargs property.
+ 
++config ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE
++	bool "Append rootblock parsing bootloader's kernel arguments"
++	help
++	  The command-line arguments provided by the boot loader will be
++	  appended to a new device tree property: bootloader-args.
++	  If there is a property "append-rootblock" in DT under /chosen 
++	  and a root= option in bootloaders command line it will be parsed 
++	  and added to DT bootargs with the form: <append-rootblock>XX.
++	  Only command line ATAG will be processed, the rest of the ATAGs
++	  sent by bootloader will be ignored.
++
+ endchoice
+ 
+ config CMDLINE
+diff --git a/arch/arm/boot/compressed/atags_to_fdt.c b/arch/arm/boot/compressed/atags_to_fdt.c
+index 1feb6b0f7a1f..fb8cd4f07e60 100644
+--- a/arch/arm/boot/compressed/atags_to_fdt.c
++++ b/arch/arm/boot/compressed/atags_to_fdt.c
+@@ -5,6 +5,8 @@
+ 
+ #if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEND)
+ #define do_extend_cmdline 1
++#elif defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
++#define do_extend_cmdline 1
+ #else
+ #define do_extend_cmdline 0
+ #endif
+@@ -69,6 +71,72 @@ static uint32_t get_cell_size(const void *fdt)
+ 	return cell_size;
+ }
+ 
++#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
++
++static char *append_rootblock(char *dest, const char *str, int len, void *fdt)
++{
++	char *ptr, *end;
++	char *root="root=";
++	int i, l;
++	const char *rootblock;
++
++	//ARM doesn't have __HAVE_ARCH_STRSTR, so search manually
++	ptr = str - 1;
++
++	do {
++		//first find an 'r' at the begining or after a space
++		do {
++			ptr++;
++			ptr = strchr(ptr, 'r');
++			if (!ptr)
++				goto no_append;
++
++		} while (ptr != str && *(ptr-1) != ' ');
++
++		//then check for the rest
++		for(i = 1; i <= 4; i++)
++			if(*(ptr+i) != *(root+i)) break;
++
++	} while (i != 5);
++
++	end = strchr(ptr, ' ');
++	end = end ? (end - 1) : (strchr(ptr, 0) - 1);
++
++	//find partition number (assumes format root=/dev/mtdXX | /dev/mtdblockXX | yy:XX )
++	for( i = 0; end >= ptr && *end >= '0' && *end <= '9'; end--, i++);
++	ptr = end + 1;
++
++	/* if append-rootblock property is set use it to append to command line */
++	rootblock = getprop(fdt, "/chosen", "append-rootblock", &l);
++	if (rootblock == NULL)
++		goto no_append;
++
++	if (*dest != ' ') {
++		*dest = ' ';
++		dest++;
++		len++;
++	}
++
++	if (len + l + i <= COMMAND_LINE_SIZE) {
++		memcpy(dest, rootblock, l);
++		dest += l - 1;
++		memcpy(dest, ptr, i);
++		dest += i;
++	}
++
++	return dest;
++
++no_append:
++	len = strlen(str);
++	if (len + 1 < COMMAND_LINE_SIZE) {
++		memcpy(dest, str, len);
++		dest += len;
++	}
++
++	return dest;
++}
++#endif
++
+ static void merge_fdt_bootargs(void *fdt, const char *fdt_cmdline)
+ {
+ 	char cmdline[COMMAND_LINE_SIZE];
+@@ -88,12 +156,21 @@ static void merge_fdt_bootargs(void *fdt, const char *fdt_cmdline)
+ 
+ 	/* and append the ATAG_CMDLINE */
+ 	if (fdt_cmdline) {
++
++#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
++		//save original bootloader args
++		//and append ubi.mtd with root partition number to current cmdline
++		setprop_string(fdt, "/chosen", "bootloader-args", fdt_cmdline);
++		ptr = append_rootblock(ptr, fdt_cmdline, len, fdt);
++
++#else
+ 		len = strlen(fdt_cmdline);
+ 		if (ptr - cmdline + len + 2 < COMMAND_LINE_SIZE) {
+ 			*ptr++ = ' ';
+ 			memcpy(ptr, fdt_cmdline, len);
+ 			ptr += len;
+ 		}
++#endif
+ 	}
+ 	*ptr = '\0';
+ 
+@@ -168,7 +245,9 @@ int atags_to_fdt(void *atag_list, void *fdt, int total_space)
+ 			else
+ 				setprop_string(fdt, "/chosen", "bootargs",
+ 					       atag->u.cmdline.cmdline);
+-		} else if (atag->hdr.tag == ATAG_MEM) {
++		}
++#ifndef CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE
++		else if (atag->hdr.tag == ATAG_MEM) {
+ 			if (memcount >= sizeof(mem_reg_property)/4)
+ 				continue;
+ 			if (!atag->u.mem.size)
+@@ -212,6 +291,10 @@ int atags_to_fdt(void *atag_list, void *fdt, int total_space)
+ 		setprop(fdt, "/memory", "reg", mem_reg_property,
+ 			4 * memcount * memsize);
+ 	}
++#else
++
++	}
++#endif
+ 
+ 	return fdt_pack(fdt);
+ }
+diff --git a/init/main.c b/init/main.c
+index 12b821626826..45a73e8c7082 100644
+--- a/init/main.c
++++ b/init/main.c
+@@ -113,6 +113,10 @@
+ 
+ #include <kunit/test.h>
+ 
++#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
++#include <linux/of.h>
++#endif
++
+ static int kernel_init(void *);
+ 
+ extern void init_IRQ(void);
+@@ -985,6 +989,18 @@ asmlinkage __visible void __init __no_sanitize_address start_kernel(void)
+ 	page_alloc_init();
+ 
+ 	pr_notice("Kernel command line: %s\n", saved_command_line);
++
++#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
++	//Show bootloader's original command line for reference
++	if(of_chosen) {
++		const char *prop = of_get_property(of_chosen, "bootloader-args", NULL);
++		if(prop)
++			pr_notice("Bootloader command line (ignored): %s\n", prop);
++		else
++			pr_notice("Bootloader command line not present\n");
++	}
++#endif
++
+ 	/* parameters may set static keys */
+ 	jump_label_init();
+ 	parse_early_param();
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0092-ARM-mvebu-385-ap-Add-partitions.patch b/nixos/modules/kernel-patches/0092-ARM-mvebu-385-ap-Add-partitions.patch
new file mode 100644
index 0000000..c5cee5a
--- /dev/null
+++ b/nixos/modules/kernel-patches/0092-ARM-mvebu-385-ap-Add-partitions.patch
@@ -0,0 +1,40 @@
+From b752a59c98eafa1cd78b06e10fa7ef17e6ace724 Mon Sep 17 00:00:00 2001
+From: Maxime Ripard <maxime.ripard@free-electrons.com>
+Date: Tue, 13 Jan 2015 11:14:09 +0100
+Subject: [PATCH 92/96] ARM: mvebu: 385-ap: Add partitions
+
+Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
+---
+ arch/arm/boot/dts/armada-385-db-ap.dts | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/arch/arm/boot/dts/armada-385-db-ap.dts b/arch/arm/boot/dts/armada-385-db-ap.dts
+index 332f8fce77dc..c36ec6a50f78 100644
+--- a/arch/arm/boot/dts/armada-385-db-ap.dts
++++ b/arch/arm/boot/dts/armada-385-db-ap.dts
+@@ -218,19 +218,19 @@ partitions {
+ 			#size-cells = <1>;
+ 
+ 			partition@0 {
+-				label = "U-Boot";
++				label = "u-boot";
+ 				reg = <0x00000000 0x00800000>;
+ 				read-only;
+ 			};
+ 
+ 			partition@800000 {
+-				label = "uImage";
++				label = "kernel";
+ 				reg = <0x00800000 0x00400000>;
+ 				read-only;
+ 			};
+ 
+ 			partition@c00000 {
+-				label = "Root";
++				label = "ubi";
+ 				reg = <0x00c00000 0x3f400000>;
+ 			};
+ 		};
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0093-ARM-dts-armada388-clearfog-emmc-on-clearfog-base.patch b/nixos/modules/kernel-patches/0093-ARM-dts-armada388-clearfog-emmc-on-clearfog-base.patch
new file mode 100644
index 0000000..990485f
--- /dev/null
+++ b/nixos/modules/kernel-patches/0093-ARM-dts-armada388-clearfog-emmc-on-clearfog-base.patch
@@ -0,0 +1,95 @@
+From 40958387c6993463d9322bcf50727b2221b21420 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@armlinux.org.uk>
+Date: Tue, 29 Nov 2016 10:15:45 +0000
+Subject: [PATCH 93/96] ARM: dts: armada388-clearfog: emmc on clearfog base
+
+Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
+---
+ .../arm/boot/dts/armada-388-clearfog-base.dts |  1 +
+ .../armada-38x-solidrun-microsom-emmc.dtsi    | 62 +++++++++++++++++++
+ 2 files changed, 63 insertions(+)
+ create mode 100644 arch/arm/boot/dts/armada-38x-solidrun-microsom-emmc.dtsi
+
+diff --git a/arch/arm/boot/dts/armada-388-clearfog-base.dts b/arch/arm/boot/dts/armada-388-clearfog-base.dts
+index 53b4bd35522a..3c46daebb447 100644
+--- a/arch/arm/boot/dts/armada-388-clearfog-base.dts
++++ b/arch/arm/boot/dts/armada-388-clearfog-base.dts
+@@ -7,6 +7,7 @@
+ 
+ /dts-v1/;
+ #include "armada-388-clearfog.dtsi"
++#include "armada-38x-solidrun-microsom-emmc.dtsi"
+ 
+ / {
+ 	model = "SolidRun Clearfog Base A1";
+diff --git a/arch/arm/boot/dts/armada-38x-solidrun-microsom-emmc.dtsi b/arch/arm/boot/dts/armada-38x-solidrun-microsom-emmc.dtsi
+new file mode 100644
+index 000000000000..6b623b6aa602
+--- /dev/null
++++ b/arch/arm/boot/dts/armada-38x-solidrun-microsom-emmc.dtsi
+@@ -0,0 +1,62 @@
++/*
++ * Device Tree file for SolidRun Armada 38x Microsom add-on for eMMC
++ *
++ *  Copyright (C) 2015 Russell King
++ *
++ * This board is in development; the contents of this file work with
++ * the A1 rev 2.0 of the board, which does not represent final
++ * production board.  Things will change, don't expect this file to
++ * remain compatible info the future.
++ *
++ * This file is dual-licensed: you can use it either under the terms
++ * of the GPL or the X11 license, at your option. Note that this dual
++ * licensing only applies to this file, and not this project as a
++ * whole.
++ *
++ *  a) This file is free software; you can redistribute it and/or
++ *     modify it under the terms of the GNU General Public License
++ *     version 2 as published by the Free Software Foundation.
++ *
++ *     This file is distributed in the hope that it will be useful
++ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
++ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ *     GNU General Public License for more details.
++ *
++ * Or, alternatively
++ *
++ *  b) Permission is hereby granted, free of charge, to any person
++ *     obtaining a copy of this software and associated documentation
++ *     files (the "Software"), to deal in the Software without
++ *     restriction, including without limitation the rights to use
++ *     copy, modify, merge, publish, distribute, sublicense, and/or
++ *     sell copies of the Software, and to permit persons to whom the
++ *     Software is furnished to do so, subject to the following
++ *     conditions:
++ *
++ *     The above copyright notice and this permission notice shall be
++ *     included in all copies or substantial portions of the Software.
++ *
++ *     THE SOFTWARE IS PROVIDED , WITHOUT WARRANTY OF ANY KIND
++ *     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
++ *     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ *     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
++ *     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY
++ *     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ *     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
++ *     OTHER DEALINGS IN THE SOFTWARE.
++ */
++/ {
++	soc {
++		internal-regs {
++			sdhci@d8000 {
++				bus-width = <4>;
++				no-1-8-v;
++				non-removable;
++				pinctrl-0 = <&microsom_sdhci_pins>;
++				pinctrl-names = "default";
++				status = "okay";
++				wp-inverted;
++			};
++		};
++	};
++};
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0094-ARM-dts-armada-xp-linksys-mamba-Increase-kernel-part.patch b/nixos/modules/kernel-patches/0094-ARM-dts-armada-xp-linksys-mamba-Increase-kernel-part.patch
new file mode 100644
index 0000000..cee07e7
--- /dev/null
+++ b/nixos/modules/kernel-patches/0094-ARM-dts-armada-xp-linksys-mamba-Increase-kernel-part.patch
@@ -0,0 +1,42 @@
+From df33ff93c6740523a4221f207b84f3b6d0075c3e Mon Sep 17 00:00:00 2001
+From: Tad <tad@spotco.us>
+Date: Fri, 5 Feb 2021 22:32:11 -0500
+Subject: [PATCH 94/96] ARM: dts: armada-xp-linksys-mamba: Increase kernel
+ partition to 4MB
+
+Signed-off-by: Tad Davanzo <tad@spotco.us>
+---
+ arch/arm/boot/dts/armada-xp-linksys-mamba.dts | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
+index 36932e3b781a..29f66dd62b85 100644
+--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
++++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
+@@ -360,9 +360,9 @@ partition@a00000 {
+ 				reg = <0xa00000 0x2800000>;  /* 40MB */
+ 			};
+ 
+-			partition@d00000 {
++			partition@e00000 {
+ 				label = "rootfs1";
+-				reg = <0xd00000 0x2500000>;  /* 37MB */
++				reg = <0xe00000 0x2400000>;  /* 36MB */
+ 			};
+ 
+ 			/* kernel2 overlaps with rootfs2 by design */
+@@ -371,9 +371,9 @@ partition@3200000 {
+ 				reg = <0x3200000 0x2800000>; /* 40MB */
+ 			};
+ 
+-			partition@3500000 {
++			partition@3600000 {
+ 				label = "rootfs2";
+-				reg = <0x3500000 0x2500000>; /* 37MB */
++				reg = <0x3600000 0x2400000>; /* 36MB */
+ 			};
+ 
+ 			/*
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0095-phy-marvell-phy-mvebu-a3700-comphy-Change-2500base-x.patch b/nixos/modules/kernel-patches/0095-phy-marvell-phy-mvebu-a3700-comphy-Change-2500base-x.patch
new file mode 100644
index 0000000..918e5a1
--- /dev/null
+++ b/nixos/modules/kernel-patches/0095-phy-marvell-phy-mvebu-a3700-comphy-Change-2500base-x.patch
@@ -0,0 +1,63 @@
+From e5aa46ceca003444bd2e7a5f4fd43d5e61499515 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
+Date: Wed, 17 Aug 2022 14:35:59 +0200
+Subject: [PATCH 95/96] phy: marvell: phy-mvebu-a3700-comphy: Change 2500base-x
+ transmit amplitude
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Change transmit amplitude to 1025 mV for 2500base-x mode.
+
+This fixes issue wherein if the 8b/10b encoded packet contains a long
+enough alternating sequence of bits (010101... or 101010...), which
+happens if the packet contains a sequence of 'J' or '\xb5' bytes, the
+packet may be lost due to FCS error. The probability of loss grows with
+the number of 'J's with default transmit amplitude setting - with 114
+'J's the probability is about 50%, with 125 'J's almost 100% of packets
+are lost.
+
+Signed-off-by: Marek Behún <kabel@kernel.org>
+---
+ drivers/phy/marvell/phy-mvebu-a3700-comphy.c | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+diff --git a/drivers/phy/marvell/phy-mvebu-a3700-comphy.c b/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
+index a4d7d9bd100d..4a18f9ca6d25 100644
+--- a/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
++++ b/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
+@@ -68,6 +68,12 @@
+ #define SPEED_PLL_MASK			GENMASK(7, 2)
+ #define SPEED_PLL_VALUE_16		FIELD_PREP(SPEED_PLL_MASK, 0x10)
+ 
++#define COMPHY_GEN3_SET0		0x0d
++#define Gx_TX_AMP_MASK			GENMASK(5, 1)
++#define Gx_TX_AMP_VALUE(x)		FIELD_PREP(Gx_TX_AMP_MASK, x)
++#define Gx_TX_AMP_ADJ			BIT(6)
++#define Gx_TX_AMP_1025MV		(Gx_TX_AMP_VALUE(0x12) | Gx_TX_AMP_ADJ)
++
+ #define COMPHY_DIG_LOOPBACK_EN		0x23
+ #define SEL_DATA_WIDTH_MASK		GENMASK(11, 10)
+ #define DATA_WIDTH_10BIT		FIELD_PREP(SEL_DATA_WIDTH_MASK, 0x0)
+@@ -746,6 +752,18 @@ mvebu_a3700_comphy_ethernet_power_on(struct mvebu_a3700_comphy_lane *lane)
+ 		comphy_gbe_phy_init(lane,
+ 				    lane->submode != PHY_INTERFACE_MODE_2500BASEX);
+ 
++	/*
++	 * Fix issue wherein a packet may be lost if it contains a long enough
++	 * sequence of 'J' or '\xb5' bytes.
++	 * This only happens with 2500base-x mode. Fix this by changing transmit
++	 * amplitude to 1025 mV.
++	 */
++	if (lane->submode == PHY_INTERFACE_MODE_2500BASEX) {
++		data = Gx_TX_AMP_1025MV;
++		mask = Gx_TX_AMP_MASK | Gx_TX_AMP_ADJ;
++		comphy_lane_reg_set(lane, COMPHY_GEN3_SET0, data, mask);
++	}
++
+ 	/*
+ 	 * 14. Check the PHY Polarity invert bit
+ 	 */
+-- 
+2.37.2
+
diff --git a/nixos/modules/kernel-patches/0096-cpuidle-mvebu-indicate-failure-to-enter-deeper-sleep.patch b/nixos/modules/kernel-patches/0096-cpuidle-mvebu-indicate-failure-to-enter-deeper-sleep.patch
new file mode 100644
index 0000000..cd7cf3a
--- /dev/null
+++ b/nixos/modules/kernel-patches/0096-cpuidle-mvebu-indicate-failure-to-enter-deeper-sleep.patch
@@ -0,0 +1,46 @@
+From a8be83aa342b173df49ca938f49c9cd8f9433643 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sat, 3 Oct 2015 09:13:05 +0100
+Subject: [PATCH 96/96] cpuidle: mvebu: indicate failure to enter deeper sleep
+ states
+
+The cpuidle ->enter method expects the return value to be the sleep
+state we entered.  Returning negative numbers or other codes is not
+permissible since coupled CPU idle was merged.
+
+At least some of the mvebu_v7_cpu_suspend() implementations return the
+value from cpu_suspend(), which returns zero if the CPU vectors back
+into the kernel via cpu_resume() (the success case), or the non-zero
+return value of the suspend actor, or one (failure cases).
+
+We do not want to be returning the failure case value back to CPU idle
+as that indicates that we successfully entered one of the deeper idle
+states.  Always return zero instead, indicating that we slept for the
+shortest amount of time.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/cpuidle/cpuidle-mvebu-v7.c | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/cpuidle/cpuidle-mvebu-v7.c b/drivers/cpuidle/cpuidle-mvebu-v7.c
+index 01a856971f05..18ded9e7cb34 100644
+--- a/drivers/cpuidle/cpuidle-mvebu-v7.c
++++ b/drivers/cpuidle/cpuidle-mvebu-v7.c
+@@ -39,8 +39,12 @@ static int mvebu_v7_enter_idle(struct cpuidle_device *dev,
+ 	ret = mvebu_v7_cpu_suspend(deepidle);
+ 	cpu_pm_exit();
+ 
++	/*
++	 * If we failed to enter the desired state, indicate that we
++	 * slept lightly.
++	 */
+ 	if (ret)
+-		return ret;
++		return 0;
+ 
+ 	return index;
+ }
+-- 
+2.37.2
+
-- 
cgit v1.2.3