xref: /linux/arch/riscv/lib/strnlen.S (revision 2a31b5378c6db9c4a8d399e5d10d32cd4b732bef)
1/* SPDX-License-Identifier: GPL-2.0-only */
2
3/*
4 * Base on arch/riscv/lib/strlen.S
5 *
6 * Copyright (C) Feng Jiang <jiangfeng@kylinos.cn>
7 */
8
9#include <linux/linkage.h>
10#include <asm/asm.h>
11#include <asm/alternative-macros.h>
12#include <asm/hwcap.h>
13
14/* size_t strnlen(const char *s, size_t count) */
15SYM_FUNC_START(strnlen)
16
17	__ALTERNATIVE_CFG("nop", "j strnlen_zbb", 0, RISCV_ISA_EXT_ZBB,
18		IS_ENABLED(CONFIG_RISCV_ISA_ZBB) && IS_ENABLED(CONFIG_TOOLCHAIN_HAS_ZBB))
19
20
21	/*
22	 * Returns
23	 *   a0 - String length
24	 *
25	 * Parameters
26	 *   a0 - String to measure
27	 *   a1 - Max length of string
28	 *
29	 * Clobbers
30	 *   t0, t1, t2
31	 */
32	addi	t1, a0, -1
33	add	t2, a0, a1
341:
35	addi	t1, t1, 1
36	beq	t1, t2, 2f
37	lbu	t0, 0(t1)
38	bnez	t0, 1b
392:
40	sub	a0, t1, a0
41	ret
42
43
44/*
45 * Variant of strnlen using the ZBB extension if available
46 */
47#if defined(CONFIG_RISCV_ISA_ZBB) && defined(CONFIG_TOOLCHAIN_HAS_ZBB)
48strnlen_zbb:
49
50#ifdef CONFIG_CPU_BIG_ENDIAN
51# define CZ	clz
52# define SHIFT	sll
53#else
54# define CZ	ctz
55# define SHIFT	srl
56#endif
57
58.option push
59.option arch,+zbb
60
61	/*
62	 * Returns
63	 *   a0 - String length
64	 *
65	 * Parameters
66	 *   a0 - String to measure
67	 *   a1 - Max length of string
68	 *
69	 * Clobbers
70	 *   t0, t1, t2, t3, t4
71	 */
72
73	/* If maxlen is 0, return 0. */
74	beqz	a1, 3f
75
76	/* Number of irrelevant bytes in the first word. */
77	andi	t2, a0, SZREG-1
78
79	/* Align pointer. */
80	andi	t0, a0, -SZREG
81
82	li	t3, SZREG
83	sub	t3, t3, t2
84	slli	t2, t2, 3
85
86	/*
87	 * Aligned boundary.  Use the address of the last valid byte
88	 * (s + count - 1) to avoid loading a word past the count
89	 * boundary in the loop below.  count == 0 is handled above.
90	 *
91	 * Saturate the boundary when s + count would wrap around (very
92	 * large counts, e.g. SIZE_MAX passed by FORTIFY strcat/strlcat
93	 * with a destination whose size is unknown at compile time).
94	 * Without this, the wrapped boundary lands before s and the
95	 * pre-loop guard below always exits, returning a truncated
96	 * length.
97	 *
98	 * Clamping the increment to ~s (== SIZE_MAX - s) keeps the
99	 * computation branchless and wrap-free:
100	 *
101	 *     s + min(count - 1, ~s) == saturating_add(s, count - 1)
102	 *
103	 * Saturating makes the scan equivalent to strlen().
104	 */
105	addi	t4, a1, -1	/* count - 1 */
106	not	t1, a0		/* SIZE_MAX - s */
107	minu	t4, t4, t1	/* clamp, so s + t4 can never wrap */
108	add	t4, a0, t4	/* saturated s + count - 1 */
109	andi	t4, t4, -SZREG
110
111	/* Get the first word.  */
112	REG_L	t1, 0(t0)
113
114	/*
115	 * Shift away the partial data we loaded to remove the irrelevant bytes
116	 * preceding the string with the effect of adding NUL bytes at the
117	 * end of the string's first word.
118	 */
119	SHIFT	t1, t1, t2
120
121	/* Convert non-NUL into 0xff and NUL into 0x00. */
122	orc.b	t1, t1
123
124	/* Convert non-NUL into 0x00 and NUL into 0xff. */
125	not	t1, t1
126
127	/*
128	 * Search for the first set bit (corresponding to a NUL byte in the
129	 * original chunk).
130	 */
131	CZ	t1, t1
132
133	/*
134	 * The first chunk is special: compare against the number
135	 * of valid bytes in this chunk.
136	 */
137	srli	a0, t1, 3
138
139	/* Limit the result by maxlen. */
140	minu	a0, a0, a1
141
142	bgtu	t3, a0, 2f
143
144	/* All remaining bytes are in the first word, no loop needed. */
145	bgeu	t0, t4, 2f
146
147	/* Prepare for the word comparison loop. */
148	addi	t2, t0, SZREG
149	li	t3, -1
150
151	/*
152	 * Our critical loop is 4 instructions and processes data in
153	 * 4 byte or 8 byte chunks.
154	 */
155	.p2align 3
1561:
157	REG_L	t1, SZREG(t0)
158	addi	t0, t0, SZREG
159	orc.b	t1, t1
160	bgeu	t0, t4, 4f
161	beq	t1, t3, 1b
1624:
163	not	t1, t1
164	CZ	t1, t1
165	srli	t1, t1, 3
166
167	/* Get number of processed bytes. */
168	sub	t2, t0, t2
169
170	/* Add number of characters in the first word.  */
171	add	a0, a0, t2
172
173	/* Add number of characters in the last word.  */
174	add	a0, a0, t1
175
176	/* Ensure the final result does not exceed maxlen. */
177	minu	a0, a0, a1
1782:
179	ret
1803:
181	mv	a0, a1
182	ret
183
184.option pop
185#endif
186SYM_FUNC_END(strnlen)
187SYM_FUNC_ALIAS(__pi_strnlen, strnlen)
188EXPORT_SYMBOL(strnlen)
189