| /* SPDX-License-Identifier: GPL-2.0-only */ | |
| /* | |
| * Base on arch/riscv/lib/strlen.S | |
| * | |
| * Copyright (C) Feng Jiang <jiangfeng@kylinos.cn> | |
| */ | |
| #include <linux/linkage.h> | |
| #include <asm/asm.h> | |
| #include <asm/alternative-macros.h> | |
| #include <asm/hwcap.h> | |
| /* size_t strnlen(const char *s, size_t count) */ | |
| SYM_FUNC_START(strnlen) | |
| __ALTERNATIVE_CFG("nop", "j strnlen_zbb", 0, RISCV_ISA_EXT_ZBB, | |
| IS_ENABLED(CONFIG_RISCV_ISA_ZBB) && IS_ENABLED(CONFIG_TOOLCHAIN_HAS_ZBB)) | |
| /* | |
| * Returns | |
| * a0 - String length | |
| * | |
| * Parameters | |
| * a0 - String to measure | |
| * a1 - Max length of string | |
| * | |
| * Clobbers | |
| * t0, t1, t2 | |
| */ | |
| addi t1, a0, -1 | |
| add t2, a0, a1 | |
| 1: | |
| addi t1, t1, 1 | |
| beq t1, t2, 2f | |
| lbu t0, 0(t1) | |
| bnez t0, 1b | |
| 2: | |
| sub a0, t1, a0 | |
| ret | |
| /* | |
| * Variant of strnlen using the ZBB extension if available | |
| */ | |
| #if defined(CONFIG_RISCV_ISA_ZBB) && defined(CONFIG_TOOLCHAIN_HAS_ZBB) | |
| strnlen_zbb: | |
| #ifdef CONFIG_CPU_BIG_ENDIAN | |
| # define CZ clz | |
| # define SHIFT sll | |
| #else | |
| # define CZ ctz | |
| # define SHIFT srl | |
| #endif | |
| .option push | |
| .option arch,+zbb | |
| /* | |
| * Returns | |
| * a0 - String length | |
| * | |
| * Parameters | |
| * a0 - String to measure | |
| * a1 - Max length of string | |
| * | |
| * Clobbers | |
| * t0, t1, t2, t3, t4 | |
| */ | |
| /* If maxlen is 0, return 0. */ | |
| beqz a1, 3f | |
| /* Number of irrelevant bytes in the first word. */ | |
| andi t2, a0, SZREG-1 | |
| /* Align pointer. */ | |
| andi t0, a0, -SZREG | |
| li t3, SZREG | |
| sub t3, t3, t2 | |
| slli t2, t2, 3 | |
| /* | |
| * Aligned boundary. Use the address of the last valid byte | |
| * (s + count - 1) to avoid loading a word past the count | |
| * boundary in the loop below. count == 0 is handled above. | |
| * | |
| * Saturate the boundary when s + count would wrap around (very | |
| * large counts, e.g. SIZE_MAX passed by FORTIFY strcat/strlcat | |
| * with a destination whose size is unknown at compile time). | |
| * Without this, the wrapped boundary lands before s and the | |
| * pre-loop guard below always exits, returning a truncated | |
| * length. | |
| * | |
| * Clamping the increment to ~s (== SIZE_MAX - s) keeps the | |
| * computation branchless and wrap-free: | |
| * | |
| * s + min(count - 1, ~s) == saturating_add(s, count - 1) | |
| * | |
| * Saturating makes the scan equivalent to strlen(). | |
| */ | |
| addi t4, a1, -1 /* count - 1 */ | |
| not t1, a0 /* SIZE_MAX - s */ | |
| minu t4, t4, t1 /* clamp, so s + t4 can never wrap */ | |
| add t4, a0, t4 /* saturated s + count - 1 */ | |
| andi t4, t4, -SZREG | |
| /* Get the first word. */ | |
| REG_L t1, 0(t0) | |
| /* | |
| * Shift away the partial data we loaded to remove the irrelevant bytes | |
| * preceding the string with the effect of adding NUL bytes at the | |
| * end of the string's first word. | |
| */ | |
| SHIFT t1, t1, t2 | |
| /* Convert non-NUL into 0xff and NUL into 0x00. */ | |
| orc.b t1, t1 | |
| /* Convert non-NUL into 0x00 and NUL into 0xff. */ | |
| not t1, t1 | |
| /* | |
| * Search for the first set bit (corresponding to a NUL byte in the | |
| * original chunk). | |
| */ | |
| CZ t1, t1 | |
| /* | |
| * The first chunk is special: compare against the number | |
| * of valid bytes in this chunk. | |
| */ | |
| srli a0, t1, 3 | |
| /* Limit the result by maxlen. */ | |
| minu a0, a0, a1 | |
| bgtu t3, a0, 2f | |
| /* All remaining bytes are in the first word, no loop needed. */ | |
| bgeu t0, t4, 2f | |
| /* Prepare for the word comparison loop. */ | |
| addi t2, t0, SZREG | |
| li t3, -1 | |
| /* | |
| * Our critical loop is 4 instructions and processes data in | |
| * 4 byte or 8 byte chunks. | |
| */ | |
| .p2align 3 | |
| 1: | |
| REG_L t1, SZREG(t0) | |
| addi t0, t0, SZREG | |
| orc.b t1, t1 | |
| bgeu t0, t4, 4f | |
| beq t1, t3, 1b | |
| 4: | |
| not t1, t1 | |
| CZ t1, t1 | |
| srli t1, t1, 3 | |
| /* Get number of processed bytes. */ | |
| sub t2, t0, t2 | |
| /* Add number of characters in the first word. */ | |
| add a0, a0, t2 | |
| /* Add number of characters in the last word. */ | |
| add a0, a0, t1 | |
| /* Ensure the final result does not exceed maxlen. */ | |
| minu a0, a0, a1 | |
| 2: | |
| ret | |
| 3: | |
| mv a0, a1 | |
| ret | |
| .option pop | |
| #endif | |
| SYM_FUNC_END(strnlen) | |
| SYM_FUNC_ALIAS(__pi_strnlen, strnlen) | |
| EXPORT_SYMBOL(strnlen) |