Merge patch series "ISA string parser cleanups"
[platform/kernel/linux-starfive.git] / arch / riscv / kernel / cpufeature.c
index f8dc577..bdcf460 100644 (file)
@@ -131,7 +131,6 @@ void __init riscv_fill_hwcap(void)
        for_each_possible_cpu(cpu) {
                struct riscv_isainfo *isainfo = &hart_isa[cpu];
                unsigned long this_hwcap = 0;
-               const char *temp;
 
                if (acpi_disabled) {
                        node = of_cpu_device_node_get(cpu);
@@ -154,22 +153,22 @@ void __init riscv_fill_hwcap(void)
                        }
                }
 
-               temp = isa;
-               if (IS_ENABLED(CONFIG_32BIT) && !strncasecmp(isa, "rv32", 4))
-                       isa += 4;
-               else if (IS_ENABLED(CONFIG_64BIT) && !strncasecmp(isa, "rv64", 4))
-                       isa += 4;
-               /* The riscv,isa DT property must start with rv64 or rv32 */
-               if (temp == isa)
-                       continue;
-               for (; *isa; ++isa) {
+               /*
+                * For all possible cpus, we have already validated in
+                * the boot process that they at least contain "rv" and
+                * whichever of "32"/"64" this kernel supports, and so this
+                * section can be skipped.
+                */
+               isa += 4;
+
+               while (*isa) {
                        const char *ext = isa++;
                        const char *ext_end = isa;
                        bool ext_long = false, ext_err = false;
 
                        switch (*ext) {
                        case 's':
-                               /**
+                               /*
                                 * Workaround for invalid single-letter 's' & 'u'(QEMU).
                                 * No need to set the bit in riscv_isa as 's' & 'u' are
                                 * not valid ISA extensions. It works until multi-letter
@@ -186,55 +185,101 @@ void __init riscv_fill_hwcap(void)
                        case 'X':
                        case 'z':
                        case 'Z':
+                               /*
+                                * Before attempting to parse the extension itself, we find its end.
+                                * As multi-letter extensions must be split from other multi-letter
+                                * extensions with an "_", the end of a multi-letter extension will
+                                * either be the null character or the "_" at the start of the next
+                                * multi-letter extension.
+                                *
+                                * Next, as the extensions version is currently ignored, we
+                                * eliminate that portion. This is done by parsing backwards from
+                                * the end of the extension, removing any numbers. This may be a
+                                * major or minor number however, so the process is repeated if a
+                                * minor number was found.
+                                *
+                                * ext_end is intended to represent the first character *after* the
+                                * name portion of an extension, but will be decremented to the last
+                                * character itself while eliminating the extensions version number.
+                                * A simple re-increment solves this problem.
+                                */
                                ext_long = true;
-                               /* Multi-letter extension must be delimited */
                                for (; *isa && *isa != '_'; ++isa)
                                        if (unlikely(!isalnum(*isa)))
                                                ext_err = true;
-                               /* Parse backwards */
+
                                ext_end = isa;
                                if (unlikely(ext_err))
                                        break;
+
                                if (!isdigit(ext_end[-1]))
                                        break;
-                               /* Skip the minor version */
+
                                while (isdigit(*--ext_end))
                                        ;
-                               if (tolower(ext_end[0]) != 'p'
-                                   || !isdigit(ext_end[-1])) {
-                                       /* Advance it to offset the pre-decrement */
+
+                               if (tolower(ext_end[0]) != 'p' || !isdigit(ext_end[-1])) {
                                        ++ext_end;
                                        break;
                                }
-                               /* Skip the major version */
+
                                while (isdigit(*--ext_end))
                                        ;
+
                                ++ext_end;
                                break;
                        default:
+                               /*
+                                * Things are a little easier for single-letter extensions, as they
+                                * are parsed forwards.
+                                *
+                                * After checking that our starting position is valid, we need to
+                                * ensure that, when isa was incremented at the start of the loop,
+                                * that it arrived at the start of the next extension.
+                                *
+                                * If we are already on a non-digit, there is nothing to do. Either
+                                * we have a multi-letter extension's _, or the start of an
+                                * extension.
+                                *
+                                * Otherwise we have found the current extension's major version
+                                * number. Parse past it, and a subsequent p/minor version number
+                                * if present. The `p` extension must not appear immediately after
+                                * a number, so there is no fear of missing it.
+                                *
+                                */
                                if (unlikely(!isalpha(*ext))) {
                                        ext_err = true;
                                        break;
                                }
-                               /* Find next extension */
+
                                if (!isdigit(*isa))
                                        break;
-                               /* Skip the minor version */
+
                                while (isdigit(*++isa))
                                        ;
+
                                if (tolower(*isa) != 'p')
                                        break;
+
                                if (!isdigit(*++isa)) {
                                        --isa;
                                        break;
                                }
-                               /* Skip the major version */
+
                                while (isdigit(*++isa))
                                        ;
+
                                break;
                        }
-                       if (*isa != '_')
-                               --isa;
+
+                       /*
+                        * The parser expects that at the start of an iteration isa points to the
+                        * first character of the next extension. As we stop parsing an extension
+                        * on meeting a non-alphanumeric character, an extra increment is needed
+                        * where the succeeding extension is a multi-letter prefixed with an "_".
+                        */
+                       if (*isa == '_')
+                               ++isa;
 
 #define SET_ISA_EXT_MAP(name, bit)                                                     \
                        do {                                                            \
@@ -273,6 +318,23 @@ void __init riscv_fill_hwcap(void)
                }
 
                /*
+                * Linux requires the following extensions, so we may as well
+                * always set them.
+                */
+               set_bit(RISCV_ISA_EXT_ZICSR, isainfo->isa);
+               set_bit(RISCV_ISA_EXT_ZIFENCEI, isainfo->isa);
+
+               /*
+                * These ones were as they were part of the base ISA when the
+                * port & dt-bindings were upstreamed, and so can be set
+                * unconditionally where `i` is in riscv,isa on DT systems.
+                */
+               if (acpi_disabled) {
+                       set_bit(RISCV_ISA_EXT_ZICNTR, isainfo->isa);
+                       set_bit(RISCV_ISA_EXT_ZIHPM, isainfo->isa);
+               }
+
+               /*
                 * All "okay" hart should have same isa. Set HWCAP based on
                 * common capabilities of every "okay" hart, in case they don't
                 * have.