generic: platform/mikrotik: implement multi caldata
authorThibaut VARÈNE <hacks@slashdirt.org>
Tue, 18 Aug 2020 08:46:27 +0000 (10:46 +0200)
committerAlexander Couzens <lynxis@fe80.eu>
Fri, 18 Sep 2020 23:33:06 +0000 (01:33 +0200)
WIP

Signed-off-by: Thibaut VARÈNE <hacks@slashdirt.org>
target/linux/generic/files/drivers/platform/mikrotik/rb_hardconfig.c

index 8861814be440ac052539637f8737daf094c00f5c..899fd5872b12cc1b02a34a46d4c15514bb357799 100644 (file)
@@ -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 */
 #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_battrs = {
+       .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 only uses
+ * 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_battrs.pld_ofs = hc_attrs[i].pld_ofs;
+                               hc_wd_solo_battrs.pld_len = hc_attrs[i].pld_len;
+
+                               ret = sysfs_create_bin_file(hc_kobj, &hc_wd_solo_battrs.battr);
+                               if (ret)
+                                       pr_warn(RB_HC_PR_PFX "Could not create %s sysfs entry (%d)\n",
+                                              hc_wd_solo_battrs.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 {