aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/generic/files/drivers/platform/mikrotik/rb_softconfig.c
blob: 54e263df8c0ee1bc3783b490fb795af3e8992e7a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
// 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>

#ifdef CONFIG_ATH79
 #include <asm/mach-ath79/ath79.h>
#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 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 9xxx 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_9X_A	(0 << 3)
#define RB_CPU_FREQ_IDX_ATH79_9X_B	(1 << 3)	// 0x8
#define RB_CPU_FREQ_IDX_ATH79_9X_C	(2 << 3)	// 0x10 - factory freq for many devices
#define RB_CPU_FREQ_IDX_ATH79_9X_D	(3 << 3)	// 0x18
#define RB_CPU_FREQ_IDX_ATH79_9X_E	(4 << 3)	// 0x20
#define RB_CPU_FREQ_IDX_ATH79_9X_F	(5 << 3)	// 0x28

#define RB_CPU_FREQ_IDX_ATH79_9X_MIN		0	// all devices support lowest setting
#define RB_CPU_FREQ_IDX_ATH79_9X_AR9334_MAX	5	// stops at F
#define RB_CPU_FREQ_IDX_ATH79_9X_QCA953X_MAX	4	// stops at E
#define RB_CPU_FREQ_IDX_ATH79_9X_QCA9556_MAX	2	// stops at C
#define RB_CPU_FREQ_IDX_ATH79_9X_QCA9558_MAX	3	// stops at D

/* ATH79 7xxx CPU frequency indices. */
#define RB_CPU_FREQ_IDX_ATH79_7X_A	((0 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_B	((1 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_C	((2 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_D	((3 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_E	((4 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_F	((5 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_G	((6 * 9) << 4)
#define RB_CPU_FREQ_IDX_ATH79_7X_H	((7 * 9) << 4)

#define RB_CPU_FREQ_IDX_ATH79_7X_MIN		0	// all devices support lowest setting
#define RB_CPU_FREQ_IDX_ATH79_7X_AR724X_MAX	3	// stops at D
#define RB_CPU_FREQ_IDX_ATH79_7X_AR7161_MAX	7	// stops at H - check if applies to all AR71xx devices

#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;

	// fallback to raw hex output if we can't handle the input
	if (tvselmts < 0)
		return routerboot_tag_show_u32s(pld, pld_len, buf);

	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_9x[] = {
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_A,	"a"),
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_B,	"b"),
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_C,	"c"),
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_D,	"d"),
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_E,	"e"),
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_9X_F,	"f"),
};

static struct sc_u32tvs const sc_cpufreq_indexes_ath79_7x[] = {
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_A,	"a"),
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_B,	"b"),
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_C,	"c"),
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_D,	"d"),
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_E,	"e"),
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_F,	"f"),
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_G,	"g"),
	RB_SC_TVS(RB_CPU_FREQ_IDX_ATH79_7X_H,	"h"),
};

static int sc_tag_cpufreq_ath79_arraysize(void)
{
	int idx_max;

	if (ATH79_SOC_AR7161 == ath79_soc)
		idx_max = RB_CPU_FREQ_IDX_ATH79_7X_AR7161_MAX+1;
	else if (soc_is_ar724x())
		idx_max = RB_CPU_FREQ_IDX_ATH79_7X_AR724X_MAX+1;
	else if (soc_is_ar9344())
		idx_max = RB_CPU_FREQ_IDX_ATH79_9X_AR9334_MAX+1;
	else if (soc_is_qca953x())
		idx_max = RB_CPU_FREQ_IDX_ATH79_9X_QCA953X_MAX+1;
	else if (soc_is_qca9556())
		idx_max = RB_CPU_FREQ_IDX_ATH79_9X_QCA9556_MAX+1;
	else if (soc_is_qca9558())
		idx_max = RB_CPU_FREQ_IDX_ATH79_9X_QCA9558_MAX+1;
	else
		idx_max = -EOPNOTSUPP;

	return idx_max;
}

static ssize_t sc_tag_show_cpufreq_indexes(const u8 *pld, u16 pld_len, char *buf)
{
	const struct sc_u32tvs *tvs;

	if (soc_is_ar71xx() || soc_is_ar724x())
		tvs = sc_cpufreq_indexes_ath79_7x;
	else
		tvs = sc_cpufreq_indexes_ath79_9x;

	return sc_tag_show_u32tvs(pld, pld_len, buf, tvs, sc_tag_cpufreq_ath79_arraysize());
}

static ssize_t sc_tag_store_cpufreq_indexes(const u8 *pld, u16 pld_len, const char *buf, size_t count)
{
	const struct sc_u32tvs *tvs;

	if (soc_is_ar71xx() || soc_is_ar724x())
		tvs = sc_cpufreq_indexes_ath79_7x;
	else
		tvs = sc_cpufreq_indexes_ath79_9x;

	return sc_tag_store_u32tvs(pld, pld_len, buf, count, tvs, sc_tag_cpufreq_ath79_arraysize());
}
#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);
}