// SPDX-License-Identifier: GPL-2.0-only /* * Driver for MikroTik RouterBoot soft config. * * Copyright (C) 2020 Thibaut VARĂˆNE * * 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 * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_ATH79 #include #endif #include "routerboot.h" #define RB_SOFTCONFIG_VER "0.03" #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 upper levels (QCA955x), while F is presumably AR9344-only. */ #define RB_CPU_FREQ_IDX_ATH79_A (0 << 3) #define RB_CPU_FREQ_IDX_ATH79_B (1 << 3) // 0x8 #define RB_CPU_FREQ_IDX_ATH79_C (2 << 3) // 0x10 - factory freq for many devices #define RB_CPU_FREQ_IDX_ATH79_D (3 << 3) // 0x18 #define RB_CPU_FREQ_IDX_ATH79_E (4 << 3) // 0x20 #define RB_CPU_FREQ_IDX_ATH79_F (5 << 3) // 0x28 #define RB_CPU_FREQ_IDX_ATH79_MIN 0 // all devices support lowest setting #define RB_CPU_FREQ_IDX_ATH79_AR9334_MAX 5 // stops at F #define RB_CPU_FREQ_IDX_ATH79_QCA953X_MAX 4 // stops at E #define RB_CPU_FREQ_IDX_ATH79_QCA9556_MAX 2 // stops at C #define RB_CPU_FREQ_IDX_ATH79_QCA9558_MAX 3 // stops at D #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 (tvselmts < 0) return tvselmts; 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 (tvselmts < 0) return tvselmts; 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; } /* Support CPU frequency accessors only when the tag format has been asserted */ #if defined(CONFIG_ATH79) /* Use the same letter-based nomenclature as RouterBOOT */ static struct sc_u32tvs const sc_cpufreq_indexes_ath79[] = { RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_A, "a"), RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_B, "b"), RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_C, "c"), RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_D, "d"), RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_E, "e"), RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_F, "f"), }; static int sc_tag_cpufreq_ath79_idxmax(void) { int idx_max = -EOPNOTSUPP; if (soc_is_ar9344()) idx_max = RB_CPU_FREQ_IDX_ATH79_AR9334_MAX; else if (soc_is_qca953x()) idx_max = RB_CPU_FREQ_IDX_ATH79_QCA953X_MAX; else if (soc_is_qca9556()) idx_max = RB_CPU_FREQ_IDX_ATH79_QCA9556_MAX; else if (soc_is_qca9558()) idx_max = RB_CPU_FREQ_IDX_ATH79_QCA9558_MAX; return idx_max; } static ssize_t sc_tag_show_cpufreq_indexes(const u8 *pld, u16 pld_len, char * buf) { return sc_tag_show_u32tvs(pld, pld_len, buf, sc_cpufreq_indexes_ath79, sc_tag_cpufreq_ath79_idxmax()+1); } static ssize_t sc_tag_store_cpufreq_indexes(const u8 *pld, u16 pld_len, const char *buf, size_t count) { return sc_tag_store_u32tvs(pld, pld_len, buf, count, sc_cpufreq_indexes_ath79, sc_tag_cpufreq_ath79_idxmax()+1); } #else /* By default we only show the raw value to help with reverse-engineering */ #define sc_tag_show_cpufreq_indexes routerboot_tag_show_u32s #define sc_tag_store_cpufreq_indexes NULL #endif 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_CPU_FREQ_IDX, .tshow = sc_tag_show_cpufreq_indexes, .tstore = sc_tag_store_cpufreq_indexes, .kattr = __ATTR(cpufreq_index, 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), }, }; 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); }