Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0 2 : : #include <linux/err.h> 3 : : #include <linux/bug.h> 4 : : #include <linux/atomic.h> 5 : : #include <linux/errseq.h> 6 : : 7 : : /* 8 : : * An errseq_t is a way of recording errors in one place, and allowing any 9 : : * number of "subscribers" to tell whether it has changed since a previous 10 : : * point where it was sampled. 11 : : * 12 : : * It's implemented as an unsigned 32-bit value. The low order bits are 13 : : * designated to hold an error code (between 0 and -MAX_ERRNO). The upper bits 14 : : * are used as a counter. This is done with atomics instead of locking so that 15 : : * these functions can be called from any context. 16 : : * 17 : : * The general idea is for consumers to sample an errseq_t value. That value 18 : : * can later be used to tell whether any new errors have occurred since that 19 : : * sampling was done. 20 : : * 21 : : * Note that there is a risk of collisions if new errors are being recorded 22 : : * frequently, since we have so few bits to use as a counter. 23 : : * 24 : : * To mitigate this, one bit is used as a flag to tell whether the value has 25 : : * been sampled since a new value was recorded. That allows us to avoid bumping 26 : : * the counter if no one has sampled it since the last time an error was 27 : : * recorded. 28 : : * 29 : : * A new errseq_t should always be zeroed out. A errseq_t value of all zeroes 30 : : * is the special (but common) case where there has never been an error. An all 31 : : * zero value thus serves as the "epoch" if one wishes to know whether there 32 : : * has ever been an error set since it was first initialized. 33 : : */ 34 : : 35 : : /* The low bits are designated for error code (max of MAX_ERRNO) */ 36 : : #define ERRSEQ_SHIFT ilog2(MAX_ERRNO + 1) 37 : : 38 : : /* This bit is used as a flag to indicate whether the value has been seen */ 39 : : #define ERRSEQ_SEEN (1 << ERRSEQ_SHIFT) 40 : : 41 : : /* The lowest bit of the counter */ 42 : : #define ERRSEQ_CTR_INC (1 << (ERRSEQ_SHIFT + 1)) 43 : : 44 : : /** 45 : : * errseq_set - set a errseq_t for later reporting 46 : : * @eseq: errseq_t field that should be set 47 : : * @err: error to set (must be between -1 and -MAX_ERRNO) 48 : : * 49 : : * This function sets the error in @eseq, and increments the sequence counter 50 : : * if the last sequence was sampled at some point in the past. 51 : : * 52 : : * Any error set will always overwrite an existing error. 53 : : * 54 : : * Return: The previous value, primarily for debugging purposes. The 55 : : * return value should not be used as a previously sampled value in later 56 : : * calls as it will not have the SEEN flag set. 57 : : */ 58 : 0 : errseq_t errseq_set(errseq_t *eseq, int err) 59 : : { 60 : : errseq_t cur, old; 61 : : 62 : : /* MAX_ERRNO must be able to serve as a mask */ 63 : : BUILD_BUG_ON_NOT_POWER_OF_2(MAX_ERRNO + 1); 64 : : 65 : : /* 66 : : * Ensure the error code actually fits where we want it to go. If it 67 : : * doesn't then just throw a warning and don't record anything. We 68 : : * also don't accept zero here as that would effectively clear a 69 : : * previous error. 70 : : */ 71 : : old = READ_ONCE(*eseq); 72 : : 73 [ # # # # ]: 0 : if (WARN(unlikely(err == 0 || (unsigned int)-err > MAX_ERRNO), 74 : : "err = %d\n", err)) 75 : : return old; 76 : : 77 : : for (;;) { 78 : : errseq_t new; 79 : : 80 : : /* Clear out error bits and set new error */ 81 : 0 : new = (old & ~(MAX_ERRNO|ERRSEQ_SEEN)) | -err; 82 : : 83 : : /* Only increment if someone has looked at it */ 84 [ # # ]: 0 : if (old & ERRSEQ_SEEN) 85 : 0 : new += ERRSEQ_CTR_INC; 86 : : 87 : : /* If there would be no change, then call it done */ 88 [ # # ]: 0 : if (new == old) { 89 : 0 : cur = new; 90 : 0 : break; 91 : : } 92 : : 93 : : /* Try to swap the new value into place */ 94 : 0 : cur = cmpxchg(eseq, old, new); 95 : : 96 : : /* 97 : : * Call it success if we did the swap or someone else beat us 98 : : * to it for the same value. 99 : : */ 100 [ # # ]: 0 : if (likely(cur == old || cur == new)) 101 : : break; 102 : : 103 : : /* Raced with an update, try again */ 104 : : old = cur; 105 : : } 106 : 0 : return cur; 107 : : } 108 : : EXPORT_SYMBOL(errseq_set); 109 : : 110 : : /** 111 : : * errseq_sample() - Grab current errseq_t value. 112 : : * @eseq: Pointer to errseq_t to be sampled. 113 : : * 114 : : * This function allows callers to initialise their errseq_t variable. 115 : : * If the error has been "seen", new callers will not see an old error. 116 : : * If there is an unseen error in @eseq, the caller of this function will 117 : : * see it the next time it checks for an error. 118 : : * 119 : : * Context: Any context. 120 : : * Return: The current errseq value. 121 : : */ 122 : 38723960 : errseq_t errseq_sample(errseq_t *eseq) 123 : : { 124 : : errseq_t old = READ_ONCE(*eseq); 125 : : 126 : : /* If nobody has seen this error yet, then we can be the first. */ 127 [ + + ]: 38723960 : if (!(old & ERRSEQ_SEEN)) 128 : : old = 0; 129 : 38723960 : return old; 130 : : } 131 : : EXPORT_SYMBOL(errseq_sample); 132 : : 133 : : /** 134 : : * errseq_check() - Has an error occurred since a particular sample point? 135 : : * @eseq: Pointer to errseq_t value to be checked. 136 : : * @since: Previously-sampled errseq_t from which to check. 137 : : * 138 : : * Grab the value that eseq points to, and see if it has changed @since 139 : : * the given value was sampled. The @since value is not advanced, so there 140 : : * is no need to mark the value as seen. 141 : : * 142 : : * Return: The latest error set in the errseq_t or 0 if it hasn't changed. 143 : : */ 144 : 3566 : int errseq_check(errseq_t *eseq, errseq_t since) 145 : : { 146 : : errseq_t cur = READ_ONCE(*eseq); 147 : : 148 [ - + ]: 3566 : if (likely(cur == since)) 149 : : return 0; 150 : 0 : return -(cur & MAX_ERRNO); 151 : : } 152 : : EXPORT_SYMBOL(errseq_check); 153 : : 154 : : /** 155 : : * errseq_check_and_advance() - Check an errseq_t and advance to current value. 156 : : * @eseq: Pointer to value being checked and reported. 157 : : * @since: Pointer to previously-sampled errseq_t to check against and advance. 158 : : * 159 : : * Grab the eseq value, and see whether it matches the value that @since 160 : : * points to. If it does, then just return 0. 161 : : * 162 : : * If it doesn't, then the value has changed. Set the "seen" flag, and try to 163 : : * swap it into place as the new eseq value. Then, set that value as the new 164 : : * "since" value, and return whatever the error portion is set to. 165 : : * 166 : : * Note that no locking is provided here for concurrent updates to the "since" 167 : : * value. The caller must provide that if necessary. Because of this, callers 168 : : * may want to do a lockless errseq_check before taking the lock and calling 169 : : * this. 170 : : * 171 : : * Return: Negative errno if one has been stored, or 0 if no new error has 172 : : * occurred. 173 : : */ 174 : 0 : int errseq_check_and_advance(errseq_t *eseq, errseq_t *since) 175 : : { 176 : : int err = 0; 177 : : errseq_t old, new; 178 : : 179 : : /* 180 : : * Most callers will want to use the inline wrapper to check this, 181 : : * so that the common case of no error is handled without needing 182 : : * to take the lock that protects the "since" value. 183 : : */ 184 : : old = READ_ONCE(*eseq); 185 [ # # ]: 0 : if (old != *since) { 186 : : /* 187 : : * Set the flag and try to swap it into place if it has 188 : : * changed. 189 : : * 190 : : * We don't care about the outcome of the swap here. If the 191 : : * swap doesn't occur, then it has either been updated by a 192 : : * writer who is altering the value in some way (updating 193 : : * counter or resetting the error), or another reader who is 194 : : * just setting the "seen" flag. Either outcome is OK, and we 195 : : * can advance "since" and return an error based on what we 196 : : * have. 197 : : */ 198 : 0 : new = old | ERRSEQ_SEEN; 199 [ # # ]: 0 : if (new != old) 200 : 0 : cmpxchg(eseq, old, new); 201 : 0 : *since = new; 202 : 0 : err = -(new & MAX_ERRNO); 203 : : } 204 : 0 : return err; 205 : : } 206 : : EXPORT_SYMBOL(errseq_check_and_advance);