mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-17 09:43:59 +08:00
Merge branch 'core-rcu-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull RCU updates from Ingo Molnar: "The main RCU changes in this development cycle were: - Miscellaneous fixes, including a change to call_rcu()'s rcu_head alignment check. - Security-motivated list consistency checks, which are disabled by default behind DEBUG_LIST. - Torture-test updates. - Documentation updates, yet again just simple changes" * 'core-rcu-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: torture: Prevent jitter from delaying build-only runs torture: Remove obsolete files from rcutorture .gitignore rcu: Don't kick unless grace period or request rcu: Make expedited grace periods recheck dyntick idle state torture: Trace long read-side delays rcu: RCU_TRACE enables event tracing as well as debugfs rcu: Remove obsolete comment from __call_rcu() rcu: Remove obsolete rcu_check_callbacks() header comment rcu: Tighten up __call_rcu() rcu_head alignment check Documentation/RCU: Fix minor typo documentation: Present updated RCU guarantee bug: Avoid Kconfig warning for BUG_ON_DATA_CORRUPTION lib/Kconfig.debug: Fix typo in select statement lkdtm: Add tests for struct list corruption bug: Provide toggle for BUG on data corruption list: Split list_del() debug checking into separate function rculist: Consolidate DEBUG_LIST for list_add_rcu() list: Split list_add() debug checking into separate function
This commit is contained in:
commit
718c0ddd6a
@ -547,7 +547,7 @@ The <tt>rcu_access_pointer()</tt> on line 6 is similar to
|
|||||||
It could reuse a value formerly fetched from this same pointer.
|
It could reuse a value formerly fetched from this same pointer.
|
||||||
It could also fetch the pointer from <tt>gp</tt> in a byte-at-a-time
|
It could also fetch the pointer from <tt>gp</tt> in a byte-at-a-time
|
||||||
manner, resulting in <i>load tearing</i>, in turn resulting a bytewise
|
manner, resulting in <i>load tearing</i>, in turn resulting a bytewise
|
||||||
mash-up of two distince pointer values.
|
mash-up of two distinct pointer values.
|
||||||
It might even use value-speculation optimizations, where it makes
|
It might even use value-speculation optimizations, where it makes
|
||||||
a wrong guess, but by the time it gets around to checking the
|
a wrong guess, but by the time it gets around to checking the
|
||||||
value, an update has changed the pointer to match the wrong guess.
|
value, an update has changed the pointer to match the wrong guess.
|
||||||
@ -659,6 +659,29 @@ systems with more than one CPU:
|
|||||||
In other words, a given instance of <tt>synchronize_rcu()</tt>
|
In other words, a given instance of <tt>synchronize_rcu()</tt>
|
||||||
can avoid waiting on a given RCU read-side critical section only
|
can avoid waiting on a given RCU read-side critical section only
|
||||||
if it can prove that <tt>synchronize_rcu()</tt> started first.
|
if it can prove that <tt>synchronize_rcu()</tt> started first.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
A related question is “When <tt>rcu_read_lock()</tt>
|
||||||
|
doesn't generate any code, why does it matter how it relates
|
||||||
|
to a grace period?”
|
||||||
|
The answer is that it is not the relationship of
|
||||||
|
<tt>rcu_read_lock()</tt> itself that is important, but rather
|
||||||
|
the relationship of the code within the enclosed RCU read-side
|
||||||
|
critical section to the code preceding and following the
|
||||||
|
grace period.
|
||||||
|
If we take this viewpoint, then a given RCU read-side critical
|
||||||
|
section begins before a given grace period when some access
|
||||||
|
preceding the grace period observes the effect of some access
|
||||||
|
within the critical section, in which case none of the accesses
|
||||||
|
within the critical section may observe the effects of any
|
||||||
|
access following the grace period.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
As of late 2016, mathematical models of RCU take this
|
||||||
|
viewpoint, for example, see slides 62 and 63
|
||||||
|
of the
|
||||||
|
<a href="http://www2.rdrop.com/users/paulmck/scalability/paper/LinuxMM.2016.10.04c.LCE.pdf">2016 LinuxCon EU</a>
|
||||||
|
presentation.
|
||||||
</font></td></tr>
|
</font></td></tr>
|
||||||
<tr><td> </td></tr>
|
<tr><td> </td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -237,7 +237,7 @@ rcu_dereference()
|
|||||||
|
|
||||||
The reader uses rcu_dereference() to fetch an RCU-protected
|
The reader uses rcu_dereference() to fetch an RCU-protected
|
||||||
pointer, which returns a value that may then be safely
|
pointer, which returns a value that may then be safely
|
||||||
dereferenced. Note that rcu_deference() does not actually
|
dereferenced. Note that rcu_dereference() does not actually
|
||||||
dereference the pointer, instead, it protects the pointer for
|
dereference the pointer, instead, it protects the pointer for
|
||||||
later dereferencing. It also executes any needed memory-barrier
|
later dereferencing. It also executes any needed memory-barrier
|
||||||
instructions for a given CPU architecture. Currently, only Alpha
|
instructions for a given CPU architecture. Currently, only Alpha
|
||||||
|
@ -21,6 +21,8 @@ void lkdtm_SPINLOCKUP(void);
|
|||||||
void lkdtm_HUNG_TASK(void);
|
void lkdtm_HUNG_TASK(void);
|
||||||
void lkdtm_ATOMIC_UNDERFLOW(void);
|
void lkdtm_ATOMIC_UNDERFLOW(void);
|
||||||
void lkdtm_ATOMIC_OVERFLOW(void);
|
void lkdtm_ATOMIC_OVERFLOW(void);
|
||||||
|
void lkdtm_CORRUPT_LIST_ADD(void);
|
||||||
|
void lkdtm_CORRUPT_LIST_DEL(void);
|
||||||
|
|
||||||
/* lkdtm_heap.c */
|
/* lkdtm_heap.c */
|
||||||
void lkdtm_OVERWRITE_ALLOCATION(void);
|
void lkdtm_OVERWRITE_ALLOCATION(void);
|
||||||
|
@ -5,8 +5,13 @@
|
|||||||
* test source files.
|
* test source files.
|
||||||
*/
|
*/
|
||||||
#include "lkdtm.h"
|
#include "lkdtm.h"
|
||||||
|
#include <linux/list.h>
|
||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
|
|
||||||
|
struct lkdtm_list {
|
||||||
|
struct list_head node;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make sure our attempts to over run the kernel stack doesn't trigger
|
* Make sure our attempts to over run the kernel stack doesn't trigger
|
||||||
* a compiler warning when CONFIG_FRAME_WARN is set. Then make sure we
|
* a compiler warning when CONFIG_FRAME_WARN is set. Then make sure we
|
||||||
@ -146,3 +151,66 @@ void lkdtm_ATOMIC_OVERFLOW(void)
|
|||||||
pr_info("attempting bad atomic overflow\n");
|
pr_info("attempting bad atomic overflow\n");
|
||||||
atomic_inc(&over);
|
atomic_inc(&over);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void lkdtm_CORRUPT_LIST_ADD(void)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Initially, an empty list via LIST_HEAD:
|
||||||
|
* test_head.next = &test_head
|
||||||
|
* test_head.prev = &test_head
|
||||||
|
*/
|
||||||
|
LIST_HEAD(test_head);
|
||||||
|
struct lkdtm_list good, bad;
|
||||||
|
void *target[2] = { };
|
||||||
|
void *redirection = ⌖
|
||||||
|
|
||||||
|
pr_info("attempting good list addition\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Adding to the list performs these actions:
|
||||||
|
* test_head.next->prev = &good.node
|
||||||
|
* good.node.next = test_head.next
|
||||||
|
* good.node.prev = test_head
|
||||||
|
* test_head.next = good.node
|
||||||
|
*/
|
||||||
|
list_add(&good.node, &test_head);
|
||||||
|
|
||||||
|
pr_info("attempting corrupted list addition\n");
|
||||||
|
/*
|
||||||
|
* In simulating this "write what where" primitive, the "what" is
|
||||||
|
* the address of &bad.node, and the "where" is the address held
|
||||||
|
* by "redirection".
|
||||||
|
*/
|
||||||
|
test_head.next = redirection;
|
||||||
|
list_add(&bad.node, &test_head);
|
||||||
|
|
||||||
|
if (target[0] == NULL && target[1] == NULL)
|
||||||
|
pr_err("Overwrite did not happen, but no BUG?!\n");
|
||||||
|
else
|
||||||
|
pr_err("list_add() corruption not detected!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void lkdtm_CORRUPT_LIST_DEL(void)
|
||||||
|
{
|
||||||
|
LIST_HEAD(test_head);
|
||||||
|
struct lkdtm_list item;
|
||||||
|
void *target[2] = { };
|
||||||
|
void *redirection = ⌖
|
||||||
|
|
||||||
|
list_add(&item.node, &test_head);
|
||||||
|
|
||||||
|
pr_info("attempting good list removal\n");
|
||||||
|
list_del(&item.node);
|
||||||
|
|
||||||
|
pr_info("attempting corrupted list removal\n");
|
||||||
|
list_add(&item.node, &test_head);
|
||||||
|
|
||||||
|
/* As with the list_add() test above, this corrupts "next". */
|
||||||
|
item.node.next = redirection;
|
||||||
|
list_del(&item.node);
|
||||||
|
|
||||||
|
if (target[0] == NULL && target[1] == NULL)
|
||||||
|
pr_err("Overwrite did not happen, but no BUG?!\n");
|
||||||
|
else
|
||||||
|
pr_err("list_del() corruption not detected!\n");
|
||||||
|
}
|
||||||
|
@ -197,6 +197,8 @@ struct crashtype crashtypes[] = {
|
|||||||
CRASHTYPE(EXCEPTION),
|
CRASHTYPE(EXCEPTION),
|
||||||
CRASHTYPE(LOOP),
|
CRASHTYPE(LOOP),
|
||||||
CRASHTYPE(OVERFLOW),
|
CRASHTYPE(OVERFLOW),
|
||||||
|
CRASHTYPE(CORRUPT_LIST_ADD),
|
||||||
|
CRASHTYPE(CORRUPT_LIST_DEL),
|
||||||
CRASHTYPE(CORRUPT_STACK),
|
CRASHTYPE(CORRUPT_STACK),
|
||||||
CRASHTYPE(UNALIGNED_LOAD_STORE_WRITE),
|
CRASHTYPE(UNALIGNED_LOAD_STORE_WRITE),
|
||||||
CRASHTYPE(OVERWRITE_ALLOCATION),
|
CRASHTYPE(OVERWRITE_ALLOCATION),
|
||||||
|
@ -121,4 +121,21 @@ static inline enum bug_trap_type report_bug(unsigned long bug_addr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif /* CONFIG_GENERIC_BUG */
|
#endif /* CONFIG_GENERIC_BUG */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since detected data corruption should stop operation on the affected
|
||||||
|
* structures, this returns false if the corruption condition is found.
|
||||||
|
*/
|
||||||
|
#define CHECK_DATA_CORRUPTION(condition, fmt, ...) \
|
||||||
|
do { \
|
||||||
|
if (unlikely(condition)) { \
|
||||||
|
if (IS_ENABLED(CONFIG_BUG_ON_DATA_CORRUPTION)) { \
|
||||||
|
pr_err(fmt, ##__VA_ARGS__); \
|
||||||
|
BUG(); \
|
||||||
|
} else \
|
||||||
|
WARN(1, fmt, ##__VA_ARGS__); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
#endif /* _LINUX_BUG_H */
|
#endif /* _LINUX_BUG_H */
|
||||||
|
@ -28,27 +28,42 @@ static inline void INIT_LIST_HEAD(struct list_head *list)
|
|||||||
list->prev = list;
|
list->prev = list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_DEBUG_LIST
|
||||||
|
extern bool __list_add_valid(struct list_head *new,
|
||||||
|
struct list_head *prev,
|
||||||
|
struct list_head *next);
|
||||||
|
extern bool __list_del_entry_valid(struct list_head *entry);
|
||||||
|
#else
|
||||||
|
static inline bool __list_add_valid(struct list_head *new,
|
||||||
|
struct list_head *prev,
|
||||||
|
struct list_head *next)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static inline bool __list_del_entry_valid(struct list_head *entry)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Insert a new entry between two known consecutive entries.
|
* Insert a new entry between two known consecutive entries.
|
||||||
*
|
*
|
||||||
* This is only for internal list manipulation where we know
|
* This is only for internal list manipulation where we know
|
||||||
* the prev/next entries already!
|
* the prev/next entries already!
|
||||||
*/
|
*/
|
||||||
#ifndef CONFIG_DEBUG_LIST
|
|
||||||
static inline void __list_add(struct list_head *new,
|
static inline void __list_add(struct list_head *new,
|
||||||
struct list_head *prev,
|
struct list_head *prev,
|
||||||
struct list_head *next)
|
struct list_head *next)
|
||||||
{
|
{
|
||||||
|
if (!__list_add_valid(new, prev, next))
|
||||||
|
return;
|
||||||
|
|
||||||
next->prev = new;
|
next->prev = new;
|
||||||
new->next = next;
|
new->next = next;
|
||||||
new->prev = prev;
|
new->prev = prev;
|
||||||
WRITE_ONCE(prev->next, new);
|
WRITE_ONCE(prev->next, new);
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
extern void __list_add(struct list_head *new,
|
|
||||||
struct list_head *prev,
|
|
||||||
struct list_head *next);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* list_add - add a new entry
|
* list_add - add a new entry
|
||||||
@ -96,22 +111,20 @@ static inline void __list_del(struct list_head * prev, struct list_head * next)
|
|||||||
* Note: list_empty() on entry does not return true after this, the entry is
|
* Note: list_empty() on entry does not return true after this, the entry is
|
||||||
* in an undefined state.
|
* in an undefined state.
|
||||||
*/
|
*/
|
||||||
#ifndef CONFIG_DEBUG_LIST
|
|
||||||
static inline void __list_del_entry(struct list_head *entry)
|
static inline void __list_del_entry(struct list_head *entry)
|
||||||
{
|
{
|
||||||
|
if (!__list_del_entry_valid(entry))
|
||||||
|
return;
|
||||||
|
|
||||||
__list_del(entry->prev, entry->next);
|
__list_del(entry->prev, entry->next);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void list_del(struct list_head *entry)
|
static inline void list_del(struct list_head *entry)
|
||||||
{
|
{
|
||||||
__list_del(entry->prev, entry->next);
|
__list_del_entry(entry);
|
||||||
entry->next = LIST_POISON1;
|
entry->next = LIST_POISON1;
|
||||||
entry->prev = LIST_POISON2;
|
entry->prev = LIST_POISON2;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
extern void __list_del_entry(struct list_head *entry);
|
|
||||||
extern void list_del(struct list_head *entry);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* list_replace - replace old entry by new one
|
* list_replace - replace old entry by new one
|
||||||
|
@ -45,19 +45,17 @@ static inline void INIT_LIST_HEAD_RCU(struct list_head *list)
|
|||||||
* This is only for internal list manipulation where we know
|
* This is only for internal list manipulation where we know
|
||||||
* the prev/next entries already!
|
* the prev/next entries already!
|
||||||
*/
|
*/
|
||||||
#ifndef CONFIG_DEBUG_LIST
|
|
||||||
static inline void __list_add_rcu(struct list_head *new,
|
static inline void __list_add_rcu(struct list_head *new,
|
||||||
struct list_head *prev, struct list_head *next)
|
struct list_head *prev, struct list_head *next)
|
||||||
{
|
{
|
||||||
|
if (!__list_add_valid(new, prev, next))
|
||||||
|
return;
|
||||||
|
|
||||||
new->next = next;
|
new->next = next;
|
||||||
new->prev = prev;
|
new->prev = prev;
|
||||||
rcu_assign_pointer(list_next_rcu(prev), new);
|
rcu_assign_pointer(list_next_rcu(prev), new);
|
||||||
next->prev = new;
|
next->prev = new;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
void __list_add_rcu(struct list_head *new,
|
|
||||||
struct list_head *prev, struct list_head *next);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* list_add_rcu - add a new entry to rcu-protected list
|
* list_add_rcu - add a new entry to rcu-protected list
|
||||||
|
@ -698,7 +698,10 @@ TRACE_EVENT(rcu_batch_end,
|
|||||||
/*
|
/*
|
||||||
* Tracepoint for rcutorture readers. The first argument is the name
|
* Tracepoint for rcutorture readers. The first argument is the name
|
||||||
* of the RCU flavor from rcutorture's viewpoint and the second argument
|
* of the RCU flavor from rcutorture's viewpoint and the second argument
|
||||||
* is the callback address.
|
* is the callback address. The third argument is the start time in
|
||||||
|
* seconds, and the last two arguments are the grace period numbers
|
||||||
|
* at the beginning and end of the read, respectively. Note that the
|
||||||
|
* callback address can be NULL.
|
||||||
*/
|
*/
|
||||||
TRACE_EVENT(rcu_torture_read,
|
TRACE_EVENT(rcu_torture_read,
|
||||||
|
|
||||||
|
@ -289,15 +289,24 @@ static int rcu_torture_read_lock(void) __acquires(RCU)
|
|||||||
|
|
||||||
static void rcu_read_delay(struct torture_random_state *rrsp)
|
static void rcu_read_delay(struct torture_random_state *rrsp)
|
||||||
{
|
{
|
||||||
|
unsigned long started;
|
||||||
|
unsigned long completed;
|
||||||
const unsigned long shortdelay_us = 200;
|
const unsigned long shortdelay_us = 200;
|
||||||
const unsigned long longdelay_ms = 50;
|
const unsigned long longdelay_ms = 50;
|
||||||
|
unsigned long long ts;
|
||||||
|
|
||||||
/* We want a short delay sometimes to make a reader delay the grace
|
/* We want a short delay sometimes to make a reader delay the grace
|
||||||
* period, and we want a long delay occasionally to trigger
|
* period, and we want a long delay occasionally to trigger
|
||||||
* force_quiescent_state. */
|
* force_quiescent_state. */
|
||||||
|
|
||||||
if (!(torture_random(rrsp) % (nrealreaders * 2000 * longdelay_ms)))
|
if (!(torture_random(rrsp) % (nrealreaders * 2000 * longdelay_ms))) {
|
||||||
|
started = cur_ops->completed();
|
||||||
|
ts = rcu_trace_clock_local();
|
||||||
mdelay(longdelay_ms);
|
mdelay(longdelay_ms);
|
||||||
|
completed = cur_ops->completed();
|
||||||
|
do_trace_rcu_torture_read(cur_ops->name, NULL, ts,
|
||||||
|
started, completed);
|
||||||
|
}
|
||||||
if (!(torture_random(rrsp) % (nrealreaders * 2 * shortdelay_us)))
|
if (!(torture_random(rrsp) % (nrealreaders * 2 * shortdelay_us)))
|
||||||
udelay(shortdelay_us);
|
udelay(shortdelay_us);
|
||||||
#ifdef CONFIG_PREEMPT
|
#ifdef CONFIG_PREEMPT
|
||||||
|
@ -1304,7 +1304,8 @@ static void rcu_stall_kick_kthreads(struct rcu_state *rsp)
|
|||||||
if (!rcu_kick_kthreads)
|
if (!rcu_kick_kthreads)
|
||||||
return;
|
return;
|
||||||
j = READ_ONCE(rsp->jiffies_kick_kthreads);
|
j = READ_ONCE(rsp->jiffies_kick_kthreads);
|
||||||
if (time_after(jiffies, j) && rsp->gp_kthread) {
|
if (time_after(jiffies, j) && rsp->gp_kthread &&
|
||||||
|
(rcu_gp_in_progress(rsp) || READ_ONCE(rsp->gp_flags))) {
|
||||||
WARN_ONCE(1, "Kicking %s grace-period kthread\n", rsp->name);
|
WARN_ONCE(1, "Kicking %s grace-period kthread\n", rsp->name);
|
||||||
rcu_ftrace_dump(DUMP_ALL);
|
rcu_ftrace_dump(DUMP_ALL);
|
||||||
wake_up_process(rsp->gp_kthread);
|
wake_up_process(rsp->gp_kthread);
|
||||||
@ -2828,8 +2829,7 @@ static void rcu_do_batch(struct rcu_state *rsp, struct rcu_data *rdp)
|
|||||||
* Also schedule RCU core processing.
|
* Also schedule RCU core processing.
|
||||||
*
|
*
|
||||||
* This function must be called from hardirq context. It is normally
|
* This function must be called from hardirq context. It is normally
|
||||||
* invoked from the scheduling-clock interrupt. If rcu_pending returns
|
* invoked from the scheduling-clock interrupt.
|
||||||
* false, there is no point in invoking rcu_check_callbacks().
|
|
||||||
*/
|
*/
|
||||||
void rcu_check_callbacks(int user)
|
void rcu_check_callbacks(int user)
|
||||||
{
|
{
|
||||||
@ -3121,7 +3121,9 @@ __call_rcu(struct rcu_head *head, rcu_callback_t func,
|
|||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
struct rcu_data *rdp;
|
struct rcu_data *rdp;
|
||||||
|
|
||||||
WARN_ON_ONCE((unsigned long)head & 0x1); /* Misaligned rcu_head! */
|
/* Misaligned rcu_head! */
|
||||||
|
WARN_ON_ONCE((unsigned long)head & (sizeof(void *) - 1));
|
||||||
|
|
||||||
if (debug_rcu_head_queue(head)) {
|
if (debug_rcu_head_queue(head)) {
|
||||||
/* Probable double call_rcu(), so leak the callback. */
|
/* Probable double call_rcu(), so leak the callback. */
|
||||||
WRITE_ONCE(head->func, rcu_leak_callback);
|
WRITE_ONCE(head->func, rcu_leak_callback);
|
||||||
@ -3130,13 +3132,6 @@ __call_rcu(struct rcu_head *head, rcu_callback_t func,
|
|||||||
}
|
}
|
||||||
head->func = func;
|
head->func = func;
|
||||||
head->next = NULL;
|
head->next = NULL;
|
||||||
|
|
||||||
/*
|
|
||||||
* Opportunistically note grace-period endings and beginnings.
|
|
||||||
* Note that we might see a beginning right after we see an
|
|
||||||
* end, but never vice versa, since this CPU has to pass through
|
|
||||||
* a quiescent state betweentimes.
|
|
||||||
*/
|
|
||||||
local_irq_save(flags);
|
local_irq_save(flags);
|
||||||
rdp = this_cpu_ptr(rsp->rda);
|
rdp = this_cpu_ptr(rsp->rda);
|
||||||
|
|
||||||
|
@ -404,6 +404,7 @@ struct rcu_data {
|
|||||||
atomic_long_t exp_workdone1; /* # done by others #1. */
|
atomic_long_t exp_workdone1; /* # done by others #1. */
|
||||||
atomic_long_t exp_workdone2; /* # done by others #2. */
|
atomic_long_t exp_workdone2; /* # done by others #2. */
|
||||||
atomic_long_t exp_workdone3; /* # done by others #3. */
|
atomic_long_t exp_workdone3; /* # done by others #3. */
|
||||||
|
int exp_dynticks_snap; /* Double-check need for IPI. */
|
||||||
|
|
||||||
/* 7) Callback offloading. */
|
/* 7) Callback offloading. */
|
||||||
#ifdef CONFIG_RCU_NOCB_CPU
|
#ifdef CONFIG_RCU_NOCB_CPU
|
||||||
|
@ -358,8 +358,10 @@ static void sync_rcu_exp_select_cpus(struct rcu_state *rsp,
|
|||||||
struct rcu_data *rdp = per_cpu_ptr(rsp->rda, cpu);
|
struct rcu_data *rdp = per_cpu_ptr(rsp->rda, cpu);
|
||||||
struct rcu_dynticks *rdtp = &per_cpu(rcu_dynticks, cpu);
|
struct rcu_dynticks *rdtp = &per_cpu(rcu_dynticks, cpu);
|
||||||
|
|
||||||
|
rdp->exp_dynticks_snap =
|
||||||
|
atomic_add_return(0, &rdtp->dynticks);
|
||||||
if (raw_smp_processor_id() == cpu ||
|
if (raw_smp_processor_id() == cpu ||
|
||||||
!(atomic_add_return(0, &rdtp->dynticks) & 0x1) ||
|
!(rdp->exp_dynticks_snap & 0x1) ||
|
||||||
!(rnp->qsmaskinitnext & rdp->grpmask))
|
!(rnp->qsmaskinitnext & rdp->grpmask))
|
||||||
mask_ofl_test |= rdp->grpmask;
|
mask_ofl_test |= rdp->grpmask;
|
||||||
}
|
}
|
||||||
@ -377,9 +379,17 @@ static void sync_rcu_exp_select_cpus(struct rcu_state *rsp,
|
|||||||
/* IPI the remaining CPUs for expedited quiescent state. */
|
/* IPI the remaining CPUs for expedited quiescent state. */
|
||||||
for_each_leaf_node_possible_cpu(rnp, cpu) {
|
for_each_leaf_node_possible_cpu(rnp, cpu) {
|
||||||
unsigned long mask = leaf_node_cpu_bit(rnp, cpu);
|
unsigned long mask = leaf_node_cpu_bit(rnp, cpu);
|
||||||
|
struct rcu_data *rdp = per_cpu_ptr(rsp->rda, cpu);
|
||||||
|
struct rcu_dynticks *rdtp = &per_cpu(rcu_dynticks, cpu);
|
||||||
|
|
||||||
if (!(mask_ofl_ipi & mask))
|
if (!(mask_ofl_ipi & mask))
|
||||||
continue;
|
continue;
|
||||||
retry_ipi:
|
retry_ipi:
|
||||||
|
if (atomic_add_return(0, &rdtp->dynticks) !=
|
||||||
|
rdp->exp_dynticks_snap) {
|
||||||
|
mask_ofl_test |= mask;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
ret = smp_call_function_single(cpu, func, rsp, 0);
|
ret = smp_call_function_single(cpu, func, rsp, 0);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
mask_ofl_ipi &= ~mask;
|
mask_ofl_ipi &= ~mask;
|
||||||
|
@ -1218,7 +1218,7 @@ config DEBUG_BUGVERBOSE
|
|||||||
|
|
||||||
config DEBUG_LIST
|
config DEBUG_LIST
|
||||||
bool "Debug linked list manipulation"
|
bool "Debug linked list manipulation"
|
||||||
depends on DEBUG_KERNEL
|
depends on DEBUG_KERNEL || BUG_ON_DATA_CORRUPTION
|
||||||
help
|
help
|
||||||
Enable this to turn on extended checks in the linked-list
|
Enable this to turn on extended checks in the linked-list
|
||||||
walking routines.
|
walking routines.
|
||||||
@ -1434,7 +1434,8 @@ config RCU_TRACE
|
|||||||
select TRACE_CLOCK
|
select TRACE_CLOCK
|
||||||
help
|
help
|
||||||
This option provides tracing in RCU which presents stats
|
This option provides tracing in RCU which presents stats
|
||||||
in debugfs for debugging RCU implementation.
|
in debugfs for debugging RCU implementation. It also enables
|
||||||
|
additional tracepoints for ftrace-style event tracing.
|
||||||
|
|
||||||
Say Y here if you want to enable RCU tracing
|
Say Y here if you want to enable RCU tracing
|
||||||
Say N if you are unsure.
|
Say N if you are unsure.
|
||||||
@ -1964,6 +1965,16 @@ config TEST_STATIC_KEYS
|
|||||||
|
|
||||||
If unsure, say N.
|
If unsure, say N.
|
||||||
|
|
||||||
|
config BUG_ON_DATA_CORRUPTION
|
||||||
|
bool "Trigger a BUG when data corruption is detected"
|
||||||
|
select DEBUG_LIST
|
||||||
|
help
|
||||||
|
Select this option if the kernel should BUG when it encounters
|
||||||
|
data corruption in kernel memory structures when they get checked
|
||||||
|
for validity.
|
||||||
|
|
||||||
|
If unsure, say N.
|
||||||
|
|
||||||
source "samples/Kconfig"
|
source "samples/Kconfig"
|
||||||
|
|
||||||
source "lib/Kconfig.kgdb"
|
source "lib/Kconfig.kgdb"
|
||||||
|
101
lib/list_debug.c
101
lib/list_debug.c
@ -2,8 +2,7 @@
|
|||||||
* Copyright 2006, Red Hat, Inc., Dave Jones
|
* Copyright 2006, Red Hat, Inc., Dave Jones
|
||||||
* Released under the General Public License (GPL).
|
* Released under the General Public License (GPL).
|
||||||
*
|
*
|
||||||
* This file contains the linked list implementations for
|
* This file contains the linked list validation for DEBUG_LIST.
|
||||||
* DEBUG_LIST.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/export.h>
|
#include <linux/export.h>
|
||||||
@ -13,88 +12,48 @@
|
|||||||
#include <linux/rculist.h>
|
#include <linux/rculist.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Insert a new entry between two known consecutive entries.
|
* Check that the data structures for the list manipulations are reasonably
|
||||||
*
|
* valid. Failures here indicate memory corruption (and possibly an exploit
|
||||||
* This is only for internal list manipulation where we know
|
* attempt).
|
||||||
* the prev/next entries already!
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void __list_add(struct list_head *new,
|
bool __list_add_valid(struct list_head *new, struct list_head *prev,
|
||||||
struct list_head *prev,
|
struct list_head *next)
|
||||||
struct list_head *next)
|
|
||||||
{
|
{
|
||||||
WARN(next->prev != prev,
|
CHECK_DATA_CORRUPTION(next->prev != prev,
|
||||||
"list_add corruption. next->prev should be "
|
"list_add corruption. next->prev should be prev (%p), but was %p. (next=%p).\n",
|
||||||
"prev (%p), but was %p. (next=%p).\n",
|
|
||||||
prev, next->prev, next);
|
prev, next->prev, next);
|
||||||
WARN(prev->next != next,
|
CHECK_DATA_CORRUPTION(prev->next != next,
|
||||||
"list_add corruption. prev->next should be "
|
"list_add corruption. prev->next should be next (%p), but was %p. (prev=%p).\n",
|
||||||
"next (%p), but was %p. (prev=%p).\n",
|
|
||||||
next, prev->next, prev);
|
next, prev->next, prev);
|
||||||
WARN(new == prev || new == next,
|
CHECK_DATA_CORRUPTION(new == prev || new == next,
|
||||||
"list_add double add: new=%p, prev=%p, next=%p.\n",
|
"list_add double add: new=%p, prev=%p, next=%p.\n",
|
||||||
new, prev, next);
|
new, prev, next);
|
||||||
next->prev = new;
|
|
||||||
new->next = next;
|
|
||||||
new->prev = prev;
|
|
||||||
WRITE_ONCE(prev->next, new);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(__list_add);
|
|
||||||
|
|
||||||
void __list_del_entry(struct list_head *entry)
|
return true;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(__list_add_valid);
|
||||||
|
|
||||||
|
bool __list_del_entry_valid(struct list_head *entry)
|
||||||
{
|
{
|
||||||
struct list_head *prev, *next;
|
struct list_head *prev, *next;
|
||||||
|
|
||||||
prev = entry->prev;
|
prev = entry->prev;
|
||||||
next = entry->next;
|
next = entry->next;
|
||||||
|
|
||||||
if (WARN(next == LIST_POISON1,
|
CHECK_DATA_CORRUPTION(next == LIST_POISON1,
|
||||||
"list_del corruption, %p->next is LIST_POISON1 (%p)\n",
|
"list_del corruption, %p->next is LIST_POISON1 (%p)\n",
|
||||||
entry, LIST_POISON1) ||
|
entry, LIST_POISON1);
|
||||||
WARN(prev == LIST_POISON2,
|
CHECK_DATA_CORRUPTION(prev == LIST_POISON2,
|
||||||
"list_del corruption, %p->prev is LIST_POISON2 (%p)\n",
|
"list_del corruption, %p->prev is LIST_POISON2 (%p)\n",
|
||||||
entry, LIST_POISON2) ||
|
entry, LIST_POISON2);
|
||||||
WARN(prev->next != entry,
|
CHECK_DATA_CORRUPTION(prev->next != entry,
|
||||||
"list_del corruption. prev->next should be %p, "
|
"list_del corruption. prev->next should be %p, but was %p\n",
|
||||||
"but was %p\n", entry, prev->next) ||
|
entry, prev->next);
|
||||||
WARN(next->prev != entry,
|
CHECK_DATA_CORRUPTION(next->prev != entry,
|
||||||
"list_del corruption. next->prev should be %p, "
|
"list_del corruption. next->prev should be %p, but was %p\n",
|
||||||
"but was %p\n", entry, next->prev))
|
entry, next->prev);
|
||||||
return;
|
return true;
|
||||||
|
|
||||||
__list_del(prev, next);
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(__list_del_entry);
|
EXPORT_SYMBOL(__list_del_entry_valid);
|
||||||
|
|
||||||
/**
|
|
||||||
* list_del - deletes entry from list.
|
|
||||||
* @entry: the element to delete from the list.
|
|
||||||
* Note: list_empty on entry does not return true after this, the entry is
|
|
||||||
* in an undefined state.
|
|
||||||
*/
|
|
||||||
void list_del(struct list_head *entry)
|
|
||||||
{
|
|
||||||
__list_del_entry(entry);
|
|
||||||
entry->next = LIST_POISON1;
|
|
||||||
entry->prev = LIST_POISON2;
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(list_del);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RCU variants.
|
|
||||||
*/
|
|
||||||
void __list_add_rcu(struct list_head *new,
|
|
||||||
struct list_head *prev, struct list_head *next)
|
|
||||||
{
|
|
||||||
WARN(next->prev != prev,
|
|
||||||
"list_add_rcu corruption. next->prev should be prev (%p), but was %p. (next=%p).\n",
|
|
||||||
prev, next->prev, next);
|
|
||||||
WARN(prev->next != next,
|
|
||||||
"list_add_rcu corruption. prev->next should be next (%p), but was %p. (prev=%p).\n",
|
|
||||||
next, prev->next, prev);
|
|
||||||
new->next = next;
|
|
||||||
new->prev = prev;
|
|
||||||
rcu_assign_pointer(list_next_rcu(prev), new);
|
|
||||||
next->prev = new;
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(__list_add_rcu);
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
initrd
|
initrd
|
||||||
linux-2.6
|
|
||||||
b[0-9]*
|
b[0-9]*
|
||||||
rcu-test-image
|
|
||||||
res
|
res
|
||||||
*.swp
|
*.swp
|
||||||
|
@ -303,6 +303,7 @@ then
|
|||||||
fi
|
fi
|
||||||
___EOF___
|
___EOF___
|
||||||
awk < $T/cfgcpu.pack \
|
awk < $T/cfgcpu.pack \
|
||||||
|
-v TORTURE_BUILDONLY="$TORTURE_BUILDONLY" \
|
||||||
-v CONFIGDIR="$CONFIGFRAG/" \
|
-v CONFIGDIR="$CONFIGFRAG/" \
|
||||||
-v KVM="$KVM" \
|
-v KVM="$KVM" \
|
||||||
-v ncpus=$cpus \
|
-v ncpus=$cpus \
|
||||||
@ -375,6 +376,10 @@ function dump(first, pastlast, batchnum)
|
|||||||
njitter = ncpus;
|
njitter = ncpus;
|
||||||
else
|
else
|
||||||
njitter = ja[1];
|
njitter = ja[1];
|
||||||
|
if (TORTURE_BUILDONLY && njitter != 0) {
|
||||||
|
njitter = 0;
|
||||||
|
print "echo Build-only run, so suppressing jitter >> " rd "/log"
|
||||||
|
}
|
||||||
for (j = 0; j < njitter; j++)
|
for (j = 0; j < njitter; j++)
|
||||||
print "jitter.sh " j " " dur " " ja[2] " " ja[3] "&"
|
print "jitter.sh " j " " dur " " ja[2] " " ja[3] "&"
|
||||||
print "wait"
|
print "wait"
|
||||||
|
Loading…
Reference in New Issue
Block a user