diff options
author | Thibaut VARÈNE <hacks@slashdirt.org> | 2020-05-13 22:12:41 +0200 |
---|---|---|
committer | Koen Vandeputte <koen.vandeputte@ncentric.com> | 2020-05-28 11:09:10 +0200 |
commit | f36e710e2d6da4c4a15a2af94204236d8fcae988 (patch) | |
tree | 89ef2a7f4524c2830689472d7df31dc674a7beae /target/linux | |
parent | dac18f66fb18ebadf6f050d9c5c055a82855e8f6 (diff) | |
download | upstream-f36e710e2d6da4c4a15a2af94204236d8fcae988.tar.gz upstream-f36e710e2d6da4c4a15a2af94204236d8fcae988.tar.bz2 upstream-f36e710e2d6da4c4a15a2af94204236d8fcae988.zip |
generic: routerboot sysfs: add support for soft_config
This driver exposes the data encoded in the "soft_config" flash segment
of MikroTik RouterBOARDs devices. It presents the data in a sysfs folder
named "soft_config" through a set of human-and-machine-parseable
attributes. Changes can be discarded by writing 0 to the 'commit'
attribute, or they can be committed to flash storage by writing 1.
This driver does not reuse any of the existing code previously found in
the "rbcfg" utility and makes this utility obsolete by providing a clean
sysfs interface.
Like "rbcfg", this driver requires 4K_SECTORS support since the flash
partition in which these parameters are stored is typically 4KB in size.
Tested-by: Koen Vandeputte <koen.vandeputte@ncentric.com>
Tested-by: Roger Pueyo Centelles <roger.pueyo@guifi.net>
Signed-off-by: Thibaut VARÈNE <hacks@slashdirt.org>
Diffstat (limited to 'target/linux')
6 files changed, 711 insertions, 2 deletions
diff --git a/target/linux/generic/files/drivers/platform/mikrotik/Kconfig b/target/linux/generic/files/drivers/platform/mikrotik/Kconfig index 3a0c32604e..7499ba1e1c 100644 --- a/target/linux/generic/files/drivers/platform/mikrotik/Kconfig +++ b/target/linux/generic/files/drivers/platform/mikrotik/Kconfig @@ -12,6 +12,7 @@ config MIKROTIK_RB_SYSFS tristate "RouterBoot sysfs support" depends on MTD select LZO_DECOMPRESS + select CRC32 help This driver exposes RouterBoot configuration in sysfs. diff --git a/target/linux/generic/files/drivers/platform/mikrotik/Makefile b/target/linux/generic/files/drivers/platform/mikrotik/Makefile index 4d50ede9ff..a232e1a9e8 100644 --- a/target/linux/generic/files/drivers/platform/mikrotik/Makefile +++ b/target/linux/generic/files/drivers/platform/mikrotik/Makefile @@ -1,4 +1,4 @@ # # Makefile for MikroTik RouterBoard platform specific drivers # -obj-$(CONFIG_MIKROTIK_RB_SYSFS) += routerboot.o rb_hardconfig.o +obj-$(CONFIG_MIKROTIK_RB_SYSFS) += routerboot.o rb_hardconfig.o rb_softconfig.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 index bef71a0b5f..a03aa21b4d 100644 --- a/target/linux/generic/files/drivers/platform/mikrotik/rb_hardconfig.c +++ b/target/linux/generic/files/drivers/platform/mikrotik/rb_hardconfig.c @@ -675,6 +675,9 @@ int __init rb_hardconfig_init(struct kobject *rb_kobj) int i, ret; u32 magic; + hc_buf = NULL; + hc_kobj = NULL; + // TODO allow override mtd = get_mtd_device_nm(RB_MTD_HARD_CONFIG); if (IS_ERR(mtd)) @@ -749,6 +752,7 @@ int __init rb_hardconfig_init(struct kobject *rb_kobj) fail: kfree(hc_buf); + hc_buf = NULL; return ret; } diff --git a/target/linux/generic/files/drivers/platform/mikrotik/rb_softconfig.c b/target/linux/generic/files/drivers/platform/mikrotik/rb_softconfig.c new file mode 100644 index 0000000000..63a3b17ae5 --- /dev/null +++ b/target/linux/generic/files/drivers/platform/mikrotik/rb_softconfig.c @@ -0,0 +1,692 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for MikroTik RouterBoot soft 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 "soft_config" flash segment of + * MikroTik RouterBOARDs devices. It presents the data in a sysfs folder + * named "soft_config". The data is presented in a user/machine-friendly way + * with just as much parsing as can be generalized across mikrotik platforms + * (as inferred from reverse-engineering). + * + * The known soft_config tags are presented in the "soft_config" sysfs folder, + * with the addition of one specific file named "commit", which is only + * available if the driver supports writes to the mtd device: no modifications + * made to any of the other attributes are actually written back to flash media + * until a true value is input into this file (e.g. [Yy1]). This is to avoid + * unnecessary flash wear, and to permit to revert all changes by issuing a + * false value ([Nn0]). Reading the content of this file shows the current + * status of the driver: if the data in sysfs matches the content of the + * soft_config partition, the file will read "clean". Otherwise, it will read + * "dirty". + * + * The writeable sysfs files presented by this driver will accept only inputs + * which are in a valid range for the given tag. As a design choice, the driver + * will not assess whether the inputs are identical to the existing data. + * + * Note: PAGE_SIZE is assumed to be >= 4K, hence the device attribute show + * routines need not check for output overflow. + * + * Some constant defines extracted from rbcfg.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/string.h> +#include <linux/mtd/mtd.h> +#include <linux/sysfs.h> +#include <linux/version.h> +#include <linux/capability.h> +#include <linux/spinlock.h> +#include <linux/crc32.h> + +#include "routerboot.h" + +#define RB_SOFTCONFIG_VER "0.01" +#define RB_SC_PR_PFX "[rb_softconfig] " + +/* + * mtd operations before 4.17 are asynchronous, not handled by this code + * Also make the driver act read-only if 4K_SECTORS are not enabled, since they + * are require to handle partial erasing of the small soft_config partition. + */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0)) && defined(CONFIG_MTD_SPI_NOR_USE_4K_SECTORS) + #define RB_SC_HAS_WRITE_SUPPORT true + #define RB_SC_WMODE S_IWUSR + #define RB_SC_RMODE S_IRUSR +#else + #define RB_SC_HAS_WRITE_SUPPORT false + #define RB_SC_WMODE 0 + #define RB_SC_RMODE S_IRUSR +#endif + +/* ID values for software settings */ +#define RB_SCID_UART_SPEED 0x01 // u32*1 +#define RB_SCID_BOOT_DELAY 0x02 // u32*1 +#define RB_SCID_BOOT_DEVICE 0x03 // u32*1 +#define RB_SCID_BOOT_KEY 0x04 // u32*1 +#define RB_SCID_CPU_MODE 0x05 // u32*1 +#define RB_SCID_BIOS_VERSION 0x06 // str +#define RB_SCID_BOOT_PROTOCOL 0x09 // u32*1 +#define RB_SCID_CPU_FREQ_IDX 0x0C // u32*1 +#define RB_SCID_BOOTER 0x0D // u32*1 +#define RB_SCID_SILENT_BOOT 0x0F // u32*1 +/* + * protected_routerboot seems to use tag 0x1F. It only works in combination with + * RouterOS, resulting in a wiped board otherwise, so it's not implemented here. + * The tag values are as follows: + * - off: 0x0 + * - on: the lower halfword encodes the max value in s for the reset feature, + * the higher halfword encodes the min value in s for the reset feature. + * Default value when on: 0x00140258: 0x14 = 20s / 0x258= 600s + * See details here: https://wiki.mikrotik.com/wiki/Manual:RouterBOARD_settings#Protected_bootloader + */ + +/* Tag values */ + +#define RB_UART_SPEED_115200 0 +#define RB_UART_SPEED_57600 1 +#define RB_UART_SPEED_38400 2 +#define RB_UART_SPEED_19200 3 +#define RB_UART_SPEED_9600 4 +#define RB_UART_SPEED_4800 5 +#define RB_UART_SPEED_2400 6 +#define RB_UART_SPEED_1200 7 +#define RB_UART_SPEED_OFF 8 + +/* valid boot delay: 1 - 9s in 1s increment */ +#define RB_BOOT_DELAY_MIN 1 +#define RB_BOOT_DELAY_MAX 9 + +#define RB_BOOT_DEVICE_ETHER 0 // "boot over Ethernet" +#define RB_BOOT_DEVICE_NANDETH 1 // "boot from NAND, if fail then Ethernet" +#define RB_BOOT_DEVICE_CFCARD 2 // (not available in rbcfg) +#define RB_BOOT_DEVICE_ETHONCE 3 // "boot Ethernet once, then NAND" +#define RB_BOOT_DEVICE_NANDONLY 5 // "boot from NAND only" +#define RB_BOOT_DEVICE_FLASHCFG 7 // "boot in flash configuration mode" +#define RB_BOOT_DEVICE_FLSHONCE 8 // "boot in flash configuration mode once, then NAND" + +/* + * ATH79 CPU frequency indices. + * It is unknown if they apply to all ATH79 RBs, and some do not seem to feature + * the up levels (QCA955x), while U3 is presumably AR9344-only. + */ +#define RB_CPU_FREQ_IDX_ATH79_D2 (0 << 3) +#define RB_CPU_FREQ_IDX_ATH79_D1 (1 << 3) // 0x8 +#define RB_CPU_FREQ_IDX_ATH79_N0 (2 << 3) // 0x10 - factory freq for many devices +#define RB_CPU_FREQ_IDX_ATH79_U1 (3 << 3) // 0x18 +#define RB_CPU_FREQ_IDX_ATH79_U2 (4 << 3) // 0x20 +#define RB_CPU_FREQ_IDX_ATH79_U3 (5 << 3) // 0x28 + +#define RB_SC_CRC32_OFFSET 4 // located right after magic + +static struct kobject *sc_kobj; +static u8 *sc_buf; +static size_t sc_buflen; +static rwlock_t sc_bufrwl; // rw lock to sc_buf + +/* MUST be used with lock held */ +#define RB_SC_CLRCRC() *(u32 *)(sc_buf + RB_SC_CRC32_OFFSET) = 0 +#define RB_SC_GETCRC() *(u32 *)(sc_buf + RB_SC_CRC32_OFFSET) +#define RB_SC_SETCRC(_crc) *(u32 *)(sc_buf + RB_SC_CRC32_OFFSET) = (_crc) + +struct sc_u32tvs { + const u32 val; + const char *str; +}; + +#define RB_SC_TVS(_val, _str) { \ + .val = (_val), \ + .str = (_str), \ +} + +static ssize_t sc_tag_show_u32tvs(const u8 *pld, u16 pld_len, char *buf, + const struct sc_u32tvs tvs[], const int tvselmts) +{ + const char *fmt; + char *out = buf; + u32 data; // cpu-endian + int i; + + if (sizeof(data) != pld_len) + return -EINVAL; + + read_lock(&sc_bufrwl); + data = *(u32 *)pld; // pld aliases sc_buf + read_unlock(&sc_bufrwl); + + for (i = 0; i < tvselmts; i++) { + fmt = (tvs[i].val == data) ? "[%s] " : "%s "; + out += sprintf(out, fmt, tvs[i].str); + } + + out += sprintf(out, "\n"); + return out - buf; +} + +static ssize_t sc_tag_store_u32tvs(const u8 *pld, u16 pld_len, const char *buf, size_t count, + const struct sc_u32tvs tvs[], const int tvselmts) +{ + int i; + + if (sizeof(u32) != pld_len) + return -EINVAL; + + for (i = 0; i < tvselmts; i++) { + if (sysfs_streq(buf, tvs[i].str)) { + write_lock(&sc_bufrwl); + *(u32 *)pld = tvs[i].val; // pld aliases sc_buf + RB_SC_CLRCRC(); + write_unlock(&sc_bufrwl); + return count; + } + } + + return -EINVAL; +} + +struct sc_boolts { + const char *strfalse; + const char *strtrue; +}; + +static ssize_t sc_tag_show_boolts(const u8 *pld, u16 pld_len, char *buf, + const struct sc_boolts *bts) +{ + const char *fmt; + char *out = buf; + u32 data; // cpu-endian + + if (sizeof(data) != pld_len) + return -EINVAL; + + read_lock(&sc_bufrwl); + data = *(u32 *)pld; // pld aliases sc_buf + read_unlock(&sc_bufrwl); + + fmt = (data) ? "%s [%s]\n" : "[%s] %s\n"; + out += sprintf(out, fmt, bts->strfalse, bts->strtrue); + + return out - buf; +} + +static ssize_t sc_tag_store_boolts(const u8 *pld, u16 pld_len, const char *buf, size_t count, + const struct sc_boolts *bts) +{ + u32 data; // cpu-endian + + if (sizeof(data) != pld_len) + return -EINVAL; + + if (sysfs_streq(buf, bts->strfalse)) + data = 0; + else if (sysfs_streq(buf, bts->strtrue)) + data = 1; + else + return -EINVAL; + + write_lock(&sc_bufrwl); + *(u32 *)pld = data; // pld aliases sc_buf + RB_SC_CLRCRC(); + write_unlock(&sc_bufrwl); + + return count; +} +static struct sc_u32tvs const sc_uartspeeds[] = { + RB_SC_TVS(RB_UART_SPEED_OFF, "off"), + RB_SC_TVS(RB_UART_SPEED_1200, "1200"), + RB_SC_TVS(RB_UART_SPEED_2400, "2400"), + RB_SC_TVS(RB_UART_SPEED_4800, "4800"), + RB_SC_TVS(RB_UART_SPEED_9600, "9600"), + RB_SC_TVS(RB_UART_SPEED_19200, "19200"), + RB_SC_TVS(RB_UART_SPEED_38400, "38400"), + RB_SC_TVS(RB_UART_SPEED_57600, "57600"), + RB_SC_TVS(RB_UART_SPEED_115200, "115200"), +}; + +/* + * While the defines are carried over from rbcfg, use strings that more clearly + * show the actual setting purpose (especially since the NAND* settings apply + * to both nand- and nor-based devices). "cfcard" was disabled in rbcfg: disable + * it here too. + */ +static struct sc_u32tvs const sc_bootdevices[] = { + RB_SC_TVS(RB_BOOT_DEVICE_ETHER, "eth"), + RB_SC_TVS(RB_BOOT_DEVICE_NANDETH, "flasheth"), + //RB_SC_TVS(RB_BOOT_DEVICE_CFCARD, "cfcard"), + RB_SC_TVS(RB_BOOT_DEVICE_ETHONCE, "ethonce"), + RB_SC_TVS(RB_BOOT_DEVICE_NANDONLY, "flash"), + RB_SC_TVS(RB_BOOT_DEVICE_FLASHCFG, "cfg"), + RB_SC_TVS(RB_BOOT_DEVICE_FLSHONCE, "cfgonce"), +}; + +static struct sc_boolts const sc_bootkey = { + .strfalse = "any", + .strtrue = "del", +}; + +static struct sc_boolts const sc_cpumode = { + .strfalse = "powersave", + .strtrue = "regular", +}; + +static struct sc_boolts const sc_bootproto = { + .strfalse = "bootp", + .strtrue = "dhcp", +}; + +static struct sc_boolts const sc_booter = { + .strfalse = "regular", + .strtrue = "backup", +}; + +static struct sc_boolts const sc_silent_boot = { + .strfalse = "off", + .strtrue = "on", +}; + +#define SC_TAG_SHOW_STORE_U32TVS_FUNCS(_name) \ +static ssize_t sc_tag_show_##_name(const u8 *pld, u16 pld_len, char *buf) \ +{ \ + return sc_tag_show_u32tvs(pld, pld_len, buf, sc_##_name, ARRAY_SIZE(sc_##_name)); \ +} \ +static ssize_t sc_tag_store_##_name(const u8 *pld, u16 pld_len, const char *buf, size_t count) \ +{ \ + return sc_tag_store_u32tvs(pld, pld_len, buf, count, sc_##_name, ARRAY_SIZE(sc_##_name)); \ +} + +#define SC_TAG_SHOW_STORE_BOOLTS_FUNCS(_name) \ +static ssize_t sc_tag_show_##_name(const u8 *pld, u16 pld_len, char *buf) \ +{ \ + return sc_tag_show_boolts(pld, pld_len, buf, &sc_##_name); \ +} \ +static ssize_t sc_tag_store_##_name(const u8 *pld, u16 pld_len, const char *buf, size_t count) \ +{ \ + return sc_tag_store_boolts(pld, pld_len, buf, count, &sc_##_name); \ +} + +SC_TAG_SHOW_STORE_U32TVS_FUNCS(uartspeeds) +SC_TAG_SHOW_STORE_U32TVS_FUNCS(bootdevices) +SC_TAG_SHOW_STORE_BOOLTS_FUNCS(bootkey) +SC_TAG_SHOW_STORE_BOOLTS_FUNCS(cpumode) +SC_TAG_SHOW_STORE_BOOLTS_FUNCS(bootproto) +SC_TAG_SHOW_STORE_BOOLTS_FUNCS(booter) +SC_TAG_SHOW_STORE_BOOLTS_FUNCS(silent_boot) + +static ssize_t sc_tag_show_bootdelays(const u8 *pld, u16 pld_len, char *buf) +{ + const char *fmt; + char *out = buf; + u32 data; // cpu-endian + int i; + + if (sizeof(data) != pld_len) + return -EINVAL; + + read_lock(&sc_bufrwl); + data = *(u32 *)pld; // pld aliases sc_buf + read_unlock(&sc_bufrwl); + + for (i = RB_BOOT_DELAY_MIN; i <= RB_BOOT_DELAY_MAX; i++) { + fmt = (i == data) ? "[%d] " : "%d "; + out += sprintf(out, fmt, i); + } + + out += sprintf(out, "\n"); + return out - buf; +} + +static ssize_t sc_tag_store_bootdelays(const u8 *pld, u16 pld_len, const char *buf, size_t count) +{ + u32 data; // cpu-endian + int ret; + + if (sizeof(data) != pld_len) + return -EINVAL; + + ret = kstrtou32(buf, 10, &data); + if (ret) + return ret; + + if ((data < RB_BOOT_DELAY_MIN) || (RB_BOOT_DELAY_MAX < data)) + return -EINVAL; + + write_lock(&sc_bufrwl); + *(u32 *)pld = data; // pld aliases sc_buf + RB_SC_CLRCRC(); + write_unlock(&sc_bufrwl); + + return count; +} + +static ssize_t sc_attr_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); +static ssize_t sc_attr_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count); + +/* Array of known tags to publish in sysfs */ +static struct sc_attr { + const u16 tag_id; + /* sysfs tag show attribute. Must lock sc_buf when dereferencing pld */ + ssize_t (* const tshow)(const u8 *pld, u16 pld_len, char *buf); + /* sysfs tag store attribute. Must lock sc_buf when dereferencing pld */ + ssize_t (* const tstore)(const u8 *pld, u16 pld_len, const char *buf, size_t count); + struct kobj_attribute kattr; + u16 pld_ofs; + u16 pld_len; +} sc_attrs[] = { + { + .tag_id = RB_SCID_UART_SPEED, + .tshow = sc_tag_show_uartspeeds, + .tstore = sc_tag_store_uartspeeds, + .kattr = __ATTR(uart_speed, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store), + }, { + .tag_id = RB_SCID_BOOT_DELAY, + .tshow = sc_tag_show_bootdelays, + .tstore = sc_tag_store_bootdelays, + .kattr = __ATTR(boot_delay, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store), + }, { + .tag_id = RB_SCID_BOOT_DEVICE, + .tshow = sc_tag_show_bootdevices, + .tstore = sc_tag_store_bootdevices, + .kattr = __ATTR(boot_device, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store), + }, { + .tag_id = RB_SCID_BOOT_KEY, + .tshow = sc_tag_show_bootkey, + .tstore = sc_tag_store_bootkey, + .kattr = __ATTR(boot_key, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store), + }, { + .tag_id = RB_SCID_CPU_MODE, + .tshow = sc_tag_show_cpumode, + .tstore = sc_tag_store_cpumode, + .kattr = __ATTR(cpu_mode, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store), + }, { + .tag_id = RB_SCID_BIOS_VERSION, + .tshow = routerboot_tag_show_string, + .tstore = NULL, + .kattr = __ATTR(bios_version, RB_SC_RMODE, sc_attr_show, NULL), + }, { + .tag_id = RB_SCID_BOOT_PROTOCOL, + .tshow = sc_tag_show_bootproto, + .tstore = sc_tag_store_bootproto, + .kattr = __ATTR(boot_proto, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store), + }, { + .tag_id = RB_SCID_BOOTER, + .tshow = sc_tag_show_booter, + .tstore = sc_tag_store_booter, + .kattr = __ATTR(booter, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store), + }, { + .tag_id = RB_SCID_SILENT_BOOT, + .tshow = sc_tag_show_silent_boot, + .tstore = sc_tag_store_silent_boot, + .kattr = __ATTR(silent_boot, RB_SC_RMODE|RB_SC_WMODE, sc_attr_show, sc_attr_store), + }, + // TODO CPU_FREQ +}; + +static ssize_t sc_attr_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + const struct sc_attr *sc_attr; + const u8 *pld; + u16 pld_len; + + sc_attr = container_of(attr, typeof(*sc_attr), kattr); + + if (!sc_attr->pld_len) + return -ENOENT; + + pld = sc_buf + sc_attr->pld_ofs; // pld aliases sc_buf -> lock! + pld_len = sc_attr->pld_len; + + return sc_attr->tshow(pld, pld_len, buf); +} + +static ssize_t sc_attr_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + const struct sc_attr *sc_attr; + const u8 *pld; + u16 pld_len; + + if (!RB_SC_HAS_WRITE_SUPPORT) + return -EOPNOTSUPP; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + sc_attr = container_of(attr, typeof(*sc_attr), kattr); + + if (!sc_attr->tstore) + return -EOPNOTSUPP; + + if (!sc_attr->pld_len) + return -ENOENT; + + pld = sc_buf + sc_attr->pld_ofs; // pld aliases sc_buf -> lock! + pld_len = sc_attr->pld_len; + + return sc_attr->tstore(pld, pld_len, buf, count); +} + +/* + * Shows the current buffer status: + * "clean": the buffer is in sync with the mtd data + * "dirty": the buffer is out of sync with the mtd data + */ +static ssize_t sc_commit_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + const char *str; + char *out = buf; + u32 crc; + + read_lock(&sc_bufrwl); + crc = RB_SC_GETCRC(); + read_unlock(&sc_bufrwl); + + str = (crc) ? "clean" : "dirty"; + out += sprintf(out, "%s\n", str); + + return out - buf; +} + +/* + * Performs buffer flushing: + * This routine expects an input compatible with kstrtobool(). + * - a "false" input discards the current changes and reads data back from mtd. + * - a "true" input commits the current changes to mtd. + * If there is no pending changes, this routine is a no-op. + * Handling failures is left as an exercise to userspace. + */ +static ssize_t sc_commit_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct mtd_info *mtd; + struct erase_info ei; + size_t bytes_rw, ret = count; + bool flush; + u32 crc; + + if (!RB_SC_HAS_WRITE_SUPPORT) + return -EOPNOTSUPP; + + read_lock(&sc_bufrwl); + crc = RB_SC_GETCRC(); + read_unlock(&sc_bufrwl); + + if (crc) + return count; // NO-OP + + ret = kstrtobool(buf, &flush); + if (ret) + return ret; + + mtd = get_mtd_device_nm(RB_MTD_SOFT_CONFIG); // TODO allow override + if (IS_ERR(mtd)) + return -ENODEV; + + write_lock(&sc_bufrwl); + if (!flush) // reread + ret = mtd_read(mtd, 0, mtd->size, &bytes_rw, sc_buf); + else { // crc32 + commit + /* + * CRC32 is computed on the entire buffer, excluding the CRC + * value itself. CRC is already null when we reach this point, + * so we can compute the CRC32 on the buffer as is. + * The expected CRC32 is Ethernet FCS style, meaning the seed is + * ~0 and the final result is also bitflipped. + */ + + crc = ~crc32(~0, sc_buf, sc_buflen); + RB_SC_SETCRC(crc); + + /* + * The soft_config partition is assumed to be entirely contained + * in a single eraseblock. + */ + + ei.addr = 0; + ei.len = mtd->size; + ret = mtd_erase(mtd, &ei); + if (!ret) + ret = mtd_write(mtd, 0, mtd->size, &bytes_rw, sc_buf); + + /* + * Handling mtd_write() failure here is a tricky situation. The + * proposed approach is to let userspace deal with retrying, + * with the caveat that it must try to flush the buffer again as + * rereading the mtd contents could potentially read garbage. + * The rationale is: even if we keep a shadow buffer of the + * original content, there is no guarantee that we will ever be + * able to write it anyway. + * Regardless, it appears that RouterBOOT will ignore an invalid + * soft_config (including a completely wiped segment) and will + * write back factory defaults when it happens. + */ + } + write_unlock(&sc_bufrwl); + + if (ret) + goto mtdfail; + + if (bytes_rw != sc_buflen) { + ret = -EIO; + goto mtdfail; + } + + return count; + +mtdfail: + RB_SC_CLRCRC(); // mark buffer content as dirty/invalid + return ret; +} + +static struct kobj_attribute sc_kattrcommit = __ATTR(commit, RB_SC_RMODE|RB_SC_WMODE, sc_commit_show, sc_commit_store); + +int __init rb_softconfig_init(struct kobject *rb_kobj) +{ + struct mtd_info *mtd; + size_t bytes_read, buflen; + const u8 *buf; + int i, ret; + u32 magic; + + sc_buf = NULL; + sc_kobj = NULL; + + // TODO allow override + mtd = get_mtd_device_nm(RB_MTD_SOFT_CONFIG); + if (IS_ERR(mtd)) + return -ENODEV; + + sc_buflen = mtd->size; + sc_buf = kmalloc(sc_buflen, GFP_KERNEL); + if (!sc_buf) + return -ENOMEM; + + ret = mtd_read(mtd, 0, sc_buflen, &bytes_read, sc_buf); + + if (ret) + goto fail; + + if (bytes_read != sc_buflen) { + ret = -EIO; + goto fail; + } + + /* Check we have what we expect */ + magic = *(const u32 *)sc_buf; + if (RB_MAGIC_SOFT != magic) { + ret = -EINVAL; + goto fail; + } + + /* Skip magic and 32bit CRC located immediately after */ + buf = sc_buf + (sizeof(magic) + sizeof(u32)); + buflen = sc_buflen - (sizeof(magic) + sizeof(u32)); + + /* Populate sysfs */ + ret = -ENOMEM; + sc_kobj = kobject_create_and_add(RB_MTD_SOFT_CONFIG, rb_kobj); + if (!sc_kobj) + goto fail; + + rwlock_init(&sc_bufrwl); + + /* Locate and publish all known tags */ + for (i = 0; i < ARRAY_SIZE(sc_attrs); i++) { + ret = routerboot_tag_find(buf, buflen, sc_attrs[i].tag_id, + &sc_attrs[i].pld_ofs, &sc_attrs[i].pld_len); + if (ret) { + sc_attrs[i].pld_ofs = sc_attrs[i].pld_len = 0; + continue; + } + + /* Account for skipped magic and crc32 */ + sc_attrs[i].pld_ofs += sizeof(magic) + sizeof(u32); + + ret = sysfs_create_file(sc_kobj, &sc_attrs[i].kattr.attr); + if (ret) + pr_warn(RB_SC_PR_PFX "Could not create %s sysfs entry (%d)\n", + sc_attrs[i].kattr.attr.name, ret); + } + + /* Finally add the 'commit' attribute */ + if (RB_SC_HAS_WRITE_SUPPORT) { + ret = sysfs_create_file(sc_kobj, &sc_kattrcommit.attr); + if (ret) { + pr_err(RB_SC_PR_PFX "Could not create %s sysfs entry (%d), aborting!\n", + sc_kattrcommit.attr.name, ret); + goto sysfsfail; // required attribute + } + } + + pr_info("MikroTik RouterBOARD software configuration sysfs driver v" RB_SOFTCONFIG_VER "\n"); + + return 0; + +sysfsfail: + kobject_put(sc_kobj); + sc_kobj = NULL; +fail: + kfree(sc_buf); + sc_buf = NULL; + return ret; +} + +void __exit rb_softconfig_exit(void) +{ + kobject_put(sc_kobj); + kfree(sc_buf); +} diff --git a/target/linux/generic/files/drivers/platform/mikrotik/routerboot.c b/target/linux/generic/files/drivers/platform/mikrotik/routerboot.c index 96a100a933..f496dd7e0c 100644 --- a/target/linux/generic/files/drivers/platform/mikrotik/routerboot.c +++ b/target/linux/generic/files/drivers/platform/mikrotik/routerboot.c @@ -166,11 +166,20 @@ static int __init routerboot_init(void) if (!rb_kobj) return -ENOMEM; - return rb_hardconfig_init(rb_kobj); + /* + * We ignore the following return values and always register. + * These init() routines are designed so that their failed state is + * always manageable by the corresponding exit() calls. + */ + rb_hardconfig_init(rb_kobj); + rb_softconfig_init(rb_kobj); + + return 0; } static void __exit routerboot_exit(void) { + rb_softconfig_exit(); rb_hardconfig_exit(); kobject_put(rb_kobj); // recursive afaict } diff --git a/target/linux/generic/files/drivers/platform/mikrotik/routerboot.h b/target/linux/generic/files/drivers/platform/mikrotik/routerboot.h index e1ae972183..5b644db4fe 100644 --- a/target/linux/generic/files/drivers/platform/mikrotik/routerboot.h +++ b/target/linux/generic/files/drivers/platform/mikrotik/routerboot.h @@ -28,6 +28,9 @@ 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); +int __init rb_softconfig_init(struct kobject *rb_kobj); +void __exit rb_softconfig_exit(void); + ssize_t routerboot_tag_show_string(const u8 *pld, u16 pld_len, char *buf); #endif /* _ROUTERBOOT_H_ */ |