函数源码

Linux Kernel

v5.5.9

Brick Technologies Co., Ltd

Source File:lib\errseq.c Create Date:2022-07-27 07:22:38
首页 Copyright©Brick

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// SPDX-License-Identifier: GPL-2.0
#include <linux/err.h>
#include <linux/bug.h>
#include <linux/atomic.h>
#include <linux/errseq.h>
 
/*
 * An errseq_t is a way of recording errors in one place, and allowing any
 * number of "subscribers" to tell whether it has changed since a previous
 * point where it was sampled.
 *
 * It's implemented as an unsigned 32-bit value. The low order bits are
 * designated to hold an error code (between 0 and -MAX_ERRNO). The upper bits
 * are used as a counter. This is done with atomics instead of locking so that
 * these functions can be called from any context.
 *
 * The general idea is for consumers to sample an errseq_t value. That value
 * can later be used to tell whether any new errors have occurred since that
 * sampling was done.
 *
 * Note that there is a risk of collisions if new errors are being recorded
 * frequently, since we have so few bits to use as a counter.
 *
 * To mitigate this, one bit is used as a flag to tell whether the value has
 * been sampled since a new value was recorded. That allows us to avoid bumping
 * the counter if no one has sampled it since the last time an error was
 * recorded.
 *
 * A new errseq_t should always be zeroed out.  A errseq_t value of all zeroes
 * is the special (but common) case where there has never been an error. An all
 * zero value thus serves as the "epoch" if one wishes to know whether there
 * has ever been an error set since it was first initialized.
 */
 
/* The low bits are designated for error code (max of MAX_ERRNO) */
#define ERRSEQ_SHIFT        ilog2(MAX_ERRNO + 1)
 
/* This bit is used as a flag to indicate whether the value has been seen */
#define ERRSEQ_SEEN     (1 << ERRSEQ_SHIFT)
 
/* The lowest bit of the counter */
#define ERRSEQ_CTR_INC      (1 << (ERRSEQ_SHIFT + 1))
 
/**
 * errseq_set - set a errseq_t for later reporting
 * @eseq: errseq_t field that should be set
 * @err: error to set (must be between -1 and -MAX_ERRNO)
 *
 * This function sets the error in @eseq, and increments the sequence counter
 * if the last sequence was sampled at some point in the past.
 *
 * Any error set will always overwrite an existing error.
 *
 * Return: The previous value, primarily for debugging purposes. The
 * return value should not be used as a previously sampled value in later
 * calls as it will not have the SEEN flag set.
 */
errseq_t errseq_set(errseq_t *eseq, int err)
{
    errseq_t cur, old;
 
    /* MAX_ERRNO must be able to serve as a mask */
    BUILD_BUG_ON_NOT_POWER_OF_2(MAX_ERRNO + 1);
 
    /*
     * Ensure the error code actually fits where we want it to go. If it
     * doesn't then just throw a warning and don't record anything. We
     * also don't accept zero here as that would effectively clear a
     * previous error.
     */
    old = READ_ONCE(*eseq);
 
    if (WARN(unlikely(err == 0 || (unsigned int)-err > MAX_ERRNO),
                "err = %d\n", err))
        return old;
 
    for (;;) {
        errseq_t new;
 
        /* Clear out error bits and set new error */
        new = (old & ~(MAX_ERRNO|ERRSEQ_SEEN)) | -err;
 
        /* Only increment if someone has looked at it */
        if (old & ERRSEQ_SEEN)
            new += ERRSEQ_CTR_INC;
 
        /* If there would be no change, then call it done */
        if (new == old) {
            cur = new;
            break;
        }
 
        /* Try to swap the new value into place */
        cur = cmpxchg(eseq, old, new);
 
        /*
         * Call it success if we did the swap or someone else beat us
         * to it for the same value.
         */
        if (likely(cur == old || cur == new))
            break;
 
        /* Raced with an update, try again */
        old = cur;
    }
    return cur;
}