1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
22f4f12e5SLinus Torvalds #include <linux/export.h>
32f4f12e5SLinus Torvalds #include <linux/lockref.h>
42f4f12e5SLinus Torvalds
557f4257eSPeter Zijlstra #if USE_CMPXCHG_LOCKREF
6bc08b449SLinus Torvalds
7bc08b449SLinus Torvalds /*
8bc08b449SLinus Torvalds * Note that the "cmpxchg()" reloads the "old" value for the
9bc08b449SLinus Torvalds * failure case.
10bc08b449SLinus Torvalds */
11bc08b449SLinus Torvalds #define CMPXCHG_LOOP(CODE, SUCCESS) do { \
12893a7d32SJan Glauber int retry = 100; \
13bc08b449SLinus Torvalds struct lockref old; \
14bc08b449SLinus Torvalds BUILD_BUG_ON(sizeof(old) != 8); \
154d3199e4SDavidlohr Bueso old.lock_count = READ_ONCE(lockref->lock_count); \
16bc08b449SLinus Torvalds while (likely(arch_spin_value_unlocked(old.lock.rlock.raw_lock))) { \
173378323bSUros Bizjak struct lockref new = old; \
18bc08b449SLinus Torvalds CODE \
193378323bSUros Bizjak if (likely(try_cmpxchg64_relaxed(&lockref->lock_count, \
203378323bSUros Bizjak &old.lock_count, \
213378323bSUros Bizjak new.lock_count))) { \
22bc08b449SLinus Torvalds SUCCESS; \
23bc08b449SLinus Torvalds } \
24893a7d32SJan Glauber if (!--retry) \
25893a7d32SJan Glauber break; \
26bc08b449SLinus Torvalds } \
27bc08b449SLinus Torvalds } while (0)
28bc08b449SLinus Torvalds
29bc08b449SLinus Torvalds #else
30bc08b449SLinus Torvalds
31bc08b449SLinus Torvalds #define CMPXCHG_LOOP(CODE, SUCCESS) do { } while (0)
32bc08b449SLinus Torvalds
33bc08b449SLinus Torvalds #endif
34bc08b449SLinus Torvalds
352f4f12e5SLinus Torvalds /**
362f4f12e5SLinus Torvalds * lockref_get - Increments reference count unconditionally
3744a0cf92SLinus Torvalds * @lockref: pointer to lockref structure
382f4f12e5SLinus Torvalds *
392f4f12e5SLinus Torvalds * This operation is only valid if you already hold a reference
402f4f12e5SLinus Torvalds * to the object, so you know the count cannot be zero.
412f4f12e5SLinus Torvalds */
lockref_get(struct lockref * lockref)422f4f12e5SLinus Torvalds void lockref_get(struct lockref *lockref)
432f4f12e5SLinus Torvalds {
44bc08b449SLinus Torvalds CMPXCHG_LOOP(
45bc08b449SLinus Torvalds new.count++;
46bc08b449SLinus Torvalds ,
47bc08b449SLinus Torvalds return;
48bc08b449SLinus Torvalds );
49bc08b449SLinus Torvalds
502f4f12e5SLinus Torvalds spin_lock(&lockref->lock);
512f4f12e5SLinus Torvalds lockref->count++;
522f4f12e5SLinus Torvalds spin_unlock(&lockref->lock);
532f4f12e5SLinus Torvalds }
542f4f12e5SLinus Torvalds EXPORT_SYMBOL(lockref_get);
552f4f12e5SLinus Torvalds
562f4f12e5SLinus Torvalds /**
57360f5479SLinus Torvalds * lockref_get_not_zero - Increments count unless the count is 0 or dead
5844a0cf92SLinus Torvalds * @lockref: pointer to lockref structure
592f4f12e5SLinus Torvalds * Return: 1 if count updated successfully or 0 if count was zero
602f4f12e5SLinus Torvalds */
lockref_get_not_zero(struct lockref * lockref)61*6d2868d5SChristoph Hellwig bool lockref_get_not_zero(struct lockref *lockref)
622f4f12e5SLinus Torvalds {
63*6d2868d5SChristoph Hellwig bool retval = false;
64bc08b449SLinus Torvalds
65bc08b449SLinus Torvalds CMPXCHG_LOOP(
66bc08b449SLinus Torvalds new.count++;
67360f5479SLinus Torvalds if (old.count <= 0)
68*6d2868d5SChristoph Hellwig return false;
69bc08b449SLinus Torvalds ,
70*6d2868d5SChristoph Hellwig return true;
71bc08b449SLinus Torvalds );
722f4f12e5SLinus Torvalds
732f4f12e5SLinus Torvalds spin_lock(&lockref->lock);
74360f5479SLinus Torvalds if (lockref->count > 0) {
752f4f12e5SLinus Torvalds lockref->count++;
76*6d2868d5SChristoph Hellwig retval = true;
772f4f12e5SLinus Torvalds }
782f4f12e5SLinus Torvalds spin_unlock(&lockref->lock);
792f4f12e5SLinus Torvalds return retval;
802f4f12e5SLinus Torvalds }
812f4f12e5SLinus Torvalds EXPORT_SYMBOL(lockref_get_not_zero);
822f4f12e5SLinus Torvalds
832f4f12e5SLinus Torvalds /**
84360f5479SLinus Torvalds * lockref_put_return - Decrement reference count if possible
85360f5479SLinus Torvalds * @lockref: pointer to lockref structure
86360f5479SLinus Torvalds *
87360f5479SLinus Torvalds * Decrement the reference count and return the new value.
88d60f2280SChristoph Hellwig * If the lockref was dead or locked, return -1.
89360f5479SLinus Torvalds */
lockref_put_return(struct lockref * lockref)90360f5479SLinus Torvalds int lockref_put_return(struct lockref *lockref)
91360f5479SLinus Torvalds {
92360f5479SLinus Torvalds CMPXCHG_LOOP(
93360f5479SLinus Torvalds new.count--;
94360f5479SLinus Torvalds if (old.count <= 0)
95360f5479SLinus Torvalds return -1;
96360f5479SLinus Torvalds ,
97360f5479SLinus Torvalds return new.count;
98360f5479SLinus Torvalds );
99360f5479SLinus Torvalds return -1;
100360f5479SLinus Torvalds }
101360f5479SLinus Torvalds EXPORT_SYMBOL(lockref_put_return);
102360f5479SLinus Torvalds
103360f5479SLinus Torvalds /**
1042f4f12e5SLinus Torvalds * lockref_put_or_lock - decrements count unless count <= 1 before decrement
10544a0cf92SLinus Torvalds * @lockref: pointer to lockref structure
1062f4f12e5SLinus Torvalds * Return: 1 if count updated successfully or 0 if count <= 1 and lock taken
1072f4f12e5SLinus Torvalds */
lockref_put_or_lock(struct lockref * lockref)108*6d2868d5SChristoph Hellwig bool lockref_put_or_lock(struct lockref *lockref)
1092f4f12e5SLinus Torvalds {
110bc08b449SLinus Torvalds CMPXCHG_LOOP(
111bc08b449SLinus Torvalds new.count--;
112bc08b449SLinus Torvalds if (old.count <= 1)
113bc08b449SLinus Torvalds break;
114bc08b449SLinus Torvalds ,
115*6d2868d5SChristoph Hellwig return true;
116bc08b449SLinus Torvalds );
117bc08b449SLinus Torvalds
1182f4f12e5SLinus Torvalds spin_lock(&lockref->lock);
1192f4f12e5SLinus Torvalds if (lockref->count <= 1)
120*6d2868d5SChristoph Hellwig return false;
1212f4f12e5SLinus Torvalds lockref->count--;
1222f4f12e5SLinus Torvalds spin_unlock(&lockref->lock);
123*6d2868d5SChristoph Hellwig return true;
1242f4f12e5SLinus Torvalds }
1252f4f12e5SLinus Torvalds EXPORT_SYMBOL(lockref_put_or_lock);
126e7d33bb5SLinus Torvalds
127e7d33bb5SLinus Torvalds /**
128e7d33bb5SLinus Torvalds * lockref_mark_dead - mark lockref dead
129e7d33bb5SLinus Torvalds * @lockref: pointer to lockref structure
130e7d33bb5SLinus Torvalds */
lockref_mark_dead(struct lockref * lockref)131e7d33bb5SLinus Torvalds void lockref_mark_dead(struct lockref *lockref)
132e7d33bb5SLinus Torvalds {
133e7d33bb5SLinus Torvalds assert_spin_locked(&lockref->lock);
134e7d33bb5SLinus Torvalds lockref->count = -128;
135e7d33bb5SLinus Torvalds }
136e66cf161SSteven Whitehouse EXPORT_SYMBOL(lockref_mark_dead);
137e7d33bb5SLinus Torvalds
138e7d33bb5SLinus Torvalds /**
139e7d33bb5SLinus Torvalds * lockref_get_not_dead - Increments count unless the ref is dead
140e7d33bb5SLinus Torvalds * @lockref: pointer to lockref structure
141e7d33bb5SLinus Torvalds * Return: 1 if count updated successfully or 0 if lockref was dead
142e7d33bb5SLinus Torvalds */
lockref_get_not_dead(struct lockref * lockref)143*6d2868d5SChristoph Hellwig bool lockref_get_not_dead(struct lockref *lockref)
144e7d33bb5SLinus Torvalds {
145*6d2868d5SChristoph Hellwig bool retval = false;
146e7d33bb5SLinus Torvalds
147e7d33bb5SLinus Torvalds CMPXCHG_LOOP(
148e7d33bb5SLinus Torvalds new.count++;
149360f5479SLinus Torvalds if (old.count < 0)
150*6d2868d5SChristoph Hellwig return false;
151e7d33bb5SLinus Torvalds ,
152*6d2868d5SChristoph Hellwig return true;
153e7d33bb5SLinus Torvalds );
154e7d33bb5SLinus Torvalds
155e7d33bb5SLinus Torvalds spin_lock(&lockref->lock);
156360f5479SLinus Torvalds if (lockref->count >= 0) {
157e7d33bb5SLinus Torvalds lockref->count++;
158*6d2868d5SChristoph Hellwig retval = true;
159e7d33bb5SLinus Torvalds }
160e7d33bb5SLinus Torvalds spin_unlock(&lockref->lock);
161e7d33bb5SLinus Torvalds return retval;
162e7d33bb5SLinus Torvalds }
163e7d33bb5SLinus Torvalds EXPORT_SYMBOL(lockref_get_not_dead);
164