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

Tetsuo Handa posted 1 patch 1 month ago
include/linux/sched.h | 10 +++++
kernel/kcov.c         | 94 +++++++++++++++++++++++++------------------
lib/Kconfig.debug     |  1 +
3 files changed, 65 insertions(+), 40 deletions(-)
[PATCH v3] kcov: move kcov_remote_data to task_struct for RT and remove local_lock
Posted by Tetsuo Handa 1 month ago
In CONFIG_PREEMPT_RT=y kernels, softirqs are executed in a per-CPU
ksoftirqd thread or in the context of the task that raised the softirq.
This means in_task() can return true even while serving softirqs. This
behavior causes KCOV to incorrectly identify the context, leading to state
corruption and various kcov-related warnings reported by syzbot.

Furthermore, commit d5d2c51f1e5f ("kcov: replace local_irq_save() with a
local_lock_t") introduced local_lock to protect per-CPU kcov_percpu_data.
However, the need for this physical CPU-level locking is questionable
because:

1. kcov_remote_start/stop() already bail out early if called from
   in_hardirq() or in_nmi().
2. The core tracing function check_kcov_mode() also skips hardirq/NMI
   contexts, meaning no KCOV state is accessed during hardirqs even if
   they interrupt a KCOV-enabled task/softirq.
3. In non-RT kernels, softirqs do not nest, so no concurrent access to
   per-CPU data occurs between softirqs.
4. In RT kernels, while softirqs can be preempted, this patch moves the
   KCOV state from per-CPU variables to task_struct (per-task), eliminating
   the contention on shared per-CPU resources.

By moving kcov_remote_data to task_struct for RT kernels and replacing
the in_task() check with !in_serving_softirq(), we ensure consistent
context detection. Since the data is now isolated per-task and not accessed
by hardirqs, the local_lock (and the original local_irq_save) is no longer
necessary and is removed to reduce overhead.

Changes:

* Move remote coverage state from kcov_percpu_data to task_struct under
  CONFIG_PREEMPT_RT.
* Replace in_task() with !in_serving_softirq() in kcov_remote_start/stop()
  for accurate context tracking.
* Remove local_lock and IRQ disabling from kcov_remote_start/stop() as the
  state is now task-local and hardirqs are already excluded.
* Ensure CONFIG_PREEMPT_RT=y uses kcov_remote_area_get() (the
  vmalloc-backed pool) instead of the single per-CPU irq_area.

Analyzed-by: AI Mode in Google Search (no mail address)
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Fixes: 5ff3b30ab57d ("kcov: collect coverage from interrupts")
---
Only compile tested. I don't have environment to measure how not
preallocating CONFIG_KCOV_IRQ_AREA_SIZE bytes of buffers and
add to the global pool in kcov_init() impacts reliability / performance.
Please be sure to test this patch using CONFIG_PREEMPT_RT=y and
CONFIG_PREEMPT_RT=n kernels in local syzkaller environment before
sending upstream.

This patch is expected to address various KCOV related reports.
But I don't add Closes: lines because we can't tell whether this patch
alone is sufficient for marking as Closes:. We will find the answer
some time after being sent to upstream.

  https://syzkaller.appspot.com/bug?extid=90984d3713722683112e
  https://syzkaller.appspot.com/bug?extid=47cf95ca1f9dcca872c8
  https://syzkaller.appspot.com/bug?extid=8a173e13208949931dc7
  https://syzkaller.appspot.com/bug?extid=3f51ad7ac3ae57a6fdcc
  https://syzkaller.appspot.com/bug?extid=e6686317bd9fe911591a

 include/linux/sched.h | 10 +++++
 kernel/kcov.c         | 94 +++++++++++++++++++++++++------------------
 lib/Kconfig.debug     |  1 +
 3 files changed, 65 insertions(+), 40 deletions(-)

diff --git a/include/linux/sched.h b/include/linux/sched.h
index 368c7b4d7cb5..2c963f4271d6 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1522,6 +1522,16 @@ struct task_struct {
 
 	/* Collect coverage from softirq context: */
 	unsigned int			kcov_softirq;
+
+#ifdef CONFIG_PREEMPT_RT
+	/* 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
+
 #endif
 
 #ifdef CONFIG_MEMCG_V1
diff --git a/kernel/kcov.c b/kernel/kcov.c
index 0b369e88c7c9..965c11a75b36 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -88,9 +88,9 @@ 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);
 
+#ifndef CONFIG_PREEMPT_RT
 struct kcov_percpu_data {
 	void			*irq_area;
-	local_lock_t		lock;
 
 	unsigned int		saved_mode;
 	unsigned int		saved_size;
@@ -100,8 +100,8 @@ struct kcov_percpu_data {
 };
 
 static DEFINE_PER_CPU(struct kcov_percpu_data, kcov_percpu_data) = {
-	.lock = INIT_LOCAL_LOCK(lock),
 };
+#endif
 
 /* Must be called with kcov_remote_lock locked. */
 static struct kcov_remote *kcov_remote_find(u64 handle)
@@ -823,8 +823,38 @@ static inline bool kcov_mode_enabled(unsigned int mode)
 	return (mode & ~KCOV_IN_CTXSW) != KCOV_MODE_DISABLED;
 }
 
+#ifdef CONFIG_PREEMPT_RT
+static void kcov_remote_softirq_start(struct task_struct *t)
+{
+	unsigned int mode;
+
+	mode = READ_ONCE(t->kcov_mode);
+	barrier();
+	if (kcov_mode_enabled(mode)) {
+		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)
+{
+	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;
+	}
+}
+#else
 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;
@@ -842,7 +872,6 @@ static void kcov_remote_softirq_start(struct task_struct *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);
 
@@ -857,6 +886,7 @@ static void kcov_remote_softirq_stop(struct task_struct *t)
 		data->saved_kcov = NULL;
 	}
 }
+#endif
 
 void kcov_remote_start(u64 handle)
 {
@@ -867,43 +897,35 @@ void kcov_remote_start(u64 handle)
 	void *area;
 	unsigned int size;
 	int sequence;
-	unsigned long flags;
 
-	if (WARN_ON(!kcov_check_handle(handle, true, true, true)))
+	/* Don't use in_task() in order to allow consistent checks in RT kernels. */
+	if (in_hardirq() || in_nmi())
 		return;
-	if (!in_task() && !in_softirq_really())
+	if (WARN_ON(!kcov_check_handle(handle, true, true, true)))
 		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);
+	if (WARN_ON(!in_serving_softirq() && kcov_mode_enabled(READ_ONCE(t->kcov_mode))))
 		return;
-	}
 	/*
 	 * Check that kcov_remote_start() is not called twice in softirqs.
 	 * Note, that kcov_remote_start() can be called from a softirq that
 	 * 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);
+	if (WARN_ON(in_serving_softirq() && t->kcov_softirq))
 		return;
-	}
 
 	spin_lock(&kcov_remote_lock);
 	remote = kcov_remote_find(handle);
 	if (!remote) {
 		spin_unlock(&kcov_remote_lock);
-		local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
 		return;
 	}
 	kcov_debug("handle = %llx, context: %s\n", handle,
-			in_task() ? "task" : "softirq");
+		   !in_serving_softirq() ? "task" : "softirq");
 	kcov = remote->kcov;
 	/* Put in kcov_remote_stop(). */
 	kcov_get(kcov);
@@ -915,6 +937,10 @@ void kcov_remote_start(u64 handle)
 	 */
 	mode = context_unsafe(kcov->mode);
 	sequence = kcov->sequence;
+#ifdef CONFIG_PREEMPT_RT
+	size = kcov->remote_size;
+	area = kcov_remote_area_get(size);
+#else
 	if (in_task()) {
 		size = kcov->remote_size;
 		area = kcov_remote_area_get(size);
@@ -922,17 +948,16 @@ void kcov_remote_start(u64 handle)
 		size = CONFIG_KCOV_IRQ_AREA_SIZE;
 		area = this_cpu_ptr(&kcov_percpu_data)->irq_area;
 	}
+#endif
 	spin_unlock(&kcov_remote_lock);
 
-	/* Can only happen when in_task(). */
+	/* Can only happen when CONFIG_PREEMPT_RT=y or in_task(). */
 	if (!area) {
-		local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
 		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 +968,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);
 
@@ -1022,32 +1044,24 @@ void kcov_remote_stop(void)
 	void *area;
 	unsigned int size;
 	int sequence;
-	unsigned long flags;
 
-	if (!in_task() && !in_softirq_really())
+	/* Don't use in_task() in order to allow consistent checks in RT kernels. */
+	if (in_hardirq() || in_nmi())
 		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);
+	if (!kcov_mode_enabled(mode))
 		return;
-	}
 	/*
 	 * When in softirq, check if the corresponding kcov_remote_start()
 	 * actually found the remote handle and started collecting coverage.
 	 */
-	if (in_serving_softirq() && !t->kcov_softirq) {
-		local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
+	if (in_serving_softirq() && !t->kcov_softirq)
 		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);
+	if (WARN_ON(!in_serving_softirq() && t->kcov_softirq))
 		return;
-	}
 
 	kcov = t->kcov;
 	area = t->kcov_area;
@@ -1069,14 +1083,12 @@ void kcov_remote_stop(void)
 		kcov_move_area(kcov->mode, kcov->area, kcov->size, area);
 	spin_unlock(&kcov->lock);
 
-	if (in_task()) {
+	if (IS_ENABLED(CONFIG_PREEMPT_RT) || 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);
-
 	/* Get in kcov_remote_start(). */
 	kcov_put(kcov);
 }
@@ -1119,6 +1131,7 @@ static void __init selftest(void)
 
 static int __init kcov_init(void)
 {
+#ifndef CONFIG_PREEMPT_RT
 	int cpu;
 
 	for_each_possible_cpu(cpu) {
@@ -1128,6 +1141,7 @@ static int __init kcov_init(void)
 			return -ENOMEM;
 		per_cpu_ptr(&kcov_percpu_data, cpu)->irq_area = area;
 	}
+#endif
 
 	/*
 	 * The kcov debugfs file won't ever get removed and thus,
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 13f3297aa823..493be2c73c9d 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2247,6 +2247,7 @@ config KCOV_INSTRUMENT_ALL
 config KCOV_IRQ_AREA_SIZE
 	hex "Size of interrupt coverage collection area in words"
 	depends on KCOV
+	depends on !PREEMPT_RT
 	default 0x40000
 	help
 	  KCOV uses preallocated per-cpu areas to collect coverage from
-- 
2.47.3
Re: [PATCH v3] kcov: move kcov_remote_data to task_struct for RT and remove local_lock
Posted by Alexander Potapenko 3 weeks, 1 day ago
I think a deadlock is possible if a softirq is delivered while a
background task is executing kcov_remote_start():

>  void kcov_remote_start(u64 handle)
>  {
> @@ -867,43 +897,35 @@ void kcov_remote_start(u64 handle)
>         void *area;
>         unsigned int size;
>         int sequence;
> -       unsigned long flags;
>
> -       if (WARN_ON(!kcov_check_handle(handle, true, true, true)))
> +       /* Don't use in_task() in order to allow consistent checks in RT kernels. */
> +       if (in_hardirq() || in_nmi())
>                 return;
> -       if (!in_task() && !in_softirq_really())
> +       if (WARN_ON(!kcov_check_handle(handle, true, true, true)))
>                 return;
>
> -       local_lock_irqsave(&kcov_percpu_data.lock, flags);
> -

Removing this lock allows softirqs on the current task (that task
could be e.g. a background task in vhost_kthread_worker_create())

>         /*
>          * 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);
> +       if (WARN_ON(!in_serving_softirq() && kcov_mode_enabled(READ_ONCE(t->kcov_mode))))
>                 return;
> -       }
>         /*
>          * Check that kcov_remote_start() is not called twice in softirqs.
>          * Note, that kcov_remote_start() can be called from a softirq that
>          * 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);
> +       if (WARN_ON(in_serving_softirq() && t->kcov_softirq))
>                 return;
> -       }
>
>         spin_lock(&kcov_remote_lock);

A softirq may kick in here and start executing e.g.
__usb_hcd_giveback_urb(), which calls kcov_remote_start() in a nested
fashion.
in_hardirq() and in_nmi() is now false, and in_serving_softirq() is
true, so we will slide past all the WARN_ON checks.
This includes `WARN_ON(in_serving_softirq() && t->kcov_softirq)`,
because we haven't set `t->kcov_softirq` yet.

As a result, the task will attempt to grab `kcov_remote_lock` recursively.
Re: [PATCH v3] kcov: move kcov_remote_data to task_struct for RT and remove local_lock
Posted by Tetsuo Handa 3 weeks, 1 day ago
On 2026/05/21 22:06, Alexander Potapenko wrote:
> I think a deadlock is possible if a softirq is delivered while a
> background task is executing kcov_remote_start():

Indeed. Since kcov_remote_start() is called by

  static int vhost_run_work_kthread_list(void *data)
  {
    kcov_remote_start_common(worker->kcov_handle);
    work->fn(work);
    kcov_remote_stop();
  }

and it can be preempted by

  static void usb_giveback_urb_bh(struct work_struct *work)
  {
    static void __usb_hcd_giveback_urb(struct urb *urb)
    {
      kcov_remote_start_usb_softirq((u64)urb->dev->bus->busnum);
      urb->complete(urb);
      kcov_remote_stop_softirq();
    }
  }

, we need to use _irq{save,restore} version. Any other concerns?

 kernel/kcov.c |   12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/kernel/kcov.c b/kernel/kcov.c
index 965c11a75b36..70ce99c3205d 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -897,6 +897,7 @@ void kcov_remote_start(u64 handle)
 	void *area;
 	unsigned int size;
 	int sequence;
+	unsigned long flags;
 
 	/* Don't use in_task() in order to allow consistent checks in RT kernels. */
 	if (in_hardirq() || in_nmi())
@@ -918,10 +919,10 @@ void kcov_remote_start(u64 handle)
 	if (WARN_ON(in_serving_softirq() && t->kcov_softirq))
 		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);
+		spin_unlock_irqrestore(&kcov_remote_lock, flags);
 		return;
 	}
 	kcov_debug("handle = %llx, context: %s\n", handle,
@@ -949,7 +950,7 @@ void kcov_remote_start(u64 handle)
 		area = this_cpu_ptr(&kcov_percpu_data)->irq_area;
 	}
 #endif
-	spin_unlock(&kcov_remote_lock);
+	spin_unlock_irqrestore(&kcov_remote_lock, flags);
 
 	/* Can only happen when CONFIG_PREEMPT_RT=y or in_task(). */
 	if (!area) {
@@ -1044,6 +1045,7 @@ void kcov_remote_stop(void)
 	void *area;
 	unsigned int size;
 	int sequence;
+	unsigned long flags;
 
 	/* Don't use in_task() in order to allow consistent checks in RT kernels. */
 	if (in_hardirq() || in_nmi())
@@ -1084,9 +1086,9 @@ void kcov_remote_stop(void)
 	spin_unlock(&kcov->lock);
 
 	if (IS_ENABLED(CONFIG_PREEMPT_RT) || in_task()) {
-		spin_lock(&kcov_remote_lock);
+		spin_lock_irqsave(&kcov_remote_lock, flags);
 		kcov_remote_area_put(area, size);
-		spin_unlock(&kcov_remote_lock);
+		spin_unlock_irqrestore(&kcov_remote_lock, flags);
 	}
 
 	/* Get in kcov_remote_start(). */
Re: [PATCH v3] kcov: move kcov_remote_data to task_struct for RT and remove local_lock
Posted by Sebastian Andrzej Siewior 3 weeks, 1 day ago
On 2026-05-09 17:09:44 [+0900], Tetsuo Handa wrote:
> 3. In non-RT kernels, softirqs do not nest, so no concurrent access to
>    per-CPU data occurs between softirqs.
> 4. In RT kernels, while softirqs can be preempted, this patch moves the
>    KCOV state from per-CPU variables to task_struct (per-task), eliminating
>    the contention on shared per-CPU resources.

This is correct: The softirq handling can be preempted _and_ another
softirq can be handled. softirq handling is still per-task. However this
could mess up the per-CPU data if it is not meant to be shared by two
tasks doing softirq in parallel.

> By moving kcov_remote_data to task_struct for RT kernels and replacing
> the in_task() check with !in_serving_softirq(), we ensure consistent

This I don't get. in_task() returns only true if it is a pure
task-context: no hardirq, or softirq handling. in_serving_softirq()
returns true if softirqs are handled but also if the current context got
interrupted by a hardirq _while_ softirqs were served. That is why there
is the in_softirq_really() to limit it just the softirq-only case.

> context detection. Since the data is now isolated per-task and not accessed
> by hardirqs, the local_lock (and the original local_irq_save) is no longer
> necessary and is removed to reduce overhead.
> 
> Changes:
> 
> * Move remote coverage state from kcov_percpu_data to task_struct under
>   CONFIG_PREEMPT_RT.
> * Replace in_task() with !in_serving_softirq() in kcov_remote_start/stop()
>   for accurate context tracking.

Doesn't this exclude softirq handling and allows hardirq/ nmi?

> Only compile tested. I don't have environment to measure how not
> preallocating CONFIG_KCOV_IRQ_AREA_SIZE bytes of buffers and
> add to the global pool in kcov_init() impacts reliability / performance.
> Please be sure to test this patch using CONFIG_PREEMPT_RT=y and
> CONFIG_PREEMPT_RT=n kernels in local syzkaller environment before
> sending upstream.
> 
> This patch is expected to address various KCOV related reports.
> But I don't add Closes: lines because we can't tell whether this patch
> alone is sufficient for marking as Closes:. We will find the answer
> some time after being sent to upstream.
> 
>   https://syzkaller.appspot.com/bug?extid=90984d3713722683112e
>   https://syzkaller.appspot.com/bug?extid=47cf95ca1f9dcca872c8
>   https://syzkaller.appspot.com/bug?extid=8a173e13208949931dc7
>   https://syzkaller.appspot.com/bug?extid=3f51ad7ac3ae57a6fdcc
>   https://syzkaller.appspot.com/bug?extid=e6686317bd9fe911591a

None of these have a reproducer which is unfortunate. The parallel
softirq handling is only possible since commit 3253cb49cbad4 ("softirq:
Allow to drop the softirq-BKL lock on PREEMPT_RT") which is v6.18-rc1.
This is true most of these reports since they are from 2026 except for
two in the 3f51ad7ac3ae57a6fdcc report. But this might be another issue
which got fixed since then.

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.

Sebastian
Re: [PATCH v3] kcov: move kcov_remote_data to task_struct for RT and remove local_lock
Posted by Tetsuo Handa 3 weeks, 1 day ago
On 2026/05/21 17:38, Sebastian Andrzej Siewior wrote:
>> By moving kcov_remote_data to task_struct for RT kernels and replacing
>> the in_task() check with !in_serving_softirq(), we ensure consistent
> 
> This I don't get. in_task() returns only true if it is a pure
> task-context: no hardirq, or softirq handling. in_serving_softirq()
> returns true if softirqs are handled but also if the current context got
> interrupted by a hardirq _while_ softirqs were served. That is why there
> is the in_softirq_really() to limit it just the softirq-only case.

This patch already excludes hardirq / nmi contexts at the beginning of
kcov_remote_start()/kcov_remote_stop(). Therefore, only task / softirq
contexts can reach the location in question.

> 
>> context detection. Since the data is now isolated per-task and not accessed
>> by hardirqs, the local_lock (and the original local_irq_save) is no longer
>> necessary and is removed to reduce overhead.
>>
>> Changes:
>>
>> * Move remote coverage state from kcov_percpu_data to task_struct under
>>   CONFIG_PREEMPT_RT.
>> * Replace in_task() with !in_serving_softirq() in kcov_remote_start/stop()
>>   for accurate context tracking.
> 
> Doesn't this exclude softirq handling and allows hardirq/ nmi?

Ditto.

> 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?
Re: [PATCH v3] kcov: move kcov_remote_data to task_struct for RT and remove local_lock
Posted by Sebastian Andrzej Siewior 3 weeks, 1 day ago
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.

Sebastian
Re: [PATCH v3] kcov: move kcov_remote_data to task_struct for RT and remove local_lock
Posted by Tetsuo Handa 3 weeks, 1 day ago
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
[PATCH v4] kcov: move kcov_remote_data to task_struct and remove local_lock
Posted by Tetsuo Handa 3 weeks ago
Softirq handling can be preempted in RT kernels. If we use per-CPU buffers
for saving/restoring temporary KCOV contexts, various kcov-related warnings
happen due to state corruption.

This patch unconditionally moves temporary KCOV context buffers from
per-CPU to per-task. Although it is safe to continue using per-CPU buffers
in !RT kernels, this patch tries to unify the logic, for Dmitry Vyukov
commented

  Is it possible to unify the logic between RT and non-RT kernels (not
  have any ifdef's)?
  The logic is already rather complicated, having 2 work modes, and
  limited testing will be problematic in the future.

.

The local_lock_t is removed, for per-CPU buffers are removed.
But kcov_remote_lock and _irqsave/irqrestore remain in order to serialize
access to kcov_remote_areas lists, for kcov_remote_{start,stop}() are
called from both task context and softirq context.

Analyzed-by: AI Mode in Google Search (no mail address)
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Fixes: 5ff3b30ab57d ("kcov: collect coverage from interrupts")
---
Only compile tested.

 include/linux/sched.h |   8 +++
 kernel/kcov.c         | 111 +++++++++++++++---------------------------
 lib/Kconfig.debug     |   5 +-
 3 files changed, 51 insertions(+), 73 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..b96d6ad68f92 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -86,21 +86,8 @@ struct kcov_remote {
 
 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),
+static struct list_head kcov_remote_areas[2] = {
+	LIST_HEAD_INIT(kcov_remote_areas[0]), LIST_HEAD_INIT(kcov_remote_areas[1])
 };
 
 /* Must be called with kcov_remote_lock locked. */
@@ -136,8 +123,9 @@ static struct kcov_remote_area *kcov_remote_area_get(unsigned int size)
 {
 	struct kcov_remote_area *area;
 	struct list_head *pos;
+	struct list_head *list = &kcov_remote_areas[size == CONFIG_KCOV_IRQ_AREA_SIZE];
 
-	list_for_each(pos, &kcov_remote_areas) {
+	list_for_each(pos, list) {
 		area = list_entry(pos, struct kcov_remote_area, list);
 		if (area->size == size) {
 			list_del(&area->list);
@@ -153,7 +141,7 @@ static void kcov_remote_area_put(struct kcov_remote_area *area,
 {
 	INIT_LIST_HEAD(&area->list);
 	area->size = size;
-	list_add(&area->list, &kcov_remote_areas);
+	list_add(&area->list, &kcov_remote_areas[size == CONFIG_KCOV_IRQ_AREA_SIZE]);
 	/*
 	 * KMSAN doesn't instrument this file, so it may not know area->list
 	 * is initialized. Unpoison it explicitly to avoid reports in
@@ -824,37 +812,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 +857,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 +871,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,
@@ -920,19 +898,17 @@ void kcov_remote_start(u64 handle)
 		area = kcov_remote_area_get(size);
 	} else {
 		size = CONFIG_KCOV_IRQ_AREA_SIZE;
-		area = this_cpu_ptr(&kcov_percpu_data)->irq_area;
+		area = kcov_remote_area_get(size);
 	}
-	spin_unlock(&kcov_remote_lock);
+	spin_unlock_irqrestore(&kcov_remote_lock, flags);
 
-	/* Can only happen when in_task(). */
+	/* Allocate new buffer if we can sleep. */
 	if (!area) {
-		local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
-		area = vmalloc(size * sizeof(unsigned long));
+		area = in_task() ? vmalloc(size * sizeof(unsigned long)) : NULL;
 		if (!area) {
 			kcov_put(kcov);
 			return;
 		}
-		local_lock_irqsave(&kcov_percpu_data.lock, flags);
 	}
 
 	/* Reset coverage size. */
@@ -943,9 +919,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 +1000,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 +1010,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 +1037,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 +1083,19 @@ static void __init selftest(void)
 
 static int __init kcov_init(void)
 {
-	int cpu;
+	int cpu = num_possible_cpus();
 
-	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
+	while (cpu--) {
+		void *area = vmalloc(CONFIG_KCOV_IRQ_AREA_SIZE * sizeof(unsigned long));
+		unsigned long flags;
+
+		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..b9e6798dc66d 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 0x80 0x1000000
 	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
-- 
2.54.0
Re: [PATCH v4] kcov: move kcov_remote_data to task_struct and remove local_lock
Posted by Tetsuo Handa 2 weeks ago
On 2026/05/22 22:27, Sebastian Andrzej Siewior wrote:
>> @@ -136,8 +123,9 @@ static struct kcov_remote_area *kcov_remote_area_get(unsigned int size)
>>  {
>>  	struct kcov_remote_area *area;
>>  	struct list_head *pos;
>> +	struct list_head *list = &kcov_remote_areas[size == CONFIG_KCOV_IRQ_AREA_SIZE];
> 
> This gets probably the job done but the two lists and check condition
> looks a bit hackish. The size is not really important, it is user-sized
> buffer vs softirq-buffer. Having an explicit might not be bad.

We can add a bool argument if we want.

> 
> Are the sizes somehow important? Any reason why there should not be a
> "sane" lower size and then fallback to the user-buffer if it runs out of
> softirqs buffers?

This question should be answered by KCOV users.

> 
> The user may request multiple sized buffers via KCOV_REMOTE_ENABLE, are
> they cleaned up? The question would could the softirq use the user sized
> buffers if any.

kcov_remote_stop() does not clean up (i.e. caches for future reuse).
But /sys/kernel/debug/kcov is protected as root:root 0600 file.



Dmitry, Sebastian, and Alexander, can we start testing this version?
Re: [PATCH v4] kcov: move kcov_remote_data to task_struct and remove local_lock
Posted by Sebastian Andrzej Siewior 3 weeks ago
On 2026-05-22 20:10:47 [+0900], Tetsuo Handa wrote:
> --- a/kernel/kcov.c
> +++ b/kernel/kcov.c
> @@ -86,21 +86,8 @@ struct kcov_remote {
>  
>  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),
> +static struct list_head kcov_remote_areas[2] = {
> +	LIST_HEAD_INIT(kcov_remote_areas[0]), LIST_HEAD_INIT(kcov_remote_areas[1])
>  };
>  
>  /* Must be called with kcov_remote_lock locked. */
> @@ -136,8 +123,9 @@ static struct kcov_remote_area *kcov_remote_area_get(unsigned int size)
>  {
>  	struct kcov_remote_area *area;
>  	struct list_head *pos;
> +	struct list_head *list = &kcov_remote_areas[size == CONFIG_KCOV_IRQ_AREA_SIZE];

This gets probably the job done but the two lists and check condition
looks a bit hackish. The size is not really important, it is user-sized
buffer vs softirq-buffer. Having an explicit might not be bad.

> @@ -1119,14 +1083,19 @@ static void __init selftest(void)
>  
>  static int __init kcov_init(void)
>  {
> -	int cpu;
> +	int cpu = num_possible_cpus();
>  
> -	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

Maybe IS_ENABLED(). This assumes that we have twice as many tasks in
softirq context as we have CPUs. Is probably good enough.

Are the sizes somehow important? Any reason why there should not be a
"sane" lower size and then fallback to the user-buffer if it runs out of
softirqs buffers?

The user may request multiple sized buffers via KCOV_REMOTE_ENABLE, are
they cleaned up? The question would could the softirq use the user sized
buffers if any.

> +	while (cpu--) {
> +		void *area = vmalloc(CONFIG_KCOV_IRQ_AREA_SIZE * sizeof(unsigned long));
> +		unsigned long flags;
> +
> +		spin_lock_irqsave(&kcov_remote_lock, flags);
> +		kcov_remote_area_put(area, CONFIG_KCOV_IRQ_AREA_SIZE);
> +		spin_unlock_irqrestore(&kcov_remote_lock, flags);
>  	}
>  
>  	/*

Sebastian
Re: [PATCH v3] kcov: move kcov_remote_data to task_struct for RT and remove local_lock
Posted by Sebastian Andrzej Siewior 3 weeks, 2 days ago
On 2026-05-09 17:09:44 [+0900], Tetsuo Handa wrote:
> In CONFIG_PREEMPT_RT=y kernels, softirqs are executed in a per-CPU
> ksoftirqd thread or in the context of the task that raised the softirq.
> This means in_task() can return true even while serving softirqs. This
> behavior causes KCOV to incorrectly identify the context, leading to state
> corruption and various kcov-related warnings reported by syzbot.

I need to digest the remaining email but in_task() should not return on
PREEMPT_RT if a softirq is served. Regardless if it happens in ksoftirqd
or in the context of the task raised via local_bh_enable().

I will digest this later.

Sebastian
Re: [PATCH v3] kcov: move kcov_remote_data to task_struct for RT and remove local_lock
Posted by Dmitry Vyukov 4 weeks ago
On Sat, 9 May 2026 at 10:09, Tetsuo Handa
<penguin-kernel@i-love.sakura.ne.jp> wrote:
>
> In CONFIG_PREEMPT_RT=y kernels, softirqs are executed in a per-CPU
> ksoftirqd thread or in the context of the task that raised the softirq.
> This means in_task() can return true even while serving softirqs. This
> behavior causes KCOV to incorrectly identify the context, leading to state
> corruption and various kcov-related warnings reported by syzbot.
>
> Furthermore, commit d5d2c51f1e5f ("kcov: replace local_irq_save() with a
> local_lock_t") introduced local_lock to protect per-CPU kcov_percpu_data.
> However, the need for this physical CPU-level locking is questionable
> because:
>
> 1. kcov_remote_start/stop() already bail out early if called from
>    in_hardirq() or in_nmi().
> 2. The core tracing function check_kcov_mode() also skips hardirq/NMI
>    contexts, meaning no KCOV state is accessed during hardirqs even if
>    they interrupt a KCOV-enabled task/softirq.
> 3. In non-RT kernels, softirqs do not nest, so no concurrent access to
>    per-CPU data occurs between softirqs.
> 4. In RT kernels, while softirqs can be preempted, this patch moves the
>    KCOV state from per-CPU variables to task_struct (per-task), eliminating
>    the contention on shared per-CPU resources.
>
> By moving kcov_remote_data to task_struct for RT kernels and replacing
> the in_task() check with !in_serving_softirq(), we ensure consistent
> context detection. Since the data is now isolated per-task and not accessed
> by hardirqs, the local_lock (and the original local_irq_save) is no longer
> necessary and is removed to reduce overhead.
>
> Changes:
>
> * Move remote coverage state from kcov_percpu_data to task_struct under
>   CONFIG_PREEMPT_RT.
> * Replace in_task() with !in_serving_softirq() in kcov_remote_start/stop()
>   for accurate context tracking.
> * Remove local_lock and IRQ disabling from kcov_remote_start/stop() as the
>   state is now task-local and hardirqs are already excluded.
> * Ensure CONFIG_PREEMPT_RT=y uses kcov_remote_area_get() (the
>   vmalloc-backed pool) instead of the single per-CPU irq_area.
>
> Analyzed-by: AI Mode in Google Search (no mail address)
> Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> Fixes: 5ff3b30ab57d ("kcov: collect coverage from interrupts")
> ---
> Only compile tested. I don't have environment to measure how not

+Aleksandr Nogikh

I don't see this patch on syzbot CI:
https://ci.syzbot.org/?name=kcov%3A+move+kcov_remote_data+to+task_struct+for+RT+and+remove+local_lock

How can we make it at least tested on syzbot CI? Otherwise it risks
breaking syzbot when merged.

> preallocating CONFIG_KCOV_IRQ_AREA_SIZE bytes of buffers and
> add to the global pool in kcov_init() impacts reliability / performance.
> Please be sure to test this patch using CONFIG_PREEMPT_RT=y and
> CONFIG_PREEMPT_RT=n kernels in local syzkaller environment before
> sending upstream.
>
> This patch is expected to address various KCOV related reports.
> But I don't add Closes: lines because we can't tell whether this patch
> alone is sufficient for marking as Closes:. We will find the answer
> some time after being sent to upstream.
>
>   https://syzkaller.appspot.com/bug?extid=90984d3713722683112e
>   https://syzkaller.appspot.com/bug?extid=47cf95ca1f9dcca872c8
>   https://syzkaller.appspot.com/bug?extid=8a173e13208949931dc7
>   https://syzkaller.appspot.com/bug?extid=3f51ad7ac3ae57a6fdcc
>   https://syzkaller.appspot.com/bug?extid=e6686317bd9fe911591a
>
>  include/linux/sched.h | 10 +++++
>  kernel/kcov.c         | 94 +++++++++++++++++++++++++------------------
>  lib/Kconfig.debug     |  1 +
>  3 files changed, 65 insertions(+), 40 deletions(-)
>
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index 368c7b4d7cb5..2c963f4271d6 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -1522,6 +1522,16 @@ struct task_struct {
>
>         /* Collect coverage from softirq context: */
>         unsigned int                    kcov_softirq;
> +
> +#ifdef CONFIG_PREEMPT_RT

Is it possible to unify the logic between RT and non-RT kernels (not
have any ifdef's)?
The logic is already rather complicated, having 2 work modes, and
limited testing will be problematic in the future.


> +       /* 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
> +
>  #endif
>
>  #ifdef CONFIG_MEMCG_V1
> diff --git a/kernel/kcov.c b/kernel/kcov.c
> index 0b369e88c7c9..965c11a75b36 100644
> --- a/kernel/kcov.c
> +++ b/kernel/kcov.c
> @@ -88,9 +88,9 @@ 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);
>
> +#ifndef CONFIG_PREEMPT_RT
>  struct kcov_percpu_data {
>         void                    *irq_area;
> -       local_lock_t            lock;
>
>         unsigned int            saved_mode;
>         unsigned int            saved_size;
> @@ -100,8 +100,8 @@ struct kcov_percpu_data {
>  };
>
>  static DEFINE_PER_CPU(struct kcov_percpu_data, kcov_percpu_data) = {
> -       .lock = INIT_LOCAL_LOCK(lock),
>  };
> +#endif
>
>  /* Must be called with kcov_remote_lock locked. */
>  static struct kcov_remote *kcov_remote_find(u64 handle)
> @@ -823,8 +823,38 @@ static inline bool kcov_mode_enabled(unsigned int mode)
>         return (mode & ~KCOV_IN_CTXSW) != KCOV_MODE_DISABLED;
>  }
>
> +#ifdef CONFIG_PREEMPT_RT
> +static void kcov_remote_softirq_start(struct task_struct *t)
> +{
> +       unsigned int mode;
> +
> +       mode = READ_ONCE(t->kcov_mode);
> +       barrier();
> +       if (kcov_mode_enabled(mode)) {
> +               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)
> +{
> +       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;
> +       }
> +}
> +#else
>  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;
> @@ -842,7 +872,6 @@ static void kcov_remote_softirq_start(struct task_struct *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);
>
> @@ -857,6 +886,7 @@ static void kcov_remote_softirq_stop(struct task_struct *t)
>                 data->saved_kcov = NULL;
>         }
>  }
> +#endif
>
>  void kcov_remote_start(u64 handle)
>  {
> @@ -867,43 +897,35 @@ void kcov_remote_start(u64 handle)
>         void *area;
>         unsigned int size;
>         int sequence;
> -       unsigned long flags;
>
> -       if (WARN_ON(!kcov_check_handle(handle, true, true, true)))
> +       /* Don't use in_task() in order to allow consistent checks in RT kernels. */
> +       if (in_hardirq() || in_nmi())
>                 return;
> -       if (!in_task() && !in_softirq_really())
> +       if (WARN_ON(!kcov_check_handle(handle, true, true, true)))
>                 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);
> +       if (WARN_ON(!in_serving_softirq() && kcov_mode_enabled(READ_ONCE(t->kcov_mode))))
>                 return;
> -       }
>         /*
>          * Check that kcov_remote_start() is not called twice in softirqs.
>          * Note, that kcov_remote_start() can be called from a softirq that
>          * 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);
> +       if (WARN_ON(in_serving_softirq() && t->kcov_softirq))
>                 return;
> -       }
>
>         spin_lock(&kcov_remote_lock);
>         remote = kcov_remote_find(handle);
>         if (!remote) {
>                 spin_unlock(&kcov_remote_lock);
> -               local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
>                 return;
>         }
>         kcov_debug("handle = %llx, context: %s\n", handle,
> -                       in_task() ? "task" : "softirq");
> +                  !in_serving_softirq() ? "task" : "softirq");
>         kcov = remote->kcov;
>         /* Put in kcov_remote_stop(). */
>         kcov_get(kcov);
> @@ -915,6 +937,10 @@ void kcov_remote_start(u64 handle)
>          */
>         mode = context_unsafe(kcov->mode);
>         sequence = kcov->sequence;
> +#ifdef CONFIG_PREEMPT_RT
> +       size = kcov->remote_size;
> +       area = kcov_remote_area_get(size);
> +#else
>         if (in_task()) {
>                 size = kcov->remote_size;
>                 area = kcov_remote_area_get(size);
> @@ -922,17 +948,16 @@ void kcov_remote_start(u64 handle)
>                 size = CONFIG_KCOV_IRQ_AREA_SIZE;
>                 area = this_cpu_ptr(&kcov_percpu_data)->irq_area;
>         }
> +#endif
>         spin_unlock(&kcov_remote_lock);
>
> -       /* Can only happen when in_task(). */
> +       /* Can only happen when CONFIG_PREEMPT_RT=y or in_task(). */
>         if (!area) {
> -               local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
>                 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 +968,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);
>
> @@ -1022,32 +1044,24 @@ void kcov_remote_stop(void)
>         void *area;
>         unsigned int size;
>         int sequence;
> -       unsigned long flags;
>
> -       if (!in_task() && !in_softirq_really())
> +       /* Don't use in_task() in order to allow consistent checks in RT kernels. */
> +       if (in_hardirq() || in_nmi())
>                 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);
> +       if (!kcov_mode_enabled(mode))
>                 return;
> -       }
>         /*
>          * When in softirq, check if the corresponding kcov_remote_start()
>          * actually found the remote handle and started collecting coverage.
>          */
> -       if (in_serving_softirq() && !t->kcov_softirq) {
> -               local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
> +       if (in_serving_softirq() && !t->kcov_softirq)
>                 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);
> +       if (WARN_ON(!in_serving_softirq() && t->kcov_softirq))
>                 return;
> -       }
>
>         kcov = t->kcov;
>         area = t->kcov_area;
> @@ -1069,14 +1083,12 @@ void kcov_remote_stop(void)
>                 kcov_move_area(kcov->mode, kcov->area, kcov->size, area);
>         spin_unlock(&kcov->lock);
>
> -       if (in_task()) {
> +       if (IS_ENABLED(CONFIG_PREEMPT_RT) || 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);
> -
>         /* Get in kcov_remote_start(). */
>         kcov_put(kcov);
>  }
> @@ -1119,6 +1131,7 @@ static void __init selftest(void)
>
>  static int __init kcov_init(void)
>  {
> +#ifndef CONFIG_PREEMPT_RT
>         int cpu;
>
>         for_each_possible_cpu(cpu) {
> @@ -1128,6 +1141,7 @@ static int __init kcov_init(void)
>                         return -ENOMEM;
>                 per_cpu_ptr(&kcov_percpu_data, cpu)->irq_area = area;
>         }
> +#endif
>
>         /*
>          * The kcov debugfs file won't ever get removed and thus,
> diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> index 13f3297aa823..493be2c73c9d 100644
> --- a/lib/Kconfig.debug
> +++ b/lib/Kconfig.debug
> @@ -2247,6 +2247,7 @@ config KCOV_INSTRUMENT_ALL
>  config KCOV_IRQ_AREA_SIZE
>         hex "Size of interrupt coverage collection area in words"
>         depends on KCOV
> +       depends on !PREEMPT_RT
>         default 0x40000
>         help
>           KCOV uses preallocated per-cpu areas to collect coverage from
> --
> 2.47.3
>
>
Re: [PATCH v3] kcov: move kcov_remote_data to task_struct for RT and remove local_lock
Posted by Tetsuo Handa 3 weeks, 2 days ago
On 2026/05/15 16:10, Dmitry Vyukov wrote:
>> Only compile tested. I don't have environment to measure how not
> 
> +Aleksandr Nogikh
> 
> I don't see this patch on syzbot CI:
> https://ci.syzbot.org/?name=kcov%3A+move+kcov_remote_data+to+task_struct+for+RT+and+remove+local_lock

I didn't include syzbot related mail address.
What is needed to make this patch detected by syzbot CI?

> 
> How can we make it at least tested on syzbot CI? Otherwise it risks
> breaking syzbot when merged.

Someone who can run syzkaller will be able to test this patch.
If nobody can try, I can try sending this patch to only linux-next tree via my tree.

>> +#ifdef CONFIG_PREEMPT_RT
> 
> Is it possible to unify the logic between RT and non-RT kernels (not
> have any ifdef's)?
> The logic is already rather complicated, having 2 work modes, and
> limited testing will be problematic in the future.

Always using per task_struct temporary storage will be possible if we don't mind
bloating sizeof(struct task_struct). But we can't unify buffer allocation part
because allocating large buffer using kmalloc(GFP_ATOMIC) for CONFIG_PREEMPT_RT=n
is unreliable. Preallocating global non-percpu buffers for CONFIG_PREEMPT_RT=y
would be possible, but we don't know how many we should allocate due to
possibility of preemption in CONFIG_PREEMPT_RT=y.

>> @@ -915,6 +937,10 @@ void kcov_remote_start(u64 handle)
>>          */
>>         mode = context_unsafe(kcov->mode);
>>         sequence = kcov->sequence;
>> +#ifdef CONFIG_PREEMPT_RT
>> +       size = kcov->remote_size;
>> +       area = kcov_remote_area_get(size);
>> +#else
>>         if (in_task()) {
>>                 size = kcov->remote_size;
>>                 area = kcov_remote_area_get(size);
>> @@ -922,17 +948,16 @@ void kcov_remote_start(u64 handle)
>>                 size = CONFIG_KCOV_IRQ_AREA_SIZE;
>>                 area = this_cpu_ptr(&kcov_percpu_data)->irq_area;
>>         }
>> +#endif
>>         spin_unlock(&kcov_remote_lock);
>>
>> -       /* Can only happen when in_task(). */
>> +       /* Can only happen when CONFIG_PREEMPT_RT=y or in_task(). */
>>         if (!area) {
>> -               local_unlock_irqrestore(&kcov_percpu_data.lock, flags);
>>                 area = vmalloc(size * sizeof(unsigned long));
>>                 if (!area) {
>>                         kcov_put(kcov);
>>                         return;
>>                 }
>> -               local_lock_irqsave(&kcov_percpu_data.lock, flags);
>>         }
>>
>>         /* Reset coverage size. */