diff options
-rw-r--r-- | target/linux/generic/files/drivers/platform/mikrotik/rb_hardconfig.c | 139 |
1 files 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 8861814be4..41dea98b5e 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 { |