From 73a9f9f857947bc63fef1aefb7e2e4b906fb9d2b Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Sun, 9 Jun 2024 12:14:25 +0200 Subject: [PATCH] generic: platform/mikrotik: add NVMEM layout driver MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Currently, information from MikroTik hard_config is only available via sysfs, meaning that we have to rely on userspace to for example setup MACs. So, lets provide a basic NVMEM layout based driver to expose the same cells as sysfs driver exposes. Do note that the we dont extract the WLAN caldata and BDF-s at this point. Reviewed-by: Thibaut VARÈNE Link: https://github.com/openwrt/openwrt/pull/15665 Signed-off-by: Robert Marko --- target/linux/ath79/mikrotik/config-default | 1 + .../files/drivers/platform/mikrotik/Kconfig | 6 + .../files/drivers/platform/mikrotik/Makefile | 1 + .../drivers/platform/mikrotik/rb_nvmem.c | 230 ++++++++++++++++++ target/linux/ipq40xx/mikrotik/config-default | 1 + target/linux/ramips/mt7621/config-6.6 | 1 + 6 files changed, 240 insertions(+) create mode 100644 target/linux/generic/files/drivers/platform/mikrotik/rb_nvmem.c diff --git a/target/linux/ath79/mikrotik/config-default b/target/linux/ath79/mikrotik/config-default index 4325e1f69a..71b64b26ad 100644 --- a/target/linux/ath79/mikrotik/config-default +++ b/target/linux/ath79/mikrotik/config-default @@ -33,6 +33,7 @@ CONFIG_MTD_UBI_WL_THRESHOLD=4096 CONFIG_NET_DEVLINK=y CONFIG_NET_DSA=y CONFIG_NET_SWITCHDEV=y +# CONFIG_NVMEM_LAYOUT_MIKROTIK is not set CONFIG_PHYLINK=y CONFIG_PHY_AR7100_USB=y CONFIG_PHY_AR7200_USB=y diff --git a/target/linux/generic/files/drivers/platform/mikrotik/Kconfig b/target/linux/generic/files/drivers/platform/mikrotik/Kconfig index 1dc027815a..32ef8f29de 100644 --- a/target/linux/generic/files/drivers/platform/mikrotik/Kconfig +++ b/target/linux/generic/files/drivers/platform/mikrotik/Kconfig @@ -15,4 +15,10 @@ config MIKROTIK_RB_SYSFS help This driver exposes RouterBoot configuration in sysfs. +config NVMEM_LAYOUT_MIKROTIK + tristate "RouterBoot NVMEM layout support" + depends on NVMEM_LAYOUTS + help + This driver exposes MikroTik hard_config via NVMEM layout. + endif # MIKROTIK diff --git a/target/linux/generic/files/drivers/platform/mikrotik/Makefile b/target/linux/generic/files/drivers/platform/mikrotik/Makefile index a232e1a9e8..164b23b701 100644 --- a/target/linux/generic/files/drivers/platform/mikrotik/Makefile +++ b/target/linux/generic/files/drivers/platform/mikrotik/Makefile @@ -2,3 +2,4 @@ # Makefile for MikroTik RouterBoard platform specific drivers # obj-$(CONFIG_MIKROTIK_RB_SYSFS) += routerboot.o rb_hardconfig.o rb_softconfig.o +obj-$(CONFIG_NVMEM_LAYOUT_MIKROTIK) += rb_nvmem.o diff --git a/target/linux/generic/files/drivers/platform/mikrotik/rb_nvmem.c b/target/linux/generic/files/drivers/platform/mikrotik/rb_nvmem.c new file mode 100644 index 0000000000..6f785ce7d1 --- /dev/null +++ b/target/linux/generic/files/drivers/platform/mikrotik/rb_nvmem.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NVMEM layout driver for MikroTik Routerboard hard config cells + * + * Copyright (C) 2024 Robert Marko + * Based on the sysfs hard config driver by Thibaut VARÈNE + * Comments documenting the format carried over from routerboot.c + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "rb_hardconfig.h" +#include "routerboot.h" + +#define TLV_TAG_MASK GENMASK(15, 0) +#define TLV_LEN_MASK GENMASK(31, 16) + +static const char *rb_tlv_cell_name(u16 tag) +{ + switch (tag) { + case RB_ID_FLASH_INFO: + return "flash-info"; + case RB_ID_MAC_ADDRESS_PACK: + return "base-mac-address"; + case RB_ID_BOARD_PRODUCT_CODE: + return "board-product-code"; + case RB_ID_BIOS_VERSION: + return "booter-version"; + case RB_ID_SERIAL_NUMBER: + return "board-serial"; + case RB_ID_MEMORY_SIZE: + return "mem-size"; + case RB_ID_MAC_ADDRESS_COUNT: + return "mac-count"; + case RB_ID_HW_OPTIONS: + return "hw-options"; + case RB_ID_WLAN_DATA: + return "wlan-data"; + case RB_ID_BOARD_IDENTIFIER: + return "board-identifier"; + case RB_ID_PRODUCT_NAME: + return "product-name"; + case RB_ID_DEFCONF: + return "defconf"; + case RB_ID_BOARD_REVISION: + return "board-revision"; + default: + break; + } + + return NULL; +} + +static int rb_tlv_mac_read_cb(void *priv, const char *id, int index, + unsigned int offset, void *buf, + size_t bytes) +{ + if (index < 0) + return -EINVAL; + + if (!is_valid_ether_addr(buf)) + return -EINVAL; + + eth_addr_add(buf, index); + + return 0; +} + +static nvmem_cell_post_process_t rb_tlv_read_cb(u16 tag) +{ + switch (tag) { + case RB_ID_MAC_ADDRESS_PACK: + return &rb_tlv_mac_read_cb; + default: + break; + } + + return NULL; +} + +static int rb_add_cells(struct device *dev, struct nvmem_device *nvmem, + const size_t data_len, u8 *data) +{ + u32 node, offset = sizeof(RB_MAGIC_HARD); + struct nvmem_cell_info cell = {}; + struct device_node *layout; + u16 tlv_tag, tlv_len; + int ret; + + layout = of_nvmem_layout_get_container(nvmem); + if (!layout) + return -ENOENT; + + /* + * 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. + * Tag nodes are CPU-endian. + * Tag nodes are 32bit-aligned. + * + * The payload immediately follows the tag node. + * Payload offset will always be aligned. while length may not end on 32bit + * boundary (the only known case is when parsing ERD data). + * The payload is CPU-endian when applicable. + * Tag nodes are not ordered (by ID) on flash. + */ + while ((offset + sizeof(node)) <= data_len) { + node = *((const u32 *) (data + offset)); + /* Tag list ends with null node */ + if (!node) + break; + + tlv_tag = FIELD_GET(TLV_TAG_MASK, node); + tlv_len = FIELD_GET(TLV_LEN_MASK, node); + + offset += sizeof(node); + if (offset + tlv_len > data_len) { + dev_err(dev, "Out of bounds field (0x%x bytes at 0x%x)\n", + tlv_len, offset); + break; + } + + cell.name = rb_tlv_cell_name(tlv_tag); + if (!cell.name) + goto skip; + + cell.offset = offset; + /* + * MikroTik stores MAC-s with length of 8 bytes, + * but kernel expects it to be ETH_ALEN (6 bytes), + * so we need to make sure that is the case. + */ + if (tlv_tag == RB_ID_MAC_ADDRESS_PACK) + cell.bytes = ETH_ALEN; + else + cell.bytes = tlv_len; + cell.np = of_get_child_by_name(layout, cell.name); + cell.read_post_process = rb_tlv_read_cb(tlv_tag); + + ret = nvmem_add_one_cell(nvmem, &cell); + if (ret) { + of_node_put(layout); + return ret; + } + + /* + * 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. + */ +skip: + offset += ALIGN(tlv_len, sizeof(offset)); + } + + of_node_put(layout); + + return 0; +} + +static int rb_parse_table(struct nvmem_layout *layout) +{ + struct nvmem_device *nvmem = layout->nvmem; + struct device *dev = &layout->dev; + size_t mtd_size; + u8 *data; + u32 hdr; + int ret; + + ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr); + if (ret < 0) + return ret; + + if (hdr != RB_MAGIC_HARD) { + dev_err(dev, "Invalid header\n"); + return -EINVAL; + } + + mtd_size = nvmem_dev_size(nvmem); + + data = devm_kmalloc(dev, mtd_size, GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = nvmem_device_read(nvmem, 0, mtd_size, data); + if (ret != mtd_size) + return ret; + + return rb_add_cells(dev, nvmem, mtd_size, data); +} + +static int rb_nvmem_probe(struct nvmem_layout *layout) +{ + layout->add_cells = rb_parse_table; + + return nvmem_layout_register(layout); +} + +static void rb_nvmem_remove(struct nvmem_layout *layout) +{ + nvmem_layout_unregister(layout); +} + +static const struct of_device_id rb_nvmem_of_match_table[] = { + { .compatible = "mikrotik,routerboot-nvmem", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rb_nvmem_of_match_table); + +static struct nvmem_layout_driver rb_nvmem_layout = { + .probe = rb_nvmem_probe, + .remove = rb_nvmem_remove, + .driver = { + .owner = THIS_MODULE, + .name = "rb_nvmem", + .of_match_table = rb_nvmem_of_match_table, + }, +}; +module_nvmem_layout_driver(rb_nvmem_layout); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Robert Marko "); +MODULE_DESCRIPTION("NVMEM layout driver for MikroTik Routerboard hard config cells"); diff --git a/target/linux/ipq40xx/mikrotik/config-default b/target/linux/ipq40xx/mikrotik/config-default index 1268d2c810..ab470ecb41 100644 --- a/target/linux/ipq40xx/mikrotik/config-default +++ b/target/linux/ipq40xx/mikrotik/config-default @@ -3,3 +3,4 @@ CONFIG_MIKROTIK_RB_SYSFS=y CONFIG_MTD_ROUTERBOOT_PARTS=y CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE=y CONFIG_MTD_SPLIT_MINOR_FW=y +# CONFIG_NVMEM_LAYOUT_MIKROTIK is not set diff --git a/target/linux/ramips/mt7621/config-6.6 b/target/linux/ramips/mt7621/config-6.6 index e77ea238e7..219e61a467 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_NVMEM_LAYOUT_MIKROTIK is not set CONFIG_MIPS=y CONFIG_MIPS_ASID_BITS=8 CONFIG_MIPS_ASID_SHIFT=0 -- 2.30.2