Skip to content

Commit efb01a8

Browse files
committed
helpers: workqueue: Fix errors seen for newer (v6.4 and later) kernels.
Signed-off-by: Imran Khan <[email protected]>
1 parent f39b328 commit efb01a8

File tree

2 files changed

+66
-13
lines changed

2 files changed

+66
-13
lines changed

drgn/helpers/linux/workqueue.py

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111

1212
from typing import Iterator, Optional, Union
1313

14-
from drgn import NULL, Object, Program, cast
14+
from drgn import NULL, IntegerLike, Object, Program, cast
1515
from drgn.helpers.common.format import escape_ascii_string
1616
from drgn.helpers.linux.idr import idr_find, idr_for_each
1717
from drgn.helpers.linux.list import (
1818
hlist_for_each_entry,
1919
list_empty,
2020
list_for_each_entry,
2121
)
22-
from drgn.helpers.linux.percpu import per_cpu
22+
from drgn.helpers.linux.percpu import per_cpu, per_cpu_ptr
2323
from drgn.helpers.linux.pid import find_task
2424

2525
__all__ = (
@@ -43,19 +43,71 @@
4343
"show_one_worker_pool",
4444
"is_task_a_worker",
4545
"find_worker_executing_work",
46+
"workqueue_get_pwq",
4647
)
4748

4849

4950
_PF_WQ_WORKER = 0x00000020
5051

5152

53+
def _work_offq_pool_none(prog: Program) -> IntegerLike:
54+
# Linux kernel commit afa4bb778e48 ("workqueue: clean up WORK_* constant
55+
# types, clarify masking") (in v6.4) changed WORK_OFFQ_POOL_NONE from
56+
# constants of type enum to constants of type unsigned long.
57+
try:
58+
val = prog["WORK_OFFQ_POOL_NONE"].value_()
59+
except KeyError:
60+
val = (
61+
Object(prog, "unsigned long", 1).value_() << prog["WORK_OFFQ_POOL_BITS"]
62+
) - 1
63+
return val
64+
65+
66+
def _work_struct_wq_data_mask(prog: Program) -> IntegerLike:
67+
# Linux kernel commit afa4bb778e48 ("workqueue: clean up WORK_* constant
68+
# types, clarify masking") (in v6.4) changed WORK_STRUCT_WQ_DATA_MASK from
69+
# constants of type enum to constants of type unsigned long.
70+
try:
71+
val = prog["WORK_STRUCT_WQ_DATA_MASK"].value_()
72+
except KeyError:
73+
val = ~((Object(prog, "unsigned long", 1) << prog["WORK_STRUCT_FLAG_BITS"]) - 1)
74+
return val
75+
76+
5277
def _print_work(work: Object) -> None:
5378
prog = work.prog_
5479
print(
5580
f" ({work.type_.type_name()})0x{work.value_():x}: func: {prog.symbol(work.func.value_()).name}"
5681
)
5782

5883

84+
def workqueue_get_pwq(workqueue: Object, cpu: int) -> Object:
85+
"""
86+
Find pool_workqueue of a bound workqueue for a given CPU.
87+
88+
:param workqueue: ``struct workqueue_struct *``
89+
:return: ``struct pool_workqueue *``.
90+
"""
91+
# At first Linux kernel commit ee1ceef72754 ("workqueue: Rename wq->cpu_pwqs to
92+
# wq->cpu_pwq") (in v6.6) renamed cpu_pwqs to cpu_pwq and then Linux kernel commit
93+
# 687a9aa56f81("workqueue: Make per-cpu pool_workqueues allocated and released
94+
# like unbound ones") (in v6.6) changed cpu_pwq to double pointer.
95+
# As both of the changes were made in v6.6, there are no kernel versions
96+
# with wq->cpu_pwq as a pointer. Still I have mentioned both the changes so that
97+
# we can track both name change and type change of this member.
98+
try:
99+
pwq = per_cpu_ptr(workqueue.cpu_pwqs, cpu)
100+
except AttributeError:
101+
prog = workqueue.prog_
102+
pwq = Object(
103+
prog,
104+
"struct pool_workqueue",
105+
address=prog.read_word(per_cpu_ptr(workqueue.cpu_pwq, cpu)),
106+
).address_of_()
107+
108+
return pwq
109+
110+
59111
def for_each_workqueue(prog: Program) -> Iterator[Object]:
60112
"""
61113
Iterate over all workqueues in the system.
@@ -210,9 +262,7 @@ def get_work_pwq(work: Object) -> Object:
210262
prog = work.prog_
211263
data = cast("unsigned long", work.data.counter.read_())
212264
if data & prog["WORK_STRUCT_PWQ"].value_():
213-
return cast(
214-
"struct pool_workqueue *", data & prog["WORK_STRUCT_WQ_DATA_MASK"].value_()
215-
)
265+
return cast("struct pool_workqueue *", data & _work_struct_wq_data_mask(prog))
216266
else:
217267
return NULL(work.prog_, "struct pool_workqueue *")
218268

@@ -230,12 +280,12 @@ def get_work_pool(work: Object) -> Object:
230280
data = cast("unsigned long", work.data.counter.read_())
231281

232282
if data & prog["WORK_STRUCT_PWQ"].value_():
233-
pwq = data & prog["WORK_STRUCT_WQ_DATA_MASK"].value_()
283+
pwq = data & _work_struct_wq_data_mask(prog)
234284
pool = Object(prog, "struct pool_workqueue", address=pwq).pool
235285
else:
236286
pool_id = data >> prog["WORK_OFFQ_POOL_SHIFT"].value_()
237287

238-
if pool_id == prog["WORK_OFFQ_POOL_NONE"].value_():
288+
if pool_id == _work_offq_pool_none(prog):
239289
return NULL(work.prog_, "struct worker_pool *")
240290

241291
pool = idr_find(prog["worker_pool_idr"].address_of_(), pool_id)

tests/linux_kernel/helpers/test_workqueue.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
for_each_workqueue,
2020
get_work_pool,
2121
get_work_pwq,
22+
workqueue_get_pwq,
2223
)
2324
from tests.linux_kernel import LinuxKernelTestCase, skip_unless_have_test_kmod
2425

@@ -114,11 +115,12 @@ def test_for_each_pwq(self):
114115
# Since "drgn_test_wq" is a bound workqueue, list pwqs
115116
# should contain only per-cpu pwqs i.e cpu_pwqs
116117
pwqs = [pwq.value_() for pwq in for_each_pwq(self.test_wq)]
117-
cpu_pwqs = [
118-
per_cpu_ptr(self.test_wq.cpu_pwqs, cpu).value_()
118+
cpu_pwqs_attr = "cpu_pwqs" if hasattr(self.test_wq, "cpu_pwqs") else "cpu_pwq"
119+
cpu_pwqs_list = [
120+
per_cpu_ptr(getattr(self.test_wq, cpu_pwqs_attr), cpu).value_()
119121
for cpu in for_each_online_cpu(self.prog)
120122
]
121-
self.assertEqual(pwqs.sort(), cpu_pwqs.sort())
123+
self.assertEqual(pwqs.sort(), cpu_pwqs_list.sort())
122124

123125
@skip_unless_have_test_kmod
124126
def test_for_each_pending_work(self):
@@ -142,16 +144,17 @@ def test_for_each_pending_work_in_pool(self):
142144

143145
@skip_unless_have_test_kmod
144146
def test_for_each_pending_work_of_pwq(self):
145-
cpu_pwqs_0 = per_cpu_ptr(self.test_wq.cpu_pwqs, 0)
147+
cpu_pwqs_0 = workqueue_get_pwq(self.test_wq, 0)
146148
all_works_of_pwq = [
147149
work.value_() for work in for_each_pending_work_of_pwq(cpu_pwqs_0)
148150
]
149151
self.assertEqual(all_works_of_pwq, self.test_works)
150152

151153
@skip_unless_have_test_kmod
152154
def test_get_work_pwq(self):
153-
cpu_pwqs_0 = per_cpu_ptr(self.test_wq.cpu_pwqs, 0)
154-
cpu_pwqs_1 = per_cpu_ptr(self.test_wq.cpu_pwqs, 1)
155+
cpu_pwqs_0 = workqueue_get_pwq(self.test_wq, 0)
156+
cpu_pwqs_1 = workqueue_get_pwq(self.test_wq, 1)
157+
155158
pwq = get_work_pwq(self.test_work_0)
156159
self.assertEqual(pwq, cpu_pwqs_0)
157160
self.assertNotEqual(pwq, cpu_pwqs_1)

0 commit comments

Comments
 (0)