Re: [PATCH v3] kcov: move kcov_remote_data to task_struct for RT and remove local_lock

From: Tetsuo Handa

Date: Thu May 21 2026 - 22:40:02 EST


On 2026/05/22 3:14, Sebastian Andrzej Siewior wrote:
> On 2026-05-22 00:28:26 [+0900], Tetsuo Handa wrote:
>>> If handling softirqs in parallel might be indeed a reasonable solution.
>>> You would need to use the per-task area instead per-CPU for this in the
>>> softirq case on PREEMPT_RT. But the other checks, such as in_task()
>>> work as intended on both.
>>
>> https://share.google/aimode/PveuuU5od6d1lAwit (expires in 7 days) says
>>
>> Non-PREEMPT_RT Kernel: in_task() returns false while executing a softirq.
>>
>> PREEMPT_RT Kernel: in_task() returns true while executing a softirq (unless
>> nested inside a hard interrupt or NMI).
>>
>> Is this explanation wrong?
>
> Yes. If a softirq is executed (in_serving_softirq() returns true) then
> in_task() returns false. On PREEMPT_RT and on non-PREEMPT_RT kernels. It
> is irrelevant if the softirq is executed at the end of an interrupt
> handler and within ksoftirqd.

OK. Google AI mode admitted that this explanation was wrong.
Also, I heard from https://share.google/aimode/olzr3v2VEBJXepuiN that
I should not change in_*() checks.

Then, combining

(1) "Is it possible to unify the logic between RT and non-RT kernels (not have any ifdef's)?"
from Dmitry Vyukov

(2) "I think a deadlock is possible if a softirq is delivered while a background task is
executing kcov_remote_start():" from Alexander Potapenko

(3) "This I don't get. in_task() returns only true if it is a pure task-context:" from
Sebastian Andrzej Siewior

comments, the updated patch would look like below diff?

(I added "range 128 16777216" check to CONFIG_KCOV_IRQ_AREA_SIZE value, for size of buffer
has to be larger than sizeof(struct kcov_remote_area) bytes.)

include/linux/sched.h | 8 +++
kernel/kcov.c | 111 ++++++++++++++----------------------------
lib/Kconfig.debug | 5 +-
3 files changed, 47 insertions(+), 77 deletions(-)

diff --git a/include/linux/sched.h b/include/linux/sched.h
index ee06cba5c6f5..eaf975138359 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1525,6 +1525,14 @@ struct task_struct {

/* Collect coverage from softirq context: */
unsigned int kcov_softirq;
+
+ /* Temporary storage for preempting remote coverage collection: */
+ unsigned int kcov_saved_mode;
+ unsigned int kcov_saved_size;
+ void *kcov_saved_area;
+ struct kcov *kcov_saved_kcov;
+ int kcov_saved_sequence;
+
#endif

#ifdef CONFIG_MEMCG_V1
diff --git a/kernel/kcov.c b/kernel/kcov.c
index 0b369e88c7c9..3b0444ef8588 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -88,21 +88,6 @@ static DEFINE_SPINLOCK(kcov_remote_lock);
static DEFINE_HASHTABLE(kcov_remote_map, 4);
static struct list_head kcov_remote_areas = LIST_HEAD_INIT(kcov_remote_areas);

-struct kcov_percpu_data {
- void *irq_area;
- local_lock_t lock;
-
- unsigned int saved_mode;
- unsigned int saved_size;
- void *saved_area;
- struct kcov *saved_kcov;
- int saved_sequence;
-};
-
-static DEFINE_PER_CPU(struct kcov_percpu_data, kcov_percpu_data) = {
- .lock = INIT_LOCAL_LOCK(lock),
-};
-
/* Must be called with kcov_remote_lock locked. */
static struct kcov_remote *kcov_remote_find(u64 handle)
{
@@ -824,37 +809,32 @@ static inline bool kcov_mode_enabled(unsigned int mode)
}

static void kcov_remote_softirq_start(struct task_struct *t)
- __must_hold(&kcov_percpu_data.lock)
{
- struct kcov_percpu_data *data = this_cpu_ptr(&kcov_percpu_data);
unsigned int mode;

mode = READ_ONCE(t->kcov_mode);
barrier();
if (kcov_mode_enabled(mode)) {
- data->saved_mode = mode;
- data->saved_size = t->kcov_size;
- data->saved_area = t->kcov_area;
- data->saved_sequence = t->kcov_sequence;
- data->saved_kcov = t->kcov;
+ t->kcov_saved_mode = mode;
+ t->kcov_saved_size = t->kcov_size;
+ t->kcov_saved_area = t->kcov_area;
+ t->kcov_saved_sequence = t->kcov_sequence;
+ t->kcov_saved_kcov = t->kcov;
kcov_stop(t);
}
}

static void kcov_remote_softirq_stop(struct task_struct *t)
- __must_hold(&kcov_percpu_data.lock)
{
- struct kcov_percpu_data *data = this_cpu_ptr(&kcov_percpu_data);
-
- if (data->saved_kcov) {
- kcov_start(t, data->saved_kcov, data->saved_size,
- data->saved_area, data->saved_mode,
- data->saved_sequence);
- data->saved_mode = 0;
- data->saved_size = 0;
- data->saved_area = NULL;
- data->saved_sequence = 0;
- data->saved_kcov = NULL;
+ if (t->kcov_saved_kcov) {
+ kcov_start(t, t->kcov_saved_kcov, t->kcov_saved_size,
+ t->kcov_saved_area, t->kcov_saved_mode,
+ t->kcov_saved_sequence);
+ t->kcov_saved_mode = 0;
+ t->kcov_saved_size = 0;
+ t->kcov_saved_area = NULL;
+ t->kcov_saved_sequence = 0;
+ t->kcov_saved_kcov = NULL;
}
}

@@ -874,15 +854,12 @@ void kcov_remote_start(u64 handle)
if (!in_task() && !in_softirq_really())
return;

- local_lock_irqsave(&kcov_percpu_data.lock, flags);
-
/*
* Check that kcov_remote_start() is not called twice in background
* threads nor called by user tasks (with enabled kcov).
*/
mode = READ_ONCE(t->kcov_mode);
if (WARN_ON(in_task() && kcov_mode_enabled(mode))) {
- local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
return;
}
/*
@@ -891,15 +868,13 @@ void kcov_remote_start(u64 handle)
* happened while collecting coverage from a background thread.
*/
if (WARN_ON(in_serving_softirq() && t->kcov_softirq)) {
- local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
return;
}

- spin_lock(&kcov_remote_lock);
+ spin_lock_irqsave(&kcov_remote_lock, flags);
remote = kcov_remote_find(handle);
if (!remote) {
- spin_unlock(&kcov_remote_lock);
- local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
+ spin_unlock_irqrestore(&kcov_remote_lock, flags);
return;
}
kcov_debug("handle = %llx, context: %s\n", handle,
@@ -915,24 +890,17 @@ void kcov_remote_start(u64 handle)
*/
mode = context_unsafe(kcov->mode);
sequence = kcov->sequence;
- if (in_task()) {
- size = kcov->remote_size;
- area = kcov_remote_area_get(size);
- } else {
- size = CONFIG_KCOV_IRQ_AREA_SIZE;
- area = this_cpu_ptr(&kcov_percpu_data)->irq_area;
- }
- spin_unlock(&kcov_remote_lock);
+ size = in_task() ? kcov->remote_size : CONFIG_KCOV_IRQ_AREA_SIZE;
+ area = kcov_remote_area_get(size);
+ spin_unlock_irqrestore(&kcov_remote_lock, flags);

- /* Can only happen when in_task(). */
- if (!area) {
- local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
+ /* Allocate new buffer if we can sleep. */
+ if (!area && in_task()) {
area = vmalloc(size * sizeof(unsigned long));
if (!area) {
kcov_put(kcov);
return;
}
- local_lock_irqsave(&kcov_percpu_data.lock, flags);
}

/* Reset coverage size. */
@@ -943,9 +911,6 @@ void kcov_remote_start(u64 handle)
t->kcov_softirq = 1;
}
kcov_start(t, kcov, size, area, mode, sequence);
-
- local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
-
}
EXPORT_SYMBOL(kcov_remote_start);

@@ -1027,12 +992,9 @@ void kcov_remote_stop(void)
if (!in_task() && !in_softirq_really())
return;

- local_lock_irqsave(&kcov_percpu_data.lock, flags);
-
mode = READ_ONCE(t->kcov_mode);
barrier();
if (!kcov_mode_enabled(mode)) {
- local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
return;
}
/*
@@ -1040,12 +1002,10 @@ void kcov_remote_stop(void)
* actually found the remote handle and started collecting coverage.
*/
if (in_serving_softirq() && !t->kcov_softirq) {
- local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
return;
}
/* Make sure that kcov_softirq is only set when in softirq. */
if (WARN_ON(!in_serving_softirq() && t->kcov_softirq)) {
- local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
return;
}

@@ -1069,13 +1029,9 @@ void kcov_remote_stop(void)
kcov_move_area(kcov->mode, kcov->area, kcov->size, area);
spin_unlock(&kcov->lock);

- if (in_task()) {
- spin_lock(&kcov_remote_lock);
- kcov_remote_area_put(area, size);
- spin_unlock(&kcov_remote_lock);
- }
-
- local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
+ spin_lock_irqsave(&kcov_remote_lock, flags);
+ kcov_remote_area_put(area, size);
+ spin_unlock_irqrestore(&kcov_remote_lock, flags);

/* Get in kcov_remote_start(). */
kcov_put(kcov);
@@ -1119,14 +1075,19 @@ static void __init selftest(void)

static int __init kcov_init(void)
{
- int cpu;
+ int i, cpu = num_possible_cpus();
+ unsigned long flags;

- for_each_possible_cpu(cpu) {
- void *area = vmalloc_node(CONFIG_KCOV_IRQ_AREA_SIZE *
- sizeof(unsigned long), cpu_to_node(cpu));
- if (!area)
- return -ENOMEM;
- per_cpu_ptr(&kcov_percpu_data, cpu)->irq_area = area;
+#ifdef CONFIG_PREEMPT_RT
+ /* Allocate some extra buffers in order to prepare for softirq preemption. */
+ cpu = cpu >= 4 ? cpu * 2 : cpu + 4;
+#endif
+ for (i = 0; i < cpu; i++) {
+ struct kcov_remote_area *area =
+ vmalloc(CONFIG_KCOV_IRQ_AREA_SIZE * sizeof(unsigned long));
+ spin_lock_irqsave(&kcov_remote_lock, flags);
+ kcov_remote_area_put(area, CONFIG_KCOV_IRQ_AREA_SIZE);
+ spin_unlock_irqrestore(&kcov_remote_lock, flags);
}

/*
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 8ff5adcfe1e0..1f3e917dbac7 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2247,10 +2247,11 @@ config KCOV_INSTRUMENT_ALL
config KCOV_IRQ_AREA_SIZE
hex "Size of interrupt coverage collection area in words"
depends on KCOV
+ range 128 16777216
default 0x40000
help
- KCOV uses preallocated per-cpu areas to collect coverage from
- soft interrupts. This specifies the size of those areas in the
+ KCOV uses preallocated areas to collect coverage from soft
+ interrupts. This specifies the size of those areas in the
number of unsigned long words.

config KCOV_SELFTEST