generic: routerboot sysfs platform driver
authorThibaut VARÈNE <hacks@slashdirt.org>
Sun, 22 Mar 2020 20:46:42 +0000 (21:46 +0100)
committerKoen Vandeputte <koen.vandeputte@ncentric.com>
Tue, 12 May 2020 10:43:38 +0000 (12:43 +0200)
This driver exposes the data encoded in the "hard_config" flash segment
of MikroTik RouterBOARDs devices. It presents the data in a sysfs folder
named "hard_config". The WLAN calibration data is available on demand via
the 'wlan_data' sysfs file in that folder.

This driver permanently allocates a chunk of RAM as large as the
"hard_config" MTD partition (typically 4KB), although it is technically
possible to operate entirely from the MTD device without using a local
buffer (except when requesting WLAN calibration data), at the cost of a
performance penalty.

This driver does not reuse any of the existing code previously found in
routerboot.c.

This driver has been successfully tested on BE (ath79) and LE (ipq40xx
and ramips) hardware.

Tested-by: Roger Pueyo Centelles <roger.pueyo@guifi.net>
Tested-by: Baptiste Jonglez <git@bitsofnetworks.org>
Tested-by: Tobias Schramm <t.schramm@manjaro.org>
Tested-by: Christopher Hill <ch6574@gmail.com>
Signed-off-by: Thibaut VARÈNE <hacks@slashdirt.org>
target/linux/generic/files/drivers/platform/mikrotik/Kconfig [new file with mode: 0644]
target/linux/generic/files/drivers/platform/mikrotik/Makefile [new file with mode: 0644]
target/linux/generic/files/drivers/platform/mikrotik/rb_hardconfig.c [new file with mode: 0644]
target/linux/generic/files/drivers/platform/mikrotik/routerboot.c [new file with mode: 0644]
target/linux/generic/files/drivers/platform/mikrotik/routerboot.h [new file with mode: 0644]

diff --git a/target/linux/generic/files/drivers/platform/mikrotik/Kconfig b/target/linux/generic/files/drivers/platform/mikrotik/Kconfig
new file mode 100644 (file)
index 0000000..195a1e8
--- /dev/null
@@ -0,0 +1,18 @@
+menuconfig MIKROTIK
+       bool "Platform support for MikroTik RouterBoard virtual devices"
+       default n
+       depends on MTD
+       select LZO_DECOMPRESS
+       help
+         Say Y here to get to see options for the MikroTik RouterBoard platform.
+         This option alone does not add any kernel code.
+
+
+if MIKROTIK
+
+config MIKROTIK_RB_SYSFS
+       tristate "RouterBoot sysfs support"
+       help
+         This driver exposes RouterBoot configuration in sysfs.
+
+endif # MIKROTIK
diff --git a/target/linux/generic/files/drivers/platform/mikrotik/Makefile b/target/linux/generic/files/drivers/platform/mikrotik/Makefile
new file mode 100644 (file)
index 0000000..4d50ede
--- /dev/null
@@ -0,0 +1,4 @@
+#
+# Makefile for MikroTik RouterBoard platform specific drivers
+#
+obj-$(CONFIG_MIKROTIK_RB_SYSFS)     += routerboot.o rb_hardconfig.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
new file mode 100644 (file)
index 0000000..0aa8cb1
--- /dev/null
@@ -0,0 +1,481 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for MikroTik RouterBoot hard config.
+ *
+ * Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ * This driver exposes the data encoded in the "hard_config" flash segment of
+ * MikroTik RouterBOARDs devices. It presents the data in a sysfs folder
+ * named "hard_config". The WLAN calibration data is available on demand via
+ * the 'wlan_data' sysfs file in that folder.
+ *
+ * This driver permanently allocates a chunk of RAM as large as the hard_config
+ * MTD partition, although it is technically possible to operate entirely from
+ * the MTD device without using a local buffer (except when requesting WLAN
+ * calibration data), at the cost of a performance penalty.
+ *
+ * Some constant defines extracted from routerboot.{c,h} by Gabor Juhos
+ * <juhosg@openwrt.org>
+ */
+
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/kobject.h>
+#include <linux/bitops.h>
+#include <linux/string.h>
+#include <linux/mtd/mtd.h>
+#include <linux/sysfs.h>
+#include <linux/lzo.h>
+
+#include "routerboot.h"
+
+#define RB_HARDCONFIG_VER              "0.01"
+#define RB_HC_PR_PFX                   "[rb_hardconfig] "
+
+/* ID values for hardware settings */
+#define RB_ID_FLASH_INFO               0x03
+#define RB_ID_MAC_ADDRESS_PACK         0x04
+#define RB_ID_BOARD_PRODUCT_CODE       0x05
+#define RB_ID_BIOS_VERSION             0x06
+#define RB_ID_SDRAM_TIMINGS            0x08
+#define RB_ID_DEVICE_TIMINGS           0x09
+#define RB_ID_SOFTWARE_ID              0x0A
+#define RB_ID_SERIAL_NUMBER            0x0B
+#define RB_ID_MEMORY_SIZE              0x0D
+#define RB_ID_MAC_ADDRESS_COUNT                0x0E
+#define RB_ID_HW_OPTIONS               0x15
+#define RB_ID_WLAN_DATA                        0x16
+#define RB_ID_BOARD_IDENTIFIER         0x17
+#define RB_ID_PRODUCT_NAME             0x21
+#define RB_ID_DEFCONF                  0x26
+
+/* Bit definitions for hardware options */
+#define RB_HW_OPT_NO_UART              BIT(0)
+#define RB_HW_OPT_HAS_VOLTAGE          BIT(1)
+#define RB_HW_OPT_HAS_USB              BIT(2)
+#define RB_HW_OPT_HAS_ATTINY           BIT(3)
+#define RB_HW_OPT_NO_NAND              BIT(14)
+#define RB_HW_OPT_HAS_LCD              BIT(15)
+#define RB_HW_OPT_HAS_POE_OUT          BIT(16)
+#define RB_HW_OPT_HAS_uSD              BIT(17)
+#define RB_HW_OPT_HAS_SIM              BIT(18)
+#define RB_HW_OPT_HAS_SFP              BIT(20)
+#define RB_HW_OPT_HAS_WIFI             BIT(21)
+#define RB_HW_OPT_HAS_TS_FOR_ADC       BIT(22)
+#define RB_HW_OPT_HAS_PLC              BIT(29)
+
+static struct kobject *hc_kobj;
+static u8 *hc_buf;             // ro buffer after init(): no locking required
+static size_t hc_buflen;
+
+/* Array of known hw_options bits with human-friendly parsing */
+static struct hc_hwopt {
+       const u32 bit;
+       const char *str;
+} const hc_hwopts[] = {
+       {
+               .bit = RB_HW_OPT_NO_UART,
+               .str = "no UART\t\t",
+       }, {
+               .bit = RB_HW_OPT_HAS_VOLTAGE,
+               .str = "has Vreg\t",
+       }, {
+               .bit = RB_HW_OPT_HAS_USB,
+               .str = "has usb\t\t",
+       }, {
+               .bit = RB_HW_OPT_HAS_ATTINY,
+               .str = "has ATtiny\t",
+       }, {
+               .bit = RB_HW_OPT_NO_NAND,
+               .str = "no NAND\t\t",
+       }, {
+               .bit = RB_HW_OPT_HAS_LCD,
+               .str = "has LCD\t\t",
+       }, {
+               .bit = RB_HW_OPT_HAS_POE_OUT,
+               .str = "has POE out\t",
+       }, {
+               .bit = RB_HW_OPT_HAS_uSD,
+               .str = "has MicroSD\t",
+       }, {
+               .bit = RB_HW_OPT_HAS_SIM,
+               .str = "has SIM\t\t",
+       }, {
+               .bit = RB_HW_OPT_HAS_SFP,
+               .str = "has SFP\t\t",
+       }, {
+               .bit = RB_HW_OPT_HAS_WIFI,
+               .str = "has WiFi\t",
+       }, {
+               .bit = RB_HW_OPT_HAS_TS_FOR_ADC,
+               .str = "has TS ADC\t",
+       }, {
+               .bit = RB_HW_OPT_HAS_PLC,
+               .str = "has PLC\t\t",
+       },
+};
+
+static ssize_t hc_tag_show_string(const u8 *pld, u16 pld_len, char *buf)
+{
+       return snprintf(buf, pld_len+1, "%s\n", pld);
+}
+
+static ssize_t hc_tag_show_u32(const u8 *pld, u16 pld_len, char *buf)
+{
+       char *out = buf;
+       u32 data;       // cpu-endian
+
+       /* Caller ensures pld_len > 0 */
+       if (pld_len % sizeof(data))
+               return -EINVAL;
+
+       data = *(u32 *)pld;
+
+       do {
+               out += sprintf(out, "0x%08x\n", data);
+               data++;
+       } while ((pld_len -= sizeof(data)));
+
+       return out - buf;
+}
+
+/*
+ * The MAC is stored network-endian on all devices, in 2 32-bit segments:
+ * <XX:XX:XX:XX> <XX:XX:00:00>. Kernel print has us covered.
+ */
+static ssize_t hc_tag_show_mac(const u8 *pld, u16 pld_len, char *buf)
+{
+       if (8 != pld_len)
+               return -EINVAL;
+
+       return sprintf(buf, "%pM\n", pld);
+}
+
+/*
+ * Print HW options in a human readable way:
+ * The raw number and in decoded form
+ */
+static ssize_t hc_tag_show_hwoptions(const u8 *pld, u16 pld_len, char *buf)
+{
+       char *out = buf;
+       u32 data;       // cpu-endian
+       int i;
+
+       if (sizeof(data) != pld_len)
+               return -EINVAL;
+
+       data = *(u32 *)pld;
+       out += sprintf(out, "raw\t\t: 0x%08x\n\n", data);
+
+       for (i = 0; i < ARRAY_SIZE(hc_hwopts); i++)
+               out += sprintf(out, "%s: %s\n", hc_hwopts[i].str,
+                              (data & hc_hwopts[i].bit) ? "true" : "false");
+
+       return out - buf;
+}
+
+static ssize_t hc_wlan_data_bin_read(struct file *filp, struct kobject *kobj,
+                                    struct bin_attribute *attr, char *buf,
+                                    loff_t off, size_t count);
+
+static struct hc_wlan_attr {
+       struct bin_attribute battr;
+       u16 pld_ofs;
+       u16 pld_len;
+} hc_wlandata_battr = {
+       .battr = __BIN_ATTR(wlan_data, S_IRUSR, hc_wlan_data_bin_read, NULL, 0),
+};
+
+static ssize_t hc_attr_show(struct kobject *kobj, struct kobj_attribute *attr,
+                           char *buf);
+
+/* Array of known tags to publish in sysfs */
+static struct hc_attr {
+       const u16 tag_id;
+       ssize_t (* const tshow)(const u8 *pld, u16 pld_len, char *buf);
+       struct kobj_attribute kattr;
+       u16 pld_ofs;
+       u16 pld_len;
+} hc_attrs[] = {
+       {
+               .tag_id = RB_ID_FLASH_INFO,
+               .tshow = hc_tag_show_u32,
+               .kattr = __ATTR(flash_info, S_IRUSR, hc_attr_show, NULL),
+       }, {
+               .tag_id = RB_ID_MAC_ADDRESS_PACK,
+               .tshow = hc_tag_show_mac,
+               .kattr = __ATTR(mac_base, S_IRUSR, hc_attr_show, NULL),
+       }, {
+               .tag_id = RB_ID_BOARD_PRODUCT_CODE,
+               .tshow = hc_tag_show_string,
+               .kattr = __ATTR(board_product_code, S_IRUSR, hc_attr_show, NULL),
+       }, {
+               .tag_id = RB_ID_BIOS_VERSION,
+               .tshow = hc_tag_show_string,
+               .kattr = __ATTR(booter_version, S_IRUSR, hc_attr_show, NULL),
+       }, {
+               .tag_id = RB_ID_SERIAL_NUMBER,
+               .tshow = hc_tag_show_string,
+               .kattr = __ATTR(board_serial, S_IRUSR, hc_attr_show, NULL),
+       }, {
+               .tag_id = RB_ID_MEMORY_SIZE,
+               .tshow = hc_tag_show_u32,
+               .kattr = __ATTR(mem_size, S_IRUSR, hc_attr_show, NULL),
+       }, {
+               .tag_id = RB_ID_MAC_ADDRESS_COUNT,
+               .tshow = hc_tag_show_u32,
+               .kattr = __ATTR(mac_count, S_IRUSR, hc_attr_show, NULL),
+       }, {
+               .tag_id = RB_ID_HW_OPTIONS,
+               .tshow = hc_tag_show_hwoptions,
+               .kattr = __ATTR(hw_options, S_IRUSR, hc_attr_show, NULL),
+       }, {
+               .tag_id = RB_ID_WLAN_DATA,
+               .tshow = NULL,
+       }, {
+               .tag_id = RB_ID_BOARD_IDENTIFIER,
+               .tshow = hc_tag_show_string,
+               .kattr = __ATTR(board_identifier, S_IRUSR, hc_attr_show, NULL),
+       }, {
+               .tag_id = RB_ID_PRODUCT_NAME,
+               .tshow = hc_tag_show_string,
+               .kattr = __ATTR(product_name, S_IRUSR, hc_attr_show, NULL),
+       }, {
+               .tag_id = RB_ID_DEFCONF,
+               .tshow = hc_tag_show_string,
+               .kattr = __ATTR(defconf, S_IRUSR, hc_attr_show, NULL),
+       }
+};
+
+/*
+ * 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.
+ */
+static int hc_wlan_data_unpack_erd(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);
+       if (ret) {
+               pr_debug(RB_HC_PR_PFX "ERD data not found\n");
+               goto fail;
+       }
+
+       if (lzo_len > inlen) {
+               pr_debug(RB_HC_PR_PFX "Invalid ERD data length\n");
+               ret = -EINVAL;
+               goto fail;
+       }
+
+       ret = lzo1x_decompress_safe(inbuf+lzo_ofs, lzo_len, outbuf, outlen);
+       if (ret)
+               pr_debug(RB_HC_PR_PFX "LZO decompression error (%d)\n", ret);
+
+fail:
+       return ret;
+}
+
+static int hc_wlan_data_unpack(const size_t tofs, size_t tlen,
+                              void *outbuf, size_t *outlen)
+{
+       const u8 *lbuf;
+       u32 magic;
+       int ret;
+
+       /* Caller ensure tlen > 0. tofs is aligned */
+       if ((tofs + tlen) > hc_buflen)
+               return -EIO;
+
+       lbuf = hc_buf + tofs;
+       magic = *(u32 *)lbuf;
+
+       ret = -ENODATA;
+       switch (magic) {
+       case RB_MAGIC_ERD:
+               /* Skip magic */
+               lbuf += sizeof(magic);
+               tlen -= sizeof(magic);
+               ret = hc_wlan_data_unpack_erd(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.
+                */
+               ret = routerboot_rle_decode(lbuf, tlen, outbuf, outlen);
+               if (ret)
+                       pr_debug(RB_HC_PR_PFX "RLE decoding error (%d)\n", ret);
+               break;
+       }
+
+       return ret;
+}
+
+static ssize_t hc_attr_show(struct kobject *kobj, struct kobj_attribute *attr,
+                           char *buf)
+{
+       struct hc_attr *hc_attr;
+       const u8 *pld;
+       u16 pld_len;
+
+       hc_attr = container_of(attr, typeof(*hc_attr), kattr);
+
+       if (!hc_attr->pld_len)
+               return -ENOENT;
+
+       pld = hc_buf + hc_attr->pld_ofs;
+       pld_len = hc_attr->pld_len;
+
+       return hc_attr->tshow(pld, pld_len, buf);
+}
+
+/*
+ * This function will allocate and free memory every time it is called. This
+ * is not the fastest way to do this, but since the data is rarely read (mainly
+ * at boot time to load wlan caldata), this makes it possible to save memory for
+ * the system.
+ */
+static ssize_t hc_wlan_data_bin_read(struct file *filp, struct kobject *kobj,
+                                    struct bin_attribute *attr, char *buf,
+                                    loff_t off, size_t count)
+{
+       struct hc_wlan_attr *hc_wattr;
+       size_t outlen;
+       void *outbuf;
+       int ret;
+
+       hc_wattr = container_of(attr, typeof(*hc_wattr), battr);
+
+       if (!hc_wattr->pld_len)
+               return -ENOENT;
+
+       outlen = RB_ART_SIZE;
+
+       /* Don't bother unpacking if the source is already too large */
+       if (hc_wattr->pld_len > outlen)
+               return -EFBIG;
+
+       outbuf = kmalloc(outlen, GFP_KERNEL);
+       if (!outbuf)
+               return -ENOMEM;
+
+       ret = hc_wlan_data_unpack(hc_wattr->pld_ofs, hc_wattr->pld_len, outbuf, &outlen);
+       if (ret) {
+               kfree(outbuf);
+               return ret;
+       }
+
+       if (off >= outlen) {
+               kfree(outbuf);
+               return 0;
+       }
+
+       if (off + count > outlen)
+               count = outlen - off;
+
+       memcpy(buf, outbuf + off, count);
+
+       kfree(outbuf);
+       return count;
+}
+
+int __init rb_hardconfig_init(struct kobject *rb_kobj)
+{
+       struct mtd_info *mtd;
+       size_t bytes_read, buflen;
+       const u8 *buf;
+       int i, ret;
+       u32 magic;
+
+       // TODO allow override
+       mtd = get_mtd_device_nm(RB_MTD_HARD_CONFIG);
+       if (IS_ERR(mtd))
+               return -ENODEV;
+
+       hc_buflen = mtd->size;
+       hc_buf = kmalloc(hc_buflen, GFP_KERNEL);
+       if (!hc_buf)
+               return -ENOMEM;
+
+       ret = mtd_read(mtd, 0, hc_buflen, &bytes_read, hc_buf);
+
+       if (bytes_read != hc_buflen) {
+               ret = -EIO;
+               goto fail;
+       }
+
+       /* Check we have what we expect */
+       magic = *(const u32 *)hc_buf;
+       if (RB_MAGIC_HARD != magic) {
+               ret = -EINVAL;
+               goto fail;
+       }
+
+       /* Skip magic */
+       buf = hc_buf + sizeof(magic);
+       buflen = hc_buflen - sizeof(magic);
+
+       /* Populate sysfs */
+       ret = -ENOMEM;
+       hc_kobj = kobject_create_and_add(RB_MTD_HARD_CONFIG, rb_kobj);
+       if (!hc_kobj)
+               goto fail;
+
+       /* Locate and publish all known tags */
+       for (i = 0; i < ARRAY_SIZE(hc_attrs); i++) {
+               ret = routerboot_tag_find(buf, buflen, hc_attrs[i].tag_id,
+                                         &hc_attrs[i].pld_ofs, &hc_attrs[i].pld_len);
+               if (ret) {
+                       hc_attrs[i].pld_ofs = hc_attrs[i].pld_len = 0;
+                       continue;
+               }
+
+               /* Account for skipped magic */
+               hc_attrs[i].pld_ofs += sizeof(magic);
+
+               /* Special case RB_ID_WLAN_DATA to prep and create the binary attribute */
+               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_err(RB_HC_PR_PFX "Could not create %s sysfs entry (%d)\n",
+                                      hc_wlandata_battr.battr.attr.name, ret);
+               }
+               /* All other tags are published via standard attributes */
+               else {
+                       ret = sysfs_create_file(hc_kobj, &hc_attrs[i].kattr.attr);
+                       if (ret)
+                               pr_err(RB_HC_PR_PFX "Could not create %s sysfs entry (%d)\n",
+                                      hc_attrs[i].kattr.attr.name, ret);
+               }
+       }
+
+       pr_info("MikroTik RouterBOARD hardware configuration sysfs driver v" RB_HARDCONFIG_VER "\n");
+
+       return 0;
+
+fail:
+       kfree(hc_buf);
+       return ret;
+}
+
+void __exit rb_hardconfig_exit(void)
+{
+       kobject_put(hc_kobj);
+       kfree(hc_buf);
+}
diff --git a/target/linux/generic/files/drivers/platform/mikrotik/routerboot.c b/target/linux/generic/files/drivers/platform/mikrotik/routerboot.c
new file mode 100644 (file)
index 0000000..172db02
--- /dev/null
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for MikroTik RouterBoot flash data. Common routines.
+ *
+ * Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sysfs.h>
+
+#include "routerboot.h"
+
+static struct kobject *rb_kobj;
+
+/**
+ * routerboot_tag_find() - Locate a given tag in routerboot config data.
+ * @bufhead: the buffer to look into. Must start with a tag node.
+ * @buflen: size of bufhead
+ * @tag_id: the tag identifier to look for
+ * @pld_ofs: will be updated with tag payload offset in bufhead, if tag found
+ * @pld_len: will be updated with tag payload size, if tag found
+ *
+ * This incarnation of tag_find() does only that: it finds a specific routerboot
+ * tag node in the input buffer. Routerboot tag nodes are u32 values:
+ * - The low nibble is the tag identification number,
+ * - The high nibble is the tag payload length (node excluded) in bytes.
+ * The payload immediately follows the tag node. Tag nodes are 32bit-aligned.
+ * The returned pld_ofs will always be aligned. pld_len may not end on 32bit
+ * boundary (the only known case is when parsing ERD data).
+ * The nodes are cpu-endian on the flash media. The payload is cpu-endian when
+ * applicable. Tag nodes are not ordered (by ID) on flash.
+ *
+ * Return: 0 on success (tag found) or errno
+ */
+int routerboot_tag_find(const u8 *bufhead, const size_t buflen, const u16 tag_id,
+                       u16 *pld_ofs, u16 *pld_len)
+{
+       const u32 *datum, *bufend;
+       u32 node;
+       u16 id, len;
+       int ret;
+
+       if (!bufhead || !tag_id)
+               return -EINVAL;
+
+       ret = -ENOENT;
+       datum = (const u32 *)bufhead;
+       bufend = (const u32 *)(bufhead + buflen);
+
+       while (datum < bufend) {
+               node = *datum++;
+
+               /* Tag list ends with null node */
+               if (!node)
+                       break;
+
+               id = node & 0xFFFF;
+               len = node >> 16;
+
+               if (tag_id == id) {
+                       if (datum >= bufend)
+                               break;
+
+                       if (pld_ofs)
+                               *pld_ofs = (u16)((u8 *)datum - bufhead);
+                       if (pld_len)
+                               *pld_len = len;
+
+                       ret = 0;
+                       break;
+               }
+
+               /*
+                * The only known situation where len may not end on 32bit
+                * boundary is within ERD data. Since we're only extracting
+                * one tag (the first and only one) from that data, we should
+                * never need to forcefully ALIGN(). Do it anyway, this is not a
+                * performance path.
+                */
+               len = ALIGN(len, sizeof(*datum));
+               datum += len / sizeof(*datum);
+       }
+
+       return ret;
+}
+
+/**
+ * routerboot_rle_decode() - Simple RLE (MikroTik variant) decoding routine.
+ * @in: input buffer to decode
+ * @inlen: size of in
+ * @out: output buffer to write decoded data to
+ * @outlen: pointer to out size when function is called, will be updated with
+ * size of decoded output on return
+ *
+ * MikroTik's variant of RLE operates as follows, considering a signed run byte:
+ * - positive run => classic RLE
+ * - negative run => the next -<run> bytes must be copied verbatim
+ * The API is matched to the lzo1x routines for convenience.
+ *
+ * NB: The output buffer cannot overlap with the input buffer.
+ *
+ * Return: 0 on success or errno
+ */
+int routerboot_rle_decode(const u8 *in, size_t inlen, u8 *out, size_t *outlen)
+{
+       int ret, run, nbytes;   // use native types for speed
+       u8 byte;
+
+       if (!in || (inlen < 2) || !out)
+               return -EINVAL;
+
+       ret = -ENOSPC;
+       nbytes = 0;
+       while (inlen >= 2) {
+               run = *in++;
+               inlen--;
+
+               /* Verbatim copies */
+               if (run & 0x80) {
+                       /* Invert run byte sign */
+                       run = ~run & 0xFF;
+                       run++;
+
+                       if (run > inlen)
+                               goto fail;
+
+                       inlen -= run;
+
+                       nbytes += run;
+                       if (nbytes > *outlen)
+                               goto fail;
+
+                       /* Basic memcpy */
+                       while (run-- > 0)
+                               *out++ = *in++;
+               }
+               /* Stream of half-words RLE: <run><byte>. run == 0 is ignored */
+               else {
+                       byte = *in++;
+                       inlen--;
+
+                       nbytes += run;
+                       if (nbytes > *outlen)
+                               goto fail;
+
+                       while (run-- > 0)
+                               *out++ = byte;
+               }
+       }
+
+       ret = 0;
+fail:
+       *outlen = nbytes;
+       return ret;
+}
+
+static int __init routerboot_init(void)
+{
+       rb_kobj = kobject_create_and_add("mikrotik", firmware_kobj);
+       if (!rb_kobj)
+               return -ENOMEM;
+
+       return rb_hardconfig_init(rb_kobj);
+}
+
+static void __exit routerboot_exit(void)
+{
+       rb_hardconfig_exit();
+       kobject_put(rb_kobj);   // recursive afaict
+}
+
+module_init(routerboot_init);
+module_exit(routerboot_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MikroTik RouterBoot sysfs support");
+MODULE_AUTHOR("Thibaut VARENE");
diff --git a/target/linux/generic/files/drivers/platform/mikrotik/routerboot.h b/target/linux/generic/files/drivers/platform/mikrotik/routerboot.h
new file mode 100644 (file)
index 0000000..d2ca41f
--- /dev/null
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Common definitions for MikroTik RouterBoot data.
+ *
+ * Copyright (C) 2020 Thibaut VARÈNE <hacks+kernel@slashdirt.org>
+ */
+
+
+#ifndef _ROUTERBOOT_H_
+#define _ROUTERBOOT_H_
+
+#include <linux/types.h>
+
+// these magic values are stored in cpu-endianness on flash
+#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_ERD   (('E' << 16) | ('R' << 8) | ('D'))
+
+#define RB_ART_SIZE    0x10000
+
+#define RB_MTD_HARD_CONFIG     "hard_config"
+#define RB_MTD_SOFT_CONFIG     "soft_config"
+
+int routerboot_tag_find(const u8 *bufhead, const size_t buflen, const u16 tag_id, u16 *pld_ofs, u16 *pld_len);
+int routerboot_rle_decode(const u8 *in, size_t inlen, u8 *out, size_t *outlen);
+
+int __init rb_hardconfig_init(struct kobject *rb_kobj);
+void __exit rb_hardconfig_exit(void);
+
+#endif /* _ROUTERBOOT_H_ */