From 41b867165d9c31105f6ac0e407b350b4622ad077 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Thibaut=20VAR=C3=88NE?= Date: Mon, 24 Aug 2020 12:38:40 +0200 Subject: [PATCH] generic: platform/mikrotik: implement multi caldata MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit MikroTik recently changed again the way they store wlan calibration data on devices. Prior to this change, ERD calibration data for all available radios was stored within a single identifier node ("tag" in RouterBoot parlance). Recent devices have been seen with calibration (and BDF) data stored in separate identifiers within LZOR packing for each radio: this patch addresses this by: 1) ensuring that both variants are properly supported, 2) preserving backward compatibility with existing data consumers, 3) allowing for more than 2 calibration blobs to be exposed via sysfs. Specifically, before this patch, the driver would provide a single sysfs file named /sys/firmware/mikrotik/hard_config/wlan_data that contained whatever calibration data found on the device's flash. After this patch, when executed on a device that uses the old style storage, this behavior is unchanged, but when executed on a device that uses new style storage (for either traditional "ERD" packing or "LZOR" packing), the driver replaces that single file with a folder containing one or more files each containing the data encoded within individual identifiers. As far as OpenWRT is concerned, this means that for devices which are known to exist with both styles of data storage, a suitable hotplug stub could look like this for e.g. the second radio: wdata="/sys/firmware/mikrotik/hard_config/wlan_data" ( [ -f "$wdata" ] && caldata_sysfsload_from_file "$wdata" 0x8000 0x2f20 ) || \ ( [ -d "$wdata" ] && caldata_sysfsload_from_file "$wdata/data_2" 0x0 0x2f20 ) This patch has been tested with LZOR old and new style packing on ipq4019, and with old style on ath79. Tested-by: John Thomson Tested-by: Шебанов Алексей Tested-by: Alen Opačić Signed-off-by: Thibaut VARÈNE Tested-by: Robert Marko --- .../drivers/platform/mikrotik/rb_hardconfig.c | 139 +++++++++++++----- 1 file changed, 106 insertions(+), 33 deletions(-) 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 8861814be440..41dea98b5e6d 100644 --- a/target/linux/generic/files/drivers/platform/mikrotik/rb_hardconfig.c +++ b/target/linux/generic/files/drivers/platform/mikrotik/rb_hardconfig.c @@ -39,7 +39,7 @@ #include "routerboot.h" -#define RB_HARDCONFIG_VER "0.05" +#define RB_HARDCONFIG_VER "0.06" #define RB_HC_PR_PFX "[rb_hardconfig] " /* ID values for hardware settings */ @@ -76,6 +76,17 @@ #define RB_HW_OPT_HAS_TS_FOR_ADC BIT(22) #define RB_HW_OPT_HAS_PLC BIT(29) +/* + * Tag ID values for ERD data. + * Mikrotik used to pack all calibration data under a single tag id 0x1, but + * recently switched to a new scheme where each radio calibration gets a + * separate tag. The new scheme has tag id bit 15 always set and seems to be + * mutually exclusive with the old scheme. + */ +#define RB_WLAN_ERD_ID_SOLO 0x0001 +#define RB_WLAN_ERD_ID_MULTI_8001 0x8001 +#define RB_WLAN_ERD_ID_MULTI_8201 0x8201 + static struct kobject *hc_kobj; static u8 *hc_buf; // ro buffer after init(): no locking required static size_t hc_buflen; @@ -351,10 +362,22 @@ static ssize_t hc_wlan_data_bin_read(struct file *filp, struct kobject *kobj, loff_t off, size_t count); static struct hc_wlan_attr { + const u16 erd_tag_id; struct bin_attribute battr; u16 pld_ofs; u16 pld_len; -} hc_wlandata_battr = { +} hc_wd_multi_battrs[] = { + { + .erd_tag_id = RB_WLAN_ERD_ID_MULTI_8001, + .battr = __BIN_ATTR(data_0, S_IRUSR, hc_wlan_data_bin_read, NULL, 0), + }, { + .erd_tag_id = RB_WLAN_ERD_ID_MULTI_8201, + .battr = __BIN_ATTR(data_2, S_IRUSR, hc_wlan_data_bin_read, NULL, 0), + } +}; + +static struct hc_wlan_attr hc_wd_solo_battr = { + .erd_tag_id = RB_WLAN_ERD_ID_SOLO, .battr = __BIN_ATTR(wlan_data, S_IRUSR, hc_wlan_data_bin_read, NULL, 0), }; @@ -426,19 +449,19 @@ static struct hc_attr { /* * If the RB_ID_WLAN_DATA payload starts with RB_MAGIC_ERD, then past * that magic number the payload itself contains a routerboot tag node - * locating the LZO-compressed calibration data at id 0x1. + * locating the LZO-compressed calibration data. So far this scheme is only + * known to use a single tag at id 0x1. */ -static int hc_wlan_data_unpack_erd(const u8 *inbuf, size_t inlen, +static int hc_wlan_data_unpack_erd(const u16 tag_id, const u8 *inbuf, size_t inlen, void *outbuf, size_t *outlen) { u16 lzo_ofs, lzo_len; int ret; /* Find embedded tag */ - ret = routerboot_tag_find(inbuf, inlen, 0x1, // always id 1 - &lzo_ofs, &lzo_len); + ret = routerboot_tag_find(inbuf, inlen, tag_id, &lzo_ofs, &lzo_len); if (ret) { - pr_debug(RB_HC_PR_PFX "ERD data not found\n"); + pr_debug(RB_HC_PR_PFX "no ERD data for id 0x%04x\n", tag_id); goto fail; } @@ -461,10 +484,10 @@ fail: * 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 RB_MAGIC_ERD magic number (aligned) must be located. Following that - * magic, there is a routerboot tag node (id 0x1) locating the RLE-encoded + * 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 u8 *inbuf, size_t inlen, +static int hc_wlan_data_unpack_lzor(const u16 tag_id, const u8 *inbuf, size_t inlen, void *outbuf, size_t *outlen) { u16 rle_ofs, rle_len; @@ -492,10 +515,8 @@ static int hc_wlan_data_unpack_lzor(const u8 *inbuf, size_t inlen, if (ret) { if (LZO_E_INPUT_NOT_CONSUMED == ret) { /* - * The tag length appears to always be aligned (probably - * because it is the "root" RB_ID_WLAN_DATA tag), thus - * the LZO payload may be padded, which can trigger a - * spurious error which we ignore here. + * 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 { @@ -520,9 +541,9 @@ static int hc_wlan_data_unpack_lzor(const u8 *inbuf, size_t inlen, templen -= (u8 *)needle - tempbuf; /* Past magic. Look for tag node */ - ret = routerboot_tag_find((u8 *)needle, templen, 0x1, &rle_ofs, &rle_len); + ret = routerboot_tag_find((u8 *)needle, templen, tag_id, &rle_ofs, &rle_len); if (ret) { - pr_debug(RB_HC_PR_PFX "LZOR: RLE data not found\n"); + pr_debug(RB_HC_PR_PFX "LZOR: no RLE data for id 0x%04x\n", tag_id); goto fail; } @@ -542,7 +563,7 @@ fail: return ret; } -static int hc_wlan_data_unpack(const size_t tofs, size_t tlen, +static int hc_wlan_data_unpack(const u16 tag_id, const size_t tofs, size_t tlen, void *outbuf, size_t *outlen) { const u8 *lbuf; @@ -562,23 +583,25 @@ static int hc_wlan_data_unpack(const size_t tofs, size_t tlen, /* Skip magic */ lbuf += sizeof(magic); tlen -= sizeof(magic); - ret = hc_wlan_data_unpack_lzor(lbuf, tlen, outbuf, outlen); + ret = hc_wlan_data_unpack_lzor(tag_id, lbuf, tlen, outbuf, outlen); break; case RB_MAGIC_ERD: /* Skip magic */ lbuf += sizeof(magic); tlen -= sizeof(magic); - ret = hc_wlan_data_unpack_erd(lbuf, tlen, outbuf, outlen); + ret = hc_wlan_data_unpack_erd(tag_id, lbuf, tlen, outbuf, outlen); break; default: /* * If the RB_ID_WLAN_DATA payload doesn't start with a * magic number, the payload itself is the raw RLE-encoded - * calibration data. + * calibration data. Only RB_WLAN_ERD_ID_SOLO makes sense here. */ - ret = routerboot_rle_decode(lbuf, tlen, outbuf, outlen); - if (ret) - pr_debug(RB_HC_PR_PFX "RLE decoding error (%d)\n", ret); + if (RB_WLAN_ERD_ID_SOLO == tag_id) { + ret = routerboot_rle_decode(lbuf, tlen, outbuf, outlen); + if (ret) + pr_debug(RB_HC_PR_PFX "RLE decoding error (%d)\n", ret); + } break; } @@ -633,7 +656,7 @@ static ssize_t hc_wlan_data_bin_read(struct file *filp, struct kobject *kobj, if (!outbuf) return -ENOMEM; - ret = hc_wlan_data_unpack(hc_wattr->pld_ofs, hc_wattr->pld_len, outbuf, &outlen); + ret = hc_wlan_data_unpack(hc_wattr->erd_tag_id, hc_wattr->pld_ofs, hc_wattr->pld_len, outbuf, &outlen); if (ret) { kfree(outbuf); return ret; @@ -655,14 +678,17 @@ static ssize_t hc_wlan_data_bin_read(struct file *filp, struct kobject *kobj, int __init rb_hardconfig_init(struct kobject *rb_kobj) { + struct kobject *hc_wlan_kobj; struct mtd_info *mtd; - size_t bytes_read, buflen; + size_t bytes_read, buflen, outlen; const u8 *buf; - int i, ret; + void *outbuf; + int i, j, ret; u32 magic; hc_buf = NULL; hc_kobj = NULL; + hc_wlan_kobj = NULL; // TODO allow override mtd = get_mtd_device_nm(RB_MTD_HARD_CONFIG); @@ -713,15 +739,62 @@ int __init rb_hardconfig_init(struct kobject *rb_kobj) /* Account for skipped magic */ hc_attrs[i].pld_ofs += sizeof(magic); - /* Special case RB_ID_WLAN_DATA to prep and create the binary attribute */ + /* + * Special case RB_ID_WLAN_DATA to prep and create the binary attribute. + * We first check if the data is "old style" within a single tag (or no tag at all): + * If it is we publish this single blob as a binary attribute child of hc_kobj to + * preserve backward compatibility. + * If it isn't and instead uses multiple ERD tags, we create a subfolder and + * publish the known ones there. + */ if ((RB_ID_WLAN_DATA == hc_attrs[i].tag_id) && hc_attrs[i].pld_len) { - hc_wlandata_battr.pld_ofs = hc_attrs[i].pld_ofs; - hc_wlandata_battr.pld_len = hc_attrs[i].pld_len; - - ret = sysfs_create_bin_file(hc_kobj, &hc_wlandata_battr.battr); - if (ret) - pr_warn(RB_HC_PR_PFX "Could not create %s sysfs entry (%d)\n", - hc_wlandata_battr.battr.attr.name, ret); + outlen = RB_ART_SIZE; + outbuf = kmalloc(outlen, GFP_KERNEL); + if (!outbuf) { + pr_warn(RB_HC_PR_PFX "Out of memory parsing WLAN tag\n"); + continue; + } + + /* Test ID_SOLO first, if found: done */ + ret = hc_wlan_data_unpack(RB_WLAN_ERD_ID_SOLO, hc_attrs[i].pld_ofs, hc_attrs[i].pld_len, outbuf, &outlen); + if (!ret) { + hc_wd_solo_battr.pld_ofs = hc_attrs[i].pld_ofs; + hc_wd_solo_battr.pld_len = hc_attrs[i].pld_len; + + ret = sysfs_create_bin_file(hc_kobj, &hc_wd_solo_battr.battr); + if (ret) + pr_warn(RB_HC_PR_PFX "Could not create %s sysfs entry (%d)\n", + hc_wd_solo_battr.battr.attr.name, ret); + } + /* Otherwise, create "wlan_data" subtree and publish known data */ + else { + hc_wlan_kobj = kobject_create_and_add("wlan_data", hc_kobj); + if (!hc_wlan_kobj) { + kfree(outbuf); + pr_warn(RB_HC_PR_PFX "Could not create wlan_data sysfs folder\n"); + continue; + } + + for (j = 0; j < ARRAY_SIZE(hc_wd_multi_battrs); j++) { + outlen = RB_ART_SIZE; + ret = hc_wlan_data_unpack(hc_wd_multi_battrs[j].erd_tag_id, + hc_attrs[i].pld_ofs, hc_attrs[i].pld_len, outbuf, &outlen); + if (ret) { + hc_wd_multi_battrs[j].pld_ofs = hc_wd_multi_battrs[j].pld_len = 0; + continue; + } + + hc_wd_multi_battrs[j].pld_ofs = hc_attrs[i].pld_ofs; + hc_wd_multi_battrs[j].pld_len = hc_attrs[i].pld_len; + + ret = sysfs_create_bin_file(hc_wlan_kobj, &hc_wd_multi_battrs[j].battr); + if (ret) + pr_warn(RB_HC_PR_PFX "Could not create wlan_data/%s sysfs entry (%d)\n", + hc_wd_multi_battrs[j].battr.attr.name, ret); + } + } + + kfree(outbuf); } /* All other tags are published via standard attributes */ else { -- 2.30.2