Skip to content

ath79: add support for Linksys LAPAC1200/1750[C]#21292

Open
mmlr wants to merge 1 commit intoopenwrt:mainfrom
mmlr:lapac1750
Open

ath79: add support for Linksys LAPAC1200/1750[C]#21292
mmlr wants to merge 1 commit intoopenwrt:mainfrom
mmlr:lapac1750

Conversation

@mmlr
Copy link
Copy Markdown
Contributor

@mmlr mmlr commented Dec 25, 2025

Linksys LAPAC1200 and LAPAC1750 are dual band wireless AC access points
with PoE. They use either the QCA9557 or QCA9558 and have a 2x2:2 and
3x3:3 configuration, respectively. The variants with a C suffix are
cloud managed versions that only differ in their software.

Hardware:

  • SoC: Qualcomm Atheros QCA9558 (LAPAC1750) or QCA9557 (LAPAC1200)
  • RAM: 128GiB 2x EtronTech EM68B16CWQH-25H
  • Flash: 16MiB Macronix MX25L12835FMI SPI-NOR
  • WLAN 2.4GHz: QCA9558 3x3:3 (LAPAC1750) or 2x2:2 (LAPAC1200)
  • WLAN 5GHz: QCA9880 3x3:3 (LAPAC1750) or 2x2:2 (LAPAC1200)
  • Ethernet: QCA9558 or QCA9557 with Atheros AR8035 PHY
  • Serial Config: 3.3V TTL 115200-8-N-1
  • Serial Layout: RX 3.3V (don't connect) TX [GND] --- LEDs
  • LEDs: 1x Red, 1x Green, 1x Blue
  • Buttons: 1x Reset

MAC addresses:

  • Label: LAN, stored in u-boot partition at 0x3ff3a
  • 2.4GHz WLAN: LAN + 1
  • 5GHz WLAN: LAN + 2

Notes:
The bootloader uses a dual image setup with two slots. OpenWrt can only be
installed in the first slot. Due to limited storage, the flash layout will
consume both slots with mtd-concat and fully replace the stock firmware,
including its log and nvram partitions.

Flashing with Stock Web Interface:
Note: This does not work for the C variants, as they require the firmware
to be GPG signed, use the recovery method below instead.

  1. Determine current boot partition by injecting the following command in
    Configuration -> Administration -> SSL Certificate -> Export SSL
    Certificate to TFTP Server -> Destination File (use a dummy server
    address and confirm the upload dialog):

    ' || S=${PWD%u*} && ${S}bin${S}cat ${S}proc${S}cmdline #

  2. If the current command line uses "root=31:03", OpenWrt would be written
    to the wrong slot and a stock firmware image 0 needs to be flashed
    first.

  3. Once the stock firmware uses "root=31:09", flash the OpenWrt factory
    image.

Unbrick / Revert to Stock / Flash OpenWrt directly:

  1. Hold reset button while powering up until the LEDs flash red/blue.
  2. Use recovery tool 1 to flash either a stock or OpenWrt factory image.

@mmlr mmlr changed the title ath79: add support for Linksys LAPC1750 ath79: add support for Linksys LAPAC1750 Dec 25, 2025
@github-actions github-actions bot added build/scripts/tools pull request/issues for build, scripts and tools related changes target/ath79 pull request/issue for ath79 target labels Dec 25, 2025
@mmlr mmlr force-pushed the lapac1750 branch 2 times, most recently from acc5474 to 0735a93 Compare December 31, 2025 00:31
@mmlr
Copy link
Copy Markdown
Contributor Author

mmlr commented Dec 31, 2025

I've found that I missed adding the SERCOMM_* variables to DEVICE_VARS and fixed it.

There is also the question of the LAPAC1200 which has the same hardware minus two antennas. It uses the same vendor firmware image. It should be compatible, but I have no way of testing it as I don't own a LAPAC1200 and can't find one to buy at a reasonable price. If it was declared compatible, would this patch be the way to go, i.e. adding a DEVICE_ALT* and SUPPORTED_DEVICES?

diff --git a/target/linux/ath79/dts/qca9558_linksys_lapac1750.dts b/target/linux/ath79/dts/qca9558_linksys_lapac1750.dts
index 62adf17670..6b3793989f 100644
--- a/target/linux/ath79/dts/qca9558_linksys_lapac1750.dts
+++ b/target/linux/ath79/dts/qca9558_linksys_lapac1750.dts
@@ -9,7 +9,7 @@
 
 / {
    compatible = "linksys,lapac1750", "qca,qca9558";
-   model = "Linksys LAPAC1750";
+   model = "Linksys LAPAC1750/LAPAC1200";
 
    aliases {
        label-mac-device = &eth1;
diff --git a/target/linux/ath79/image/generic.mk b/target/linux/ath79/image/generic.mk
index 75a14ccc70..9c199f6162 100644
--- a/target/linux/ath79/image/generic.mk
+++ b/target/linux/ath79/image/generic.mk
@@ -2033,6 +2033,8 @@ define Device/linksys_lapac1750
   SOC := qca9558
   DEVICE_VENDOR := Linksys
   DEVICE_MODEL := LAPAC1750
+  DEVICE_ALT0_VENDOR := Linksys
+  DEVICE_ALT0_MODEL := LAPAC1200
   DEVICE_PACKAGES := -uboot-envtools kmod-ath10k-ct ath10k-firmware-qca988x-ct
   IMAGE_SIZE := 7100k
   LOADER_FLASH_OFFS := 0x60000
@@ -2049,6 +2051,7 @@ define Device/linksys_lapac1750
    sercomm-factory-wrap --header-offset=0x10000
   IMAGE/sysupgrade.bin := $$(IMAGE/default) | \
    sercomm-factory-wrap --no-pid --header-offset=0x50000 | append-metadata
+  SUPPORTED_DEVICES += linksys,lapac1200
 endef
 TARGET_DEVICES += linksys_lapac1750

@mmlr mmlr force-pushed the lapac1750 branch 2 times, most recently from 082084f to 573455a Compare January 28, 2026 19:14
@mmlr mmlr changed the title ath79: add support for Linksys LAPAC1750 ath79: add support for Linksys LAPAC1200/1750[C] Jan 28, 2026
@mmlr
Copy link
Copy Markdown
Contributor Author

mmlr commented Jan 28, 2026

In the meantime I was able to get a hold of a LAPAC1200C and could confirm, that the LAPAC1200, LAPAC1750, LAPAC1200C and LAPAC1750C are all very similar and can use essentially the same firmware. The LAPAC1200[C] are 2x2:2 with a QCA9557 and the LAPAC1750[C] are 3x3:3 with a QCA9558. The C variants are cloud managed, but really only have a different function code in the factory image.

So I have updated this PR to include support for all four of these models and fixed some minor issues that came up while testing. I have also rebased to a more current main branch state, but am not sure if this is a good practice or not.

@Djfe
Copy link
Copy Markdown
Contributor

Djfe commented Jan 28, 2026

yeah, rebasing is always good practice 👍

linksys,lapac1200c|\
linksys,lapac1750|\
linksys,lapac1750c)
ucidef_set_interface_lan "eth0" "dhcp"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious about what "dhcp" is?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sets the interface protocol to "dhcp" instead of "static" that a "_lan" interface would normally get. It is used on various devices in the tree already and this change adds the LAPACs to an existing use. It IMHO makes sense for pure access point devices that are not usable as routers and won't create an internal network.

@mmlr mmlr force-pushed the lapac1750 branch 2 times, most recently from b6bfead to bba8301 Compare March 24, 2026 23:44
@hauke
Copy link
Copy Markdown
Member

hauke commented Mar 25, 2026

/claude

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 25, 2026

Claude finished @hauke's task in 4m 12s —— View job


Code Review: ath79: add support for Linksys LAPAC1200/1750[C]

  • Gather context and read changed files
  • Review DTS/DTSI files
  • Review Python script (sercomm-factory-wrap.py)
  • Review image/generic.mk changes
  • Review board.d and image-commands.mk changes
  • Post final review

Overall this is a well-structured port. The use of a shared .dtsi, a common Makefile template, and the mtd-concat approach for dual-slot flash are all solid. A few issues worth addressing:


Commit Message / Documentation

target/linux/ath79 commit message — "128GiB" should be "128MiB". The EtronTech EM68B16CWQH-25H chips are 128 MiB total, not gibibytes.


target/linux/ath79/image/generic.mk

SUPPORTED_DEVICES += linksys,lapac1200 in Device/linksys_lapac_common (line 2050) — This is the most significant issue. Placing this in the common template means all four device images (lapac1200, lapac1750, lapac1200c, lapac1750c) will list linksys,lapac1200 in their SUPPORTED_DEVICES. The practical consequence is that a device running OpenWrt with linksys,lapac1200 in its DTS compatible string can accept sysupgrade images built for any of the four devices — including the LAPAC1750 image which contains a QCA9558 DTB, not QCA9557. That would brick a LAPAC1200.

This should be moved out of the common template. If cross-flashing between the 1200 and 1750 is intentionally unsupported, remove it entirely. If needed as a legacy migration string for the original single-device PR iteration, put it only in Device/linksys_lapac1200.

Missing C ↔ non-C cross-compatibility — The commit message states C and non-C variants "only differ in their software" and are otherwise hardware-identical. It would be expected that a lapac1200 could sysupgrade to a lapac1200c image and vice versa (e.g. to drop/add cloud management). To support this, linksys,lapac1200c should be in linksys_lapac1200's SUPPORTED_DEVICES and vice versa, and similarly for the 1750 pair.

SERCOMM_FUNCTION_CODE := 7 is the same for both lapac1200 and lapac1750 — Is this correct? Since the 1200 and 1750 share the same vendor firmware image (FW_LAPAC1200_LAPAC1750_1.1.03.000.img), using the same function code for both makes sense. Just confirming this is intentional, not a copy-paste error.


scripts/sercomm-factory-wrap.py

--kernel-split is not marked required but on line 70, data[:args.kernel_split] and data[args.kernel_split:] are always evaluated. In Python, data[:None] returns all of data and data[None:] also returns all of data, so omitting --kernel-split would produce two full-copy images in the header rather than raising an error. Since SERCOMM_KERNEL_SPLIT is always set in the common template this is not a practical issue, but making the argument required=True (or adding an explicit if args.kernel_split is None guard) would improve robustness. Fix this →

Argument parser is at module level — minor style nit: argparse setup and args = parser.parse_args() are at module level rather than under a if __name__ == '__main__': guard. Not a functional issue but makes the script harder to import/test.


target/linux/ath79/dts/qca955x_linksys_lapac.dtsi

led-running and led-upgrade both mapped to led_blue — This is fine; it matches the hardware description (only one blue LED).

All three LEDs use function = LED_FUNCTION_STATUS — Since the LEDs are used for distinct roles (boot=green, failsafe=red, running/upgrade=blue) via the led-* aliases, this is acceptable. The aliases drive the semantic mapping cleanly.

Virtual flash partition geometry — Looks correct:

  • fwconcat0: 0x050000–0x7f0000 = 0x7a0000 (7680 KiB)
  • fwconcat1: 0x800000–0xf00000 = 0x700000 (7168 KiB)
  • Combined = 0xea0000 = firmware partition size ✓
  • IMAGE_SIZE := 7100k fits within the combined space ✓

uimage partition offsetreg = <0x010000 0xe90000> — 0x10000 = LOADER_FLASH_OFFS (0x60000) − fwconcat0 start (0x50000). pad-to 65280 (0xFF00) ensures the loader fits within 64 KiB. Looks intentional and consistent.


target/linux/ath79/generic/base-files/etc/board.d/02_network

Adding these four devices to the ucidef_set_interface_lan "eth0" "dhcp" block is appropriate for pure AP devices with no routing capability. The pattern matches other AP-only devices already in that block (e.g. etactica,eg200). ✓


include/image-commands.mk

The Build/sercomm-factory-wrap wrapper correctly passes all SERCOMM_* variables. DEVICE_VARS is properly declared in generic.mk (lines 15–16). ✓

Linksys LAPAC1200 and LAPAC1750 are dual band wireless AC access points
with PoE. They use either the QCA9557 or QCA9558 and have a 2x2:2 and
3x3:3 configuration, respectively. The variants with a C suffix are
cloud managed versions that only differ in their software.

Hardware:
* SoC: Qualcomm Atheros QCA9558 (LAPAC1750) or QCA9557 (LAPAC1200)
* RAM: 128MiB 2x EtronTech EM68B16CWQH-25H
* Flash: 16MiB Macronix MX25L12835FMI SPI-NOR
* WLAN 2.4GHz: QCA9558 3x3:3 (LAPAC1750) or 2x2:2 (LAPAC1200)
* WLAN 5GHz: QCA9880 3x3:3 (LAPAC1750) or 2x2:2 (LAPAC1200)
* Ethernet: QCA9558 or QCA9557 with Atheros AR8035 PHY
* Serial Config: 3.3V TTL 115200-8-N-1
* Serial Layout: RX 3.3V (don't connect) TX [GND] --- LEDs
* LEDs: 1x Red, 1x Green, 1x Blue
* Buttons: 1x Reset

MAC addresses:
* Label: LAN, stored in u-boot partition at 0x3ff3a
* 2.4GHz WLAN: LAN + 1
* 5GHz WLAN: LAN + 2

Notes:
The bootloader uses a dual image setup with two slots. OpenWrt can only be
installed in the first slot. Due to limited storage, the flash layout will
consume both slots with mtd-concat and fully replace the stock firmware,
including its log and nvram partitions.
An okli loader is used because the bootloader has problems decompressing
the regular kernel directly. It is prepended to the firmware images. To
allow for sysupgrades that include the loader, an extra "firmware"
partition is used as the target. The actual uImage then overlaps that
partition with an offset for the loader to fit.

Flashing with Stock Web Interface:
Note: This does not work for the C variants, as they require the firmware
to be GPG signed, use the recovery method below instead.

1. Determine current boot partition by injecting the following command in
   Configuration -> Administration -> SSL Certificate -> Export SSL
   Certificate to TFTP Server -> Destination File (use a dummy server
   address and confirm the upload dialog):

   ' || S=${PWD%u*} && ${S}bin${S}cat ${S}proc${S}cmdline #

2. If the current command line uses "root=31:03", OpenWrt would be written
   to the wrong slot and a stock firmware image [0] needs to be flashed
   first.

3. Once the stock firmware uses "root=31:09", flash the OpenWrt factory
   image.

Unbrick / Revert to Stock / Flash OpenWrt directly:
1. Hold reset button while powering up until the LEDs flash red/blue.
2. Use recovery tool [1] to flash either a stock or OpenWrt factory image.

[0]: https://downloads.linksys.com/downloads/firmware/FW_LAPAC1200_LAPAC1750_1.1.03.000.img
[1]: https://github.com/mmlr/linksys-sercomm-recovery

Signed-off-by: Michael Lotz <mmlr@mlotz.ch>
@mmlr
Copy link
Copy Markdown
Contributor Author

mmlr commented Mar 25, 2026

I've dropped the SUPPORTED_DEVICES, as this was a remnant of the initial attempt for LAPAC1200 support. In OpenWrt there is no difference between the C and non-C variants, except for the model label, so supporting sysupgrades between them is not useful. The shared function code across the C and non-C models is indeed intentional as that is needed for stock firmware and recovery flashing.

For the kernel-split argument in the factory wrap script, I've opted to allow non-splitting and to explicitly check the argument. The arg parser being in the global context is fine IMO, as the entire functionality is in that context and the script is not intended to be imported as a module.

All the raised issues have been addressed in the latest push.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build/scripts/tools pull request/issues for build, scripts and tools related changes target/ath79 pull request/issue for ath79 target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants