From 7d33aedd10bab0d8023d608f6f900b2915c69456 Mon Sep 17 00:00:00 2001 From: John Thomson Date: Thu, 30 May 2024 15:57:00 +1000 Subject: [PATCH] generic: platform/mikrotik: add wlan lz77 decompress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Link: https://github.com/openwrt/openwrt/pull/15774 Signed-off-by: Robert Marko --- target/linux/ath79/mikrotik/config-default | 1 + .../files/drivers/platform/mikrotik/Kconfig | 7 + .../files/drivers/platform/mikrotik/Makefile | 1 + .../drivers/platform/mikrotik/rb_hardconfig.c | 90 ++-- .../files/drivers/platform/mikrotik/rb_lz77.c | 446 ++++++++++++++++++ .../files/drivers/platform/mikrotik/rb_lz77.h | 35 ++ .../drivers/platform/mikrotik/routerboot.h | 1 + target/linux/ipq40xx/mikrotik/config-default | 1 + target/linux/mvebu/cortexa72/config-6.6 | 1 + target/linux/ramips/mt7621/config-6.6 | 1 + 10 files changed, 557 insertions(+), 27 deletions(-) create mode 100644 target/linux/generic/files/drivers/platform/mikrotik/rb_lz77.c create mode 100644 target/linux/generic/files/drivers/platform/mikrotik/rb_lz77.h diff --git a/target/linux/ath79/mikrotik/config-default b/target/linux/ath79/mikrotik/config-default index 0dd79d9adc0e4..2976c244706b6 100644 --- a/target/linux/ath79/mikrotik/config-default +++ b/target/linux/ath79/mikrotik/config-default @@ -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 diff --git a/target/linux/generic/files/drivers/platform/mikrotik/Kconfig b/target/linux/generic/files/drivers/platform/mikrotik/Kconfig index 32ef8f29de86d..85fe7b20503ca 100644 --- a/target/linux/generic/files/drivers/platform/mikrotik/Kconfig +++ b/target/linux/generic/files/drivers/platform/mikrotik/Kconfig @@ -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 diff --git a/target/linux/generic/files/drivers/platform/mikrotik/Makefile b/target/linux/generic/files/drivers/platform/mikrotik/Makefile index 164b23b70160e..9ffb355c1e3dd 100644 --- a/target/linux/generic/files/drivers/platform/mikrotik/Makefile +++ b/target/linux/generic/files/drivers/platform/mikrotik/Makefile @@ -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 diff --git a/target/linux/generic/files/drivers/platform/mikrotik/rb_hardconfig.c b/target/linux/generic/files/drivers/platform/mikrotik/rb_hardconfig.c index 78e39a7f94828..4c1edad081787 100644 --- a/target/linux/generic/files/drivers/platform/mikrotik/rb_hardconfig.c +++ b/target/linux/generic/files/drivers/platform/mikrotik/rb_hardconfig.c @@ -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 */ @@ -465,23 +466,24 @@ 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; @@ -489,23 +491,50 @@ static int hc_wlan_data_unpack_lzor(const u16 tag_id, const u8 *inbuf, size_t in 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; - /* 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); + switch (magic) { + case RB_MAGIC_LZOR: + lz_type = lzor; + + /* 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; } /* @@ -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_warn(RB_HC_PR_PFX "%s: ERD magic not found. Decompressed first word: 0x%08x\n", lz_type, *(u32 *)tempbuf); ret = -ENODATA; goto fail; } @@ -526,12 +555,12 @@ 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; } @@ -539,7 +568,7 @@ static int hc_wlan_data_unpack_lzor(const u16 tag_id, const u8 *inbuf, size_t in /* 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); @@ -562,11 +591,18 @@ 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: + /* no known instances of lz77 without 8001/8201 data, skip SOLO */ + if (tag_id == RB_WLAN_ERD_ID_SOLO) { + pr_debug(RB_HC_PR_PFX "skipped LZ77 decompress in search for SOLO tag\n"); + break; + } + fallthrough; 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 */ diff --git a/target/linux/generic/files/drivers/platform/mikrotik/rb_lz77.c b/target/linux/generic/files/drivers/platform/mikrotik/rb_lz77.c new file mode 100644 index 0000000000000..d443adb128ee3 --- /dev/null +++ b/target/linux/generic/files/drivers/platform/mikrotik/rb_lz77.c @@ -0,0 +1,446 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 John Thomson + */ + +#include +#include +#include +#include +#include + +#include "rb_lz77.h" + +#define MIKRO_LZ77 "[rb lz77] " + +/* + * The maximum number of bits used in a counter. + * For the look behind window, long instruction match offsets + * up to 6449 have been seen in provided compressed caldata blobs + * (that would need 21 counter bits: 4 to 12 + 11 to 0). + * conservative value here: 27 provides offset up to 0x8000 bytes + * uses a u8 in this code + */ +#define MIKRO_LZ77_MAX_COUNT_BIT_LEN 27 + +enum rb_lz77_instruction { + INSTR_ERROR = -1, + INSTR_LITERAL_BYTE = 0, + /* a (non aligned) byte follows this instruction, + * which is directly copied into output + */ + INSTR_PREVIOUS_OFFSET = 1, + /* this group is a match, with a bytes length defined by + * following counter bits, starting at bitshift 0, + * less the built-in count of 1 + * using the previous offset as source + */ + INSTR_LONG = 2 + /* this group has two counters, + * the first counter starts at bitshift 4, + * if this counter == 0, this is a non-matching group + * the second counter (bytes length) starts at bitshift 4, + * less the built-in count of 11+1. + * The final match group has this count 0, + * and following bits which pad to byte-alignment. + * + * if this counter > 0, this is a matching group + * this first count is the match offset (in bytes) + * the second count is the match length (in bytes), + * less the built-in count of 2 + * these groups can source bytes that are part of this group + */ +}; + +struct rb_lz77_instr_opcodes { + /* group instruction */ + enum rb_lz77_instruction instruction; + /* if >0, a match group, + * which starts at byte output_position - 1*offset + */ + size_t offset; + /* how long the match group is, + * or how long the (following counter) non-match group is + */ + size_t length; + /* how many bits were used for this instruction + op code(s) */ + size_t bits_used; + /* input char */ + u8 *in; + /* offset where this instruction started */ + size_t in_pos; +}; + +/** + * rb_lz77_get_bit + * + * @in: compressed data ptr + * @in_offset_bit: bit offset to extract + * + * convert the bit offset to byte offset, + * shift to modulo of bits per bytes, so that wanted bit is lsb + * and to extract only that bit. + * Caller is responsible for ensuring that in_offset_bit/8 + * does not exceed input length + */ +static inline u8 rb_lz77_get_bit(const u8 *in, const size_t in_offset_bit) +{ + return ((in[in_offset_bit / BITS_PER_BYTE] >> + (in_offset_bit % BITS_PER_BYTE)) & + 1); +} + +/** + * rb_lz77_get_byte + * + * @in: compressed data + * @in_offset_bit: bit offset to extract byte + */ +static inline u8 rb_lz77_get_byte(const u8 *in, const size_t in_offset_bit) +{ + u8 buf = 0; + int i; + + /* built a reversed byte from (likely) unaligned bits */ + for (i = 0; i <= 7; ++i) + buf += rb_lz77_get_bit(in, in_offset_bit + i) << (7 - i); + return buf; +} + +/** + * rb_lz77_decode_count - decode bits at given offset as a count + * + * @in: compressed data + * @in_len: length of compressed data + * @in_offset_bit: bit offset where count starts + * @shift: left shift operand value of first count bit + * @count: initial count + * @bits_used: how many bits were consumed by this count + * @max_bits: maximum bit count for this counter + * + * Returns the decoded count + */ +static int rb_lz77_decode_count(const u8 *in, const size_t in_len, + const size_t in_offset_bit, u8 shift, + size_t count, u8 *bits_used, const u8 max_bits) +{ + size_t pos = in_offset_bit; + const size_t max_pos = min(pos + max_bits, in_len * BITS_PER_BYTE); + bool up = true; + + *bits_used = 0; + pr_debug(MIKRO_LZ77 + "decode_count inbit: %zu, start shift:%u, initial count:%zu\n", + in_offset_bit, shift, count); + + while (true) { + /* check the input offset bit does not overflow the minimum of + * a reasonable length for this encoded count, and + * the end of the input */ + if (unlikely(pos >= max_pos)) { + pr_err(MIKRO_LZ77 + "max bit index reached before count completed\n"); + return -EFBIG; + } + + /* if the bit value at offset is set */ + if (rb_lz77_get_bit(in, pos)) + count += (1 << shift); + + /* shift increases until we find an unsed bit */ + else if (up) + up = false; + + if (up) + ++shift; + else { + if (!shift) { + *bits_used = pos - in_offset_bit + 1; + return count; + } + --shift; + } + + ++pos; + } + + return -EINVAL; +} + +/** + * rb_lz77_decode_instruction + * + * @in: compressed data + * @in_offset_bit: bit offset where instruction starts + * @bits_used: how many bits were consumed by this count + * + * Returns the decoded instruction + */ +static enum rb_lz77_instruction +rb_lz77_decode_instruction(const u8 *in, size_t in_offset_bit, u8 *bits_used) +{ + if (rb_lz77_get_bit(in, in_offset_bit)) { + *bits_used = 2; + if (rb_lz77_get_bit(in, ++in_offset_bit)) + return INSTR_LONG; + else + return INSTR_PREVIOUS_OFFSET; + } else { + *bits_used = 1; + return INSTR_LITERAL_BYTE; + } + return INSTR_ERROR; +} + +/** + * rb_lz77_decode_instruction_operators + * + * @in: compressed data + * @in_len: length of compressed data + * @in_offset_bit: bit offset where instruction starts + * @previous_offset: last used match offset + * @opcode: struct to hold instruction & operators + * + * Returns error code + */ +static int rb_lz77_decode_instruction_operators( + const u8 *in, const size_t in_len, const size_t in_offset_bit, + const size_t previous_offset, struct rb_lz77_instr_opcodes *opcode) +{ + enum rb_lz77_instruction instruction; + u8 bit_count = 0; + u8 bits_used = 0; + int offset = 0; + int length = 0; + + instruction = rb_lz77_decode_instruction(in, in_offset_bit, &bit_count); + + /* skip bits used by instruction */ + bits_used += bit_count; + + switch (instruction) { + case INSTR_LITERAL_BYTE: + /* non-matching char */ + offset = 0; + length = 1; + break; + + case INSTR_PREVIOUS_OFFSET: + /* matching group uses previous offset */ + offset = previous_offset; + + length = rb_lz77_decode_count(in, in_len, + in_offset_bit + bits_used, 0, 1, + &bit_count, + MIKRO_LZ77_MAX_COUNT_BIT_LEN); + if (unlikely(length < 0)) + return length; + /* skip bits used by count */ + bits_used += bit_count; + break; + + case INSTR_LONG: + offset = rb_lz77_decode_count(in, in_len, + in_offset_bit + bits_used, 4, 0, + &bit_count, + MIKRO_LZ77_MAX_COUNT_BIT_LEN); + if (unlikely(offset < 0)) + return offset; + + /* skip bits used by offset count */ + bits_used += bit_count; + + if (offset == 0) { + /* non-matching long group */ + length = rb_lz77_decode_count( + in, in_len, in_offset_bit + bits_used, 4, 12, + &bit_count, MIKRO_LZ77_MAX_COUNT_BIT_LEN); + if (unlikely(length < 0)) + return length; + /* skip bits used by length count */ + bits_used += bit_count; + } else { + /* matching group */ + length = rb_lz77_decode_count( + in, in_len, in_offset_bit + bits_used, 0, 2, + &bit_count, MIKRO_LZ77_MAX_COUNT_BIT_LEN); + if (unlikely(length < 0)) + return length; + /* skip bits used by length count */ + bits_used += bit_count; + } + + break; + + case INSTR_ERROR: + return -EINVAL; + } + + opcode->instruction = instruction; + opcode->offset = offset; + opcode->length = length; + opcode->bits_used = bits_used; + opcode->in = (u8 *)in; + opcode->in_pos = in_offset_bit; + return 0; +} + +/** + * rb_lz77_decompress + * + * @in: compressed data ptr + * @in_len: length of compressed data + * @out: buffer ptr to decompress into + * @out_len: length of decompressed buffer in input, + * length of decompressed data in success + * + * Returns 0 on success, or negative error + */ +int rb_lz77_decompress(const u8 *in, const size_t in_len, u8 *out, + size_t *out_len) +{ + u8 *output_ptr; + size_t input_bit = 0; + const u8 *output_end = out + *out_len; + struct rb_lz77_instr_opcodes *opcode; + size_t match_offset = 0; + int rc = 0; + size_t match_length, partial_count, i; + + output_ptr = out; + + if (unlikely((in_len * BITS_PER_BYTE) > SIZE_MAX)) { + pr_err(MIKRO_LZ77 "input longer than expected\n"); + return -EFBIG; + } + + opcode = kmalloc(sizeof(*opcode), GFP_KERNEL); + if (!opcode) + return -ENOMEM; + + while (true) { + if (unlikely(output_ptr > output_end)) { + pr_err(MIKRO_LZ77 "output overrun\n"); + rc = -EOVERFLOW; + goto free_lz77_struct; + } + if (unlikely(input_bit > in_len * BITS_PER_BYTE)) { + pr_err(MIKRO_LZ77 "input overrun\n"); + rc = -ENODATA; + goto free_lz77_struct; + } + + rc = rb_lz77_decode_instruction_operators(in, in_len, input_bit, + match_offset, opcode); + if (unlikely(rc < 0)) { + pr_err(MIKRO_LZ77 + "instruction operands decode error\n"); + goto free_lz77_struct; + } + + pr_debug(MIKRO_LZ77 "inbit:0x%zx->outbyte:0x%zx", input_bit, + output_ptr - out); + + input_bit += opcode->bits_used; + switch (opcode->instruction) { + case INSTR_LITERAL_BYTE: + pr_debug(" short"); + fallthrough; + case INSTR_LONG: + if (opcode->offset == 0) { + /* this is a non-matching group */ + pr_debug(" non-match, len: 0x%zx\n", + opcode->length); + /* test end marker */ + if (opcode->length == 0xc && + ((input_bit + + opcode->length * BITS_PER_BYTE) > + in_len)) { + *out_len = output_ptr - out; + pr_debug( + MIKRO_LZ77 + "lz77 decompressed from %zu to %zu\n", + in_len, *out_len); + rc = 0; + goto free_lz77_struct; + } + for (i = opcode->length; i > 0; --i) { + *output_ptr = + rb_lz77_get_byte(in, input_bit); + ++output_ptr; + input_bit += BITS_PER_BYTE; + } + /* do no fallthrough if a non-match group */ + break; + } + match_offset = opcode->offset; + fallthrough; + case INSTR_PREVIOUS_OFFSET: + match_length = opcode->length; + partial_count = 0; + + pr_debug(" match, offset: 0x%zx, len: 0x%zx", + opcode->offset, match_length); + + if (unlikely(opcode->offset == 0)) { + pr_err(MIKRO_LZ77 + "match group missing opcode->offset\n"); + rc = -EBADMSG; + goto free_lz77_struct; + } + + /* overflow */ + if (unlikely((output_ptr + match_length) > + output_end)) { + pr_err(MIKRO_LZ77 + "match group output overflow\n"); + rc = -ENOBUFS; + goto free_lz77_struct; + } + + /* underflow */ + if (unlikely((output_ptr - opcode->offset) < out)) { + pr_err(MIKRO_LZ77 + "match group offset underflow\n"); + rc = -ESPIPE; + goto free_lz77_struct; + } + + /* there are cases where the match (length) includes + * data that is a part of the same match + */ + while (opcode->offset < match_length) { + ++partial_count; + memcpy(output_ptr, output_ptr - opcode->offset, + opcode->offset); + output_ptr += opcode->offset; + match_length -= opcode->offset; + } + memcpy(output_ptr, output_ptr - opcode->offset, + match_length); + output_ptr += match_length; + if (partial_count) + pr_debug(" (%zu partial memcpy)", + partial_count); + pr_debug("\n"); + + break; + + case INSTR_ERROR: + rc = -EINVAL; + goto free_lz77_struct; + } + } + + pr_err(MIKRO_LZ77 "decode loop broken\n"); + rc = -EINVAL; + +free_lz77_struct: + kfree(opcode); + return rc; +} +EXPORT_SYMBOL_GPL(rb_lz77_decompress); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Mikrotik Wi-Fi caldata LZ77 decompressor"); +MODULE_AUTHOR("John Thomson"); diff --git a/target/linux/generic/files/drivers/platform/mikrotik/rb_lz77.h b/target/linux/generic/files/drivers/platform/mikrotik/rb_lz77.h new file mode 100644 index 0000000000000..55179fcbc8e01 --- /dev/null +++ b/target/linux/generic/files/drivers/platform/mikrotik/rb_lz77.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024 John Thomson + */ + +#ifndef __MIKROTIK_WLAN_LZ77_H__ +#define __MIKROTIK_WLAN_LZ77_H__ + +#include + +#ifdef CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77 +/** + * rb_lz77_decompress + * + * @in: compressed data ptr + * @in_len: length of compressed data + * @out: buffer ptr to decompress into + * @out_len: length of decompressed buffer in input, + * length of decompressed data in success + * + * Returns 0 on success, or negative error + */ +int rb_lz77_decompress(const u8 *in, const size_t in_len, u8 *out, + size_t *out_len); + +#else /* CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77 */ + +static inline int rb_lz77_decompress(const u8 *in, const size_t in_len, u8 *out, + size_t *out_len) +{ + return -EOPNOTSUPP; +} + +#endif /* CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77 */ +#endif /* __MIKROTIK_WLAN_LZ77_H__ */ diff --git a/target/linux/generic/files/drivers/platform/mikrotik/routerboot.h b/target/linux/generic/files/drivers/platform/mikrotik/routerboot.h index e858a524af43f..723f993eebe2c 100644 --- a/target/linux/generic/files/drivers/platform/mikrotik/routerboot.h +++ b/target/linux/generic/files/drivers/platform/mikrotik/routerboot.h @@ -15,6 +15,7 @@ #define RB_MAGIC_HARD (('H') | ('a' << 8) | ('r' << 16) | ('d' << 24)) #define RB_MAGIC_SOFT (('S') | ('o' << 8) | ('f' << 16) | ('t' << 24)) #define RB_MAGIC_LZOR (('L') | ('Z' << 8) | ('O' << 16) | ('R' << 24)) +#define RB_MAGIC_LZ77 (('L' << 24) | ('Z' << 16) | ('7' << 8) | ('7')) #define RB_MAGIC_ERD (('E' << 16) | ('R' << 8) | ('D')) #define RB_ART_SIZE 0x10000 diff --git a/target/linux/ipq40xx/mikrotik/config-default b/target/linux/ipq40xx/mikrotik/config-default index 805e6db23bb46..7234e4b8f6122 100644 --- a/target/linux/ipq40xx/mikrotik/config-default +++ b/target/linux/ipq40xx/mikrotik/config-default @@ -1,5 +1,6 @@ CONFIG_MIKROTIK=y CONFIG_MIKROTIK_RB_SYSFS=y +CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77=y CONFIG_MTD_ROUTERBOOT_PARTS=y CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE=y CONFIG_MTD_SPLIT_MINOR_FW=y diff --git a/target/linux/mvebu/cortexa72/config-6.6 b/target/linux/mvebu/cortexa72/config-6.6 index 19ca2b29d1b13..af9dcf36329b6 100644 --- a/target/linux/mvebu/cortexa72/config-6.6 +++ b/target/linux/mvebu/cortexa72/config-6.6 @@ -63,6 +63,7 @@ CONFIG_MMC_SDHCI_XENON=y CONFIG_MODULES_USE_ELF_RELA=y CONFIG_MIKROTIK=y CONFIG_MIKROTIK_RB_SYSFS=y +# CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77 is not set CONFIG_MTD_ROUTERBOOT_PARTS=y CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE=y CONFIG_MVEBU_GICP=y diff --git a/target/linux/ramips/mt7621/config-6.6 b/target/linux/ramips/mt7621/config-6.6 index 609e520c62ac8..adbb7c846540b 100644 --- a/target/linux/ramips/mt7621/config-6.6 +++ b/target/linux/ramips/mt7621/config-6.6 @@ -128,6 +128,7 @@ CONFIG_MFD_SYSCON=y CONFIG_MIGRATION=y CONFIG_MIKROTIK=y CONFIG_MIKROTIK_RB_SYSFS=y +# CONFIG_MIKROTIK_WLAN_DECOMPRESS_LZ77 is not set CONFIG_MIPS=y CONFIG_MIPS_ASID_BITS=8 CONFIG_MIPS_ASID_SHIFT=0