xref: /linux/arch/riscv/lib/strnlen.S (revision 570f7e331f5febb30f1384817463c7e42b65ca7d)
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	add	t4, a0, a1
92	addi	t4, t4, -1
93	andi	t4, t4, -SZREG
94
95	/* Get the first word.  */
96	REG_L	t1, 0(t0)
97
98	/*
99	 * Shift away the partial data we loaded to remove the irrelevant bytes
100	 * preceding the string with the effect of adding NUL bytes at the
101	 * end of the string's first word.
102	 */
103	SHIFT	t1, t1, t2
104
105	/* Convert non-NUL into 0xff and NUL into 0x00. */
106	orc.b	t1, t1
107
108	/* Convert non-NUL into 0x00 and NUL into 0xff. */
109	not	t1, t1
110
111	/*
112	 * Search for the first set bit (corresponding to a NUL byte in the
113	 * original chunk).
114	 */
115	CZ	t1, t1
116
117	/*
118	 * The first chunk is special: compare against the number
119	 * of valid bytes in this chunk.
120	 */
121	srli	a0, t1, 3
122
123	/* Limit the result by maxlen. */
124	minu	a0, a0, a1
125
126	bgtu	t3, a0, 2f
127
128	/* All remaining bytes are in the first word, no loop needed. */
129	bgeu	t0, t4, 2f
130
131	/* Prepare for the word comparison loop. */
132	addi	t2, t0, SZREG
133	li	t3, -1
134
135	/*
136	 * Our critical loop is 4 instructions and processes data in
137	 * 4 byte or 8 byte chunks.
138	 */
139	.p2align 3
1401:
141	REG_L	t1, SZREG(t0)
142	addi	t0, t0, SZREG
143	orc.b	t1, t1
144	bgeu	t0, t4, 4f
145	beq	t1, t3, 1b
1464:
147	not	t1, t1
148	CZ	t1, t1
149	srli	t1, t1, 3
150
151	/* Get number of processed bytes. */
152	sub	t2, t0, t2
153
154	/* Add number of characters in the first word.  */
155	add	a0, a0, t2
156
157	/* Add number of characters in the last word.  */
158	add	a0, a0, t1
159
160	/* Ensure the final result does not exceed maxlen. */
161	minu	a0, a0, a1
1622:
163	ret
1643:
165	mv	a0, a1
166	ret
167
168.option pop
169#endif
170SYM_FUNC_END(strnlen)
171SYM_FUNC_ALIAS(__pi_strnlen, strnlen)
172EXPORT_SYMBOL(strnlen)
173