Skip to content

Commit

Permalink
generic: platform/mikrotik: add wlan lz77 decompress
Browse files Browse the repository at this point in the history
A number of new (or with recently updated caldata)
Mikrotik devices are using LZ77 magic for wlan tag hard_config data.
New devices include the Chateau LTE12 [1], and ax devices [2]
Newly factory flashed devices may include the hap ac3 [3]

This can be seen in decoded OEM supout [4] dmesg:
"radio data lz77 decompressed from"…

Investigating an arm RouterOS flash.ko module, and supplied example
hard_config dumps, the format was guessed via decompilation and live
debugging [5]. This decoder was then built from the guessed format
specification.

debug prints can be enabled in a DYNAMIC_DEBUG kernel build via the
kernel cmdline:

        chosen {
-               bootargs = "console=ttyS0,115200";
+               bootargs = "console=ttyS0,115200 dyndbg=\"file drivers/platform/mikrotik/* +p\"";
        };

[1]: https://forum.openwrt.org/t/no-wireless-mikrotik-rbd53ig-5hacd2hnd/157763/4
[2]: https://forum.openwrt.org/t/mikrotik-routeros-v7-x-and-openwrt-sysupgrade/148072/17
[3]: https://forum.openwrt.org/t/adding-support-for-mikrotik-hap-ax2/133715/47
[4]: https://github.com/farseeker/go-mikrotik-rif
[5]: https://github.com/john-tho/routeros-wlan-lz77-decode

Signed-off-by: John Thomson <[email protected]>
  • Loading branch information
john-tho committed Sep 10, 2024
1 parent 2cfcb90 commit 6ef2e0a
Show file tree
Hide file tree
Showing 10 changed files with 557 additions and 27 deletions.
1 change: 1 addition & 0 deletions target/linux/ath79/mikrotik/config-default
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ CONFIG_MFD_CORE=y
CONFIG_MFD_RB4XX_CPLD=y
CONFIG_MIKROTIK=y
CONFIG_MIKROTIK_RB_SYSFS=y
CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77=y
CONFIG_MTD_NAND_AR934X=y
CONFIG_MTD_NAND_CORE=y
CONFIG_MTD_NAND_ECC=y
Expand Down
7 changes: 7 additions & 0 deletions target/linux/generic/files/drivers/platform/mikrotik/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,11 @@ config NVMEM_LAYOUT_MIKROTIK
help
This driver exposes MikroTik hard_config via NVMEM layout.

config MIKROTIK_WLAN_DECOMPRESS_LZ77
tristate "Mikrotik factory Wi-Fi caldata LZ77 decompression support"
depends on MIKROTIK_RB_SYSFS
help
Allow Mikrotik LZ77 factory flashed Wi-Fi calibration data to be
decompressed

endif # MIKROTIK
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
#
obj-$(CONFIG_MIKROTIK_RB_SYSFS) += routerboot.o rb_hardconfig.o rb_softconfig.o
obj-$(CONFIG_NVMEM_LAYOUT_MIKROTIK) += rb_nvmem.o
obj-$(CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77) += rb_lz77.o
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@

#include "rb_hardconfig.h"
#include "routerboot.h"
#include "rb_lz77.h"

#define RB_HARDCONFIG_VER "0.07"
#define RB_HARDCONFIG_VER "0.08"
#define RB_HC_PR_PFX "[rb_hardconfig] "

/* Bit definitions for hardware options */
Expand Down Expand Up @@ -465,47 +466,75 @@ static int hc_wlan_data_unpack_erd(const u16 tag_id, const u8 *inbuf, size_t inl
/*
* If the RB_ID_WLAN_DATA payload starts with RB_MAGIC_LZOR, then past
* that magic number is a payload that must be appended to the hc_lzor_prefix,
* the resulting blob is LZO-compressed. In the LZO decompression result,
* the resulting blob is LZO-compressed.
* If payload starts with RB_MAGIC_LZ77, a separate (bit level LZ77)
* decompression function needs to be used. In the decompressed result,
* the RB_MAGIC_ERD magic number (aligned) must be located. Following that
* magic, there is one or more routerboot tag node(s) locating the RLE-encoded
* calibration data payload.
*/
static int hc_wlan_data_unpack_lzor(const u16 tag_id, const u8 *inbuf, size_t inlen,
void *outbuf, size_t *outlen)
static int hc_wlan_data_unpack_lzor_lz77(const u16 tag_id, const u8 *inbuf, size_t inlen,
void *outbuf, size_t *outlen, u32 magic)
{
u16 rle_ofs, rle_len;
const u32 *needle;
u8 *tempbuf;
size_t templen, lzo_len;
int ret;

lzo_len = inlen + sizeof(hc_lzor_prefix);
if (lzo_len > *outlen)
return -EFBIG;
const char lzor[] = "LZOR";
const char lz77[] = "LZ77";
const char *lz_type;

/* Temporary buffer same size as the outbuf */
templen = *outlen;
tempbuf = kmalloc(templen, GFP_KERNEL);
if (!tempbuf)
return -ENOMEM;

/* Concatenate into the outbuf */
memcpy(outbuf, hc_lzor_prefix, sizeof(hc_lzor_prefix));
memcpy(outbuf + sizeof(hc_lzor_prefix), inbuf, inlen);
lzo_len = inlen;
if (magic == RB_MAGIC_LZOR)
lzo_len += sizeof(hc_lzor_prefix);
if (lzo_len > *outlen)
return -EFBIG;

switch (magic) {
case RB_MAGIC_LZOR:
lz_type = lzor;

/* LZO-decompress lzo_len bytes of outbuf into the tempbuf */
ret = lzo1x_decompress_safe(outbuf, lzo_len, tempbuf, &templen);
if (ret) {
if (LZO_E_INPUT_NOT_CONSUMED == ret) {
/*
* The tag length is always aligned thus the LZO payload may be padded,
* which can trigger a spurious error which we ignore here.
*/
pr_debug(RB_HC_PR_PFX "LZOR: LZO EOF before buffer end - this may be harmless\n");
} else {
pr_debug(RB_HC_PR_PFX "LZOR: LZO decompression error (%d)\n", ret);
/* Concatenate into the outbuf */
memcpy(outbuf, hc_lzor_prefix, sizeof(hc_lzor_prefix));
memcpy(outbuf + sizeof(hc_lzor_prefix), inbuf, inlen);

/* LZO-decompress lzo_len bytes of outbuf into the tempbuf */
ret = lzo1x_decompress_safe(outbuf, lzo_len, tempbuf, &templen);
if (ret) {
if (LZO_E_INPUT_NOT_CONSUMED == ret) {
/*
* The tag length is always aligned thus the LZO payload may be padded,
* which can trigger a spurious error which we ignore here.
*/
pr_debug(RB_HC_PR_PFX "LZOR: LZO EOF before buffer end - this may be harmless\n");
} else {
pr_debug(RB_HC_PR_PFX "LZOR: LZO decompression error (%d)\n", ret);
goto fail;
}
}
break;
case RB_MAGIC_LZ77:
lz_type = lz77;
/* LZO-decompress lzo_len bytes of inbuf into the tempbuf */
ret = rb_lz77_decompress(inbuf, inlen, tempbuf, &templen);
if (ret) {
pr_err(RB_HC_PR_PFX "LZ77: LZ77 decompress error %d\n", ret);
goto fail;
}

pr_debug(RB_HC_PR_PFX "LZ77: decompressed from %zu to %zu\n",
inlen, templen);
break;
default:
return -EINVAL;
break;
}

/*
Expand All @@ -516,7 +545,7 @@ static int hc_wlan_data_unpack_lzor(const u16 tag_id, const u8 *inbuf, size_t in
needle = (const u32 *)tempbuf;
while (RB_MAGIC_ERD != *needle++) {
if ((u8 *)needle >= tempbuf+templen) {
pr_debug(RB_HC_PR_PFX "LZOR: ERD magic not found\n");
pr_debug(RB_HC_PR_PFX "%s: ERD magic not found\n", lz_type);
ret = -ENODATA;
goto fail;
}
Expand All @@ -526,20 +555,20 @@ static int hc_wlan_data_unpack_lzor(const u16 tag_id, const u8 *inbuf, size_t in
/* Past magic. Look for tag node */
ret = routerboot_tag_find((u8 *)needle, templen, tag_id, &rle_ofs, &rle_len);
if (ret) {
pr_debug(RB_HC_PR_PFX "LZOR: no RLE data for id 0x%04x\n", tag_id);
pr_debug(RB_HC_PR_PFX "%s: no RLE data for id 0x%04x\n", lz_type, tag_id);
goto fail;
}

if (rle_len > templen) {
pr_debug(RB_HC_PR_PFX "LZOR: Invalid RLE data length\n");
pr_debug(RB_HC_PR_PFX "%s: Invalid RLE data length\n", lz_type);
ret = -EINVAL;
goto fail;
}

/* RLE-decode tempbuf from needle back into the outbuf */
ret = routerboot_rle_decode((u8 *)needle+rle_ofs, rle_len, outbuf, outlen);
if (ret)
pr_debug(RB_HC_PR_PFX "LZOR: RLE decoding error (%d)\n", ret);
pr_debug(RB_HC_PR_PFX "%s: RLE decoding error (%d)\n", lz_type, ret);

fail:
kfree(tempbuf);
Expand All @@ -562,11 +591,12 @@ static int hc_wlan_data_unpack(const u16 tag_id, const size_t tofs, size_t tlen,

ret = -ENODATA;
switch (magic) {
case RB_MAGIC_LZ77:
case RB_MAGIC_LZOR:
/* Skip magic */
lbuf += sizeof(magic);
tlen -= sizeof(magic);
ret = hc_wlan_data_unpack_lzor(tag_id, lbuf, tlen, outbuf, outlen);
ret = hc_wlan_data_unpack_lzor_lz77(tag_id, lbuf, tlen, outbuf, outlen, magic);
break;
case RB_MAGIC_ERD:
/* Skip magic */
Expand Down
Loading

0 comments on commit 6ef2e0a

Please sign in to comment.