util/sconfig: Add support for discontiguous FW_CONFIG fields
Sooner or later, some board was going to need extra FW_CONFIG bits for
a field that was already in production, so this patch adds support for
adding extra (unused) bits to a field.
The extra are appended via a syntax like:
`field FIELD_NAME START0 END0 | START1 END1 | START2 END2 ...`
and the suffixed bits are all treated as if they are contiguous when
defining option values.
BUG=b:185190978
TEST=Modified volteer fw_config to the following:
field AUDIO 8 10 | 29 29 | 31 31
option NONE 0
option MAX98357_ALC5682I_I2S 1
option MAX98373_ALC5682I_I2S 2
option MAX98373_ALC5682_SNDW 3
option MAX98373_ALC5682I_I2S_UP4 4
option MAX98360_ALC5682I_I2S 5
option RT1011_ALC5682I_I2S 6
option AUDIO_FOO 7
option AUDIO_BAR 8
option AUDIO_QUUX 9
option AUDIO_BLAH1 10
option AUDIO_BLAH2 15
option AUDIO_BLAH3 16
option AUDIO_BLAH4 31
end
which yielded (in static_fw_config.h):
FW_CONFIG_FIELD_AUDIO_MASK 0xa0000700
FW_CONFIG_FIELD_AUDIO_OPTION_NONE_VALUE 0x0
FW_CONFIG_FIELD_AUDIO_OPTION_MAX98357_ALC5682I_I2S_VALUE 0x100
FW_CONFIG_FIELD_AUDIO_OPTION_MAX98373_ALC5682I_I2S_VALUE 0x200
FW_CONFIG_FIELD_AUDIO_OPTION_MAX98373_ALC5682_SNDW_VALUE 0x300
FW_CONFIG_FIELD_AUDIO_OPTION_MAX98373_ALC5682I_I2S_UP4_VALUE 0x400
FW_CONFIG_FIELD_AUDIO_OPTION_MAX98360_ALC5682I_I2S_VALUE 0x500
FW_CONFIG_FIELD_AUDIO_OPTION_RT1011_ALC5682I_I2S_VALUE 0x600
FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_FOO_VALUE 0x700
FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_BAR_VALUE 0x20000000
FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_QUUX_VALUE 0x20000100
FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_BLAH1_VALUE 0x20000200
FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_BLAH2_VALUE 0x20000700
FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_BLAH3_VALUE 0x80000000
FW_CONFIG_FIELD_AUDIO_OPTION_AUDIO_BLAH4_VALUE 0xa0000700
Change-Id: I5ed76706347ee9642198efc77139abdc3af1b8a6
Signed-off-by: Tim Wawrzynczak <twawrzynczak@chromium.org>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/52747
Reviewed-by: Duncan Laurie <duncan@iceblink.org>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
diff --git a/util/sconfig/main.c b/util/sconfig/main.c
index 91407bc..111bb50 100644
--- a/util/sconfig/main.c
+++ b/util/sconfig/main.c
@@ -398,42 +398,77 @@
}
}
-struct fw_config_field *new_fw_config_field(const char *name,
- unsigned int start_bit, unsigned int end_bit)
+void append_fw_config_bits(struct fw_config_field_bits **bits,
+ unsigned int start_bit, unsigned int end_bit)
+{
+ struct fw_config_field_bits *new_bits = S_ALLOC(sizeof(*new_bits));
+ new_bits->start_bit = start_bit;
+ new_bits->end_bit = end_bit;
+ new_bits->next = NULL;
+
+ if (*bits == NULL) {
+ *bits = new_bits;
+ return;
+ }
+
+ struct fw_config_field_bits *tmp = *bits;
+ while (tmp->next)
+ tmp = tmp->next;
+
+ tmp->next = new_bits;
+}
+
+int fw_config_masks_overlap(struct fw_config_field *existing,
+ unsigned int start_bit, unsigned int end_bit)
+{
+ struct fw_config_field_bits *bits = existing->bits;
+ while (bits) {
+ if (start_bit <= bits->end_bit && end_bit >= bits->start_bit) {
+ printf("ERROR: fw_config field [%u-%u] overlaps %s[%u-%u]\n",
+ start_bit, end_bit,
+ existing->name, bits->start_bit, bits->end_bit);
+ return 1;
+ }
+ bits = bits->next;
+ }
+
+ return 0;
+}
+
+struct fw_config_field *new_fw_config_field(const char *name, struct fw_config_field_bits *bits)
{
struct fw_config_field *field = find_fw_config_field(name);
-
- /* Check that field is within 64 bits. */
- if (start_bit > end_bit || end_bit > 63) {
- printf("ERROR: fw_config field %s has invalid range %u-%u\n", name,
- start_bit, end_bit);
- exit(1);
- }
+ struct fw_config_field_bits *tmp;
/* Don't allow re-defining a field, only adding new fields. */
if (field) {
- printf("ERROR: fw_config field %s[%u-%u] already exists with range %u-%u\n",
- name, start_bit, end_bit, field->start_bit, field->end_bit);
+ printf("ERROR: fw_config field %s already exists\n", name);
exit(1);
}
- /* Check for overlap with an existing field. */
- field = fw_config_fields;
- while (field) {
- /* Check if the mask overlaps. */
- if (start_bit <= field->end_bit && end_bit >= field->start_bit) {
- printf("ERROR: fw_config field %s[%u-%u] overlaps %s[%u-%u]\n",
- name, start_bit, end_bit,
- field->name, field->start_bit, field->end_bit);
+ /* Check that each field is within 64 bits. */
+ tmp = bits;
+ while (tmp) {
+ if (tmp->start_bit > tmp->end_bit || tmp->end_bit > 63) {
+ printf("ERROR: fw_config field %s has invalid range %u-%u\n", field->name,
+ tmp->start_bit, tmp->end_bit);
exit(1);
}
- field = field->next;
+
+ /* Check for overlap with an existing field. */
+ struct fw_config_field *existing = fw_config_fields;
+ while (existing) {
+ if (fw_config_masks_overlap(existing, tmp->start_bit, tmp->end_bit))
+ exit(1);
+ existing = existing->next;
+ }
+
+ tmp = tmp->next;
}
field = S_ALLOC(sizeof(*field));
field->name = name;
- field->start_bit = start_bit;
- field->end_bit = end_bit;
+ field->bits = bits;
append_fw_config_field(field);
return field;
@@ -453,13 +488,25 @@
}
}
+static uint64_t calc_max_field_value(const struct fw_config_field *field)
+{
+ unsigned int bit_count = 0;
+
+ const struct fw_config_field_bits *bits = field->bits;
+ while (bits) {
+ bit_count += 1 + bits->end_bit - bits->start_bit;
+ bits = bits->next;
+ };
+
+ return (1ull << bit_count) - 1ull;
+}
+
void add_fw_config_option(struct fw_config_field *field, const char *name, uint64_t value)
{
struct fw_config_option *option;
- uint64_t field_max_value;
/* Check that option value fits within field mask. */
- field_max_value = (1ull << (1ull + field->end_bit - field->start_bit)) - 1ull;
+ uint64_t field_max_value = calc_max_field_value(field);
if (value > field_max_value) {
printf("ERROR: fw_config option %s:%s value %" PRIx64 " larger than field max %"
PRIx64 "\n",
@@ -525,6 +572,46 @@
append_fw_config_probe_to_dev(bus->dev, probe);
}
+static uint64_t compute_fw_config_mask(const struct fw_config_field_bits *bits)
+{
+ uint64_t mask = 0;
+
+ while (bits) {
+ /* Compute mask from start and end bit. */
+ uint64_t tmp = ((1ull << (1ull + bits->end_bit - bits->start_bit)) - 1ull);
+ tmp <<= bits->start_bit;
+ mask |= tmp;
+ bits = bits->next;
+ }
+
+ return mask;
+}
+
+static unsigned int bits_width(const struct fw_config_field_bits *bits)
+{
+ return 1 + bits->end_bit - bits->start_bit;
+}
+
+static uint64_t calc_option_value(const struct fw_config_field *field,
+ const struct fw_config_option *option)
+{
+ uint64_t value = 0;
+ uint64_t original = option->value;
+
+ struct fw_config_field_bits *bits = field->bits;
+ while (bits) {
+ const unsigned int width = bits_width(bits);
+ const uint64_t orig_mask = (1ull << width) - 1ull;
+ const uint64_t orig = (original & orig_mask);
+ value |= (orig << bits->start_bit);
+
+ original >>= width;
+ bits = bits->next;
+ }
+
+ return value;
+}
+
static void emit_fw_config(FILE *fil)
{
struct fw_config_field *field = fw_config_fields;
@@ -539,20 +626,16 @@
fprintf(fil, "#define FW_CONFIG_FIELD_%s_NAME \"%s\"\n",
field->name, field->name);
- /* Compute mask from start and end bit. */
- mask = ((1ull << (1ull + field->end_bit - field->start_bit)) - 1ull);
- mask <<= field->start_bit;
-
+ mask = compute_fw_config_mask(field->bits);
fprintf(fil, "#define FW_CONFIG_FIELD_%s_MASK 0x%" PRIx64 "\n",
field->name, mask);
while (option) {
+ const uint64_t value = calc_option_value(field, option);
fprintf(fil, "#define FW_CONFIG_FIELD_%s_OPTION_%s_NAME \"%s\"\n",
field->name, option->name, option->name);
fprintf(fil, "#define FW_CONFIG_FIELD_%s_OPTION_%s_VALUE 0x%"
- PRIx64 "\n", field->name, option->name,
- option->value << field->start_bit);
-
+ PRIx64 "\n", field->name, option->name, value);
option = option->next;
}
@@ -1879,6 +1962,7 @@
if (override_devtree)
parse_override_devicetree(override_devtree, &override_root_dev);
+
FILE *autogen = fopen(outputc, "w");
if (!autogen) {
fprintf(stderr, "Could not open file '%s' for writing: ",