* [PATCH v3 12/24] dyndbg: drop NUM_TYPE_ARGS
From: Jim Cromie @ 2026-06-01 12:04 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Arnd Bergmann, Jason Baron,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Andrew Morton, Shuah Khan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: linux-doc, linux-kernel, linux-arch, linux-modules,
linux-kselftest, dri-devel, Jim Cromie, Louis Chauvet
In-Reply-To: <20260601-dd-maint-2-v3-0-4a15b241bd3c@gmail.com>
ARRAY_SIZE works here, since array decl is complete.
no functional change
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
Reviewed-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
v2: include linux/array_size.h, correct commit subject
review after sob
---
include/linux/dynamic_debug.h | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/include/linux/dynamic_debug.h b/include/linux/dynamic_debug.h
index 78c22c6d2312..867e3978675f 100644
--- a/include/linux/dynamic_debug.h
+++ b/include/linux/dynamic_debug.h
@@ -121,11 +121,9 @@ struct ddebug_class_param {
.mod_name = KBUILD_MODNAME, \
.base = _base, \
.map_type = _maptype, \
- .length = NUM_TYPE_ARGS(char*, __VA_ARGS__), \
+ .length = ARRAY_SIZE(_var##_classnames), \
.class_names = _var##_classnames, \
}
-#define NUM_TYPE_ARGS(eltype, ...) \
- (sizeof((eltype[]) {__VA_ARGS__}) / sizeof(eltype))
extern __printf(2, 3)
void __dynamic_pr_debug(struct _ddebug *descriptor, const char *fmt, ...);
--
2.54.0
^ permalink raw reply related
* [PATCH v3 11/24] dyndbg-API: remove DD_CLASS_TYPE_(DISJOINT|LEVEL)_NAMES and code
From: Jim Cromie @ 2026-06-01 12:04 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Arnd Bergmann, Jason Baron,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Andrew Morton, Shuah Khan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: linux-doc, linux-kernel, linux-arch, linux-modules,
linux-kselftest, dri-devel, Jim Cromie, Louis Chauvet
In-Reply-To: <20260601-dd-maint-2-v3-0-4a15b241bd3c@gmail.com>
Remove the DD_CLASS_TYPE_*_NAMES classmap types and code.
These 2 classmap types accept class names at the PARAM interface, for
example:
echo +DRM_UT_CORE,-DRM_UT_KMS > /sys/module/drm/parameters/debug_names
The code works, but its only used by test-dynamic-debug, and wasn't
asked for by anyone else, so reduce LOC & test-surface; simplify things.
Also rename enum class_map_type to enum ddebug_class_map_type.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
Reviewed-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
v3:
fix name of enum in kdoc
also change name of struct (to future name)
v2:
move RvB after SoB
respect const instr in param_set_dyndbg_module_classes, return -EINVAL on classtype err.
---
include/linux/dynamic_debug.h | 27 ++++--------
lib/dynamic_debug.c | 99 +++----------------------------------------
lib/test_dynamic_debug.c | 26 ------------
3 files changed, 14 insertions(+), 138 deletions(-)
diff --git a/include/linux/dynamic_debug.h b/include/linux/dynamic_debug.h
index a10adac8e8f0..78c22c6d2312 100644
--- a/include/linux/dynamic_debug.h
+++ b/include/linux/dynamic_debug.h
@@ -59,27 +59,16 @@ struct _ddebug {
#endif
} __attribute__((aligned(8)));
-enum class_map_type {
+enum ddebug_class_map_type {
DD_CLASS_TYPE_DISJOINT_BITS,
/**
- * DD_CLASS_TYPE_DISJOINT_BITS: classes are independent, one per bit.
- * expecting hex input. Built for drm.debug, basis for other types.
+ * DD_CLASS_TYPE_DISJOINT_BITS: classes are independent, mapped to bits[0..N].
+ * Expects hex input. Built for drm.debug, basis for other types.
*/
DD_CLASS_TYPE_LEVEL_NUM,
/**
- * DD_CLASS_TYPE_LEVEL_NUM: input is numeric level, 0-N.
- * N turns on just bits N-1 .. 0, so N=0 turns all bits off.
- */
- DD_CLASS_TYPE_DISJOINT_NAMES,
- /**
- * DD_CLASS_TYPE_DISJOINT_NAMES: input is a CSV of [+-]CLASS_NAMES,
- * classes are independent, like _DISJOINT_BITS.
- */
- DD_CLASS_TYPE_LEVEL_NAMES,
- /**
- * DD_CLASS_TYPE_LEVEL_NAMES: input is a CSV of [+-]CLASS_NAMES,
- * intended for names like: INFO,DEBUG,TRACE, with a module prefix
- * avoid EMERG,ALERT,CRIT,ERR,WARNING: they're not debug
+ * DD_CLASS_TYPE_LEVEL_NUM: input is numeric level, 0..N.
+ * Input N turns on bits 0..N-1
*/
};
@@ -90,7 +79,7 @@ struct ddebug_class_map {
const char **class_names;
const int length;
const int base; /* index of 1st .class_id, allows split/shared space */
- enum class_map_type map_type;
+ enum ddebug_class_map_type map_type;
};
/* encapsulate linker provided built-in (or module) dyndbg data */
@@ -119,8 +108,8 @@ struct ddebug_class_param {
/**
* DECLARE_DYNDBG_CLASSMAP - declare classnames known by a module
- * @_var: a struct ddebug_class_map, passed to module_param_cb
- * @_type: enum class_map_type, chooses bits/verbose, numeric/symbolic
+ * @_var: a struct _ddebug_class_map, passed to module_param_cb
+ * @_maptype: enum ddebug_class_map_type, chooses bits/verbose
* @_base: offset of 1st class-name. splits .class_id space
* @classes: class-names used to control class'd prdbgs
*/
diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c
index a9caf84ddb22..0377d9f8dcd1 100644
--- a/lib/dynamic_debug.c
+++ b/lib/dynamic_debug.c
@@ -646,76 +646,6 @@ static int ddebug_apply_class_bitmap(const struct ddebug_class_param *dcp,
#define CLASSMAP_BITMASK(width) ((1UL << (width)) - 1)
-/* accept comma-separated-list of [+-] classnames */
-static int param_set_dyndbg_classnames(const char *instr, const struct kernel_param *kp)
-{
- const struct ddebug_class_param *dcp = kp->arg;
- const struct ddebug_class_map *map = dcp->map;
- unsigned long curr_bits, old_bits;
- char *cl_str, *p, *tmp;
- int cls_id, totct = 0;
- bool wanted;
-
- cl_str = tmp = kstrdup_and_replace(instr, '\n', '\0', GFP_KERNEL);
- if (!tmp)
- return -ENOMEM;
-
- /* start with previously set state-bits, then modify */
- curr_bits = old_bits = *dcp->bits;
- vpr_info("\"%s\" > %s:0x%lx\n", cl_str, KP_NAME(kp), curr_bits);
-
- for (; cl_str; cl_str = p) {
- p = strchr(cl_str, ',');
- if (p)
- *p++ = '\0';
-
- if (*cl_str == '-') {
- wanted = false;
- cl_str++;
- } else {
- wanted = true;
- if (*cl_str == '+')
- cl_str++;
- }
- cls_id = match_string(map->class_names, map->length, cl_str);
- if (cls_id < 0) {
- pr_err("%s unknown to %s\n", cl_str, KP_NAME(kp));
- continue;
- }
-
- /* have one or more valid class_ids of one *_NAMES type */
- switch (map->map_type) {
- case DD_CLASS_TYPE_DISJOINT_NAMES:
- /* the +/- pertains to a single bit */
- if (test_bit(cls_id, &curr_bits) == wanted) {
- v3pr_info("no change on %s\n", cl_str);
- continue;
- }
- curr_bits ^= BIT(cls_id);
- totct += ddebug_apply_class_bitmap(dcp, &curr_bits, dcp->bits);
- *dcp->bits = curr_bits;
- v2pr_info("%s: changed bit %d:%s\n", KP_NAME(kp), cls_id,
- map->class_names[cls_id]);
- break;
- case DD_CLASS_TYPE_LEVEL_NAMES:
- /* cls_id = N in 0..max. wanted +/- determines N or N-1 */
- old_bits = CLASSMAP_BITMASK(*dcp->lvl);
- curr_bits = CLASSMAP_BITMASK(cls_id + (wanted ? 1 : 0 ));
-
- totct += ddebug_apply_class_bitmap(dcp, &curr_bits, &old_bits);
- *dcp->lvl = (cls_id + (wanted ? 1 : 0));
- v2pr_info("%s: changed bit-%d: \"%s\" %lx->%lx\n", KP_NAME(kp), cls_id,
- map->class_names[cls_id], old_bits, curr_bits);
- break;
- default:
- pr_err("illegal map-type value %d\n", map->map_type);
- }
- }
- kfree(tmp);
- vpr_info("total matches: %d\n", totct);
- return 0;
-}
-
/**
* param_set_dyndbg_classes - class FOO >control
* @instr: string echo>d to sysfs, input depends on map_type
@@ -734,28 +664,14 @@ int param_set_dyndbg_classes(const char *instr, const struct kernel_param *kp)
unsigned long inrep, new_bits, old_bits;
int rc, totct = 0;
- switch (map->map_type) {
-
- case DD_CLASS_TYPE_DISJOINT_NAMES:
- case DD_CLASS_TYPE_LEVEL_NAMES:
- /* handle [+-]classnames list separately, we are done here */
- return param_set_dyndbg_classnames(instr, kp);
-
- case DD_CLASS_TYPE_DISJOINT_BITS:
- case DD_CLASS_TYPE_LEVEL_NUM:
- /* numeric input, accept and fall-thru */
- rc = kstrtoul(instr, 0, &inrep);
- if (rc) {
- pr_err("expecting numeric input: %s > %s\n", instr, KP_NAME(kp));
- return -EINVAL;
- }
- break;
- default:
- pr_err("%s: bad map type: %d\n", KP_NAME(kp), map->map_type);
+ rc = kstrtoul(instr, 0, &inrep);
+ if (rc) {
+ int len = strcspn(instr, "\n");
+ pr_err("expecting numeric input, not: %.*s > %s\n",
+ len, instr, KP_NAME(kp));
return -EINVAL;
}
- /* only _BITS,_NUM (numeric) map-types get here */
switch (map->map_type) {
case DD_CLASS_TYPE_DISJOINT_BITS:
/* expect bits. mask and warn if too many */
@@ -783,6 +699,7 @@ int param_set_dyndbg_classes(const char *instr, const struct kernel_param *kp)
break;
default:
pr_warn("%s: bad map type: %d\n", KP_NAME(kp), map->map_type);
+ return -EINVAL;
}
vpr_info("%s: total matches: %d\n", KP_NAME(kp), totct);
return 0;
@@ -804,12 +721,8 @@ int param_get_dyndbg_classes(char *buffer, const struct kernel_param *kp)
const struct ddebug_class_map *map = dcp->map;
switch (map->map_type) {
-
- case DD_CLASS_TYPE_DISJOINT_NAMES:
case DD_CLASS_TYPE_DISJOINT_BITS:
return scnprintf(buffer, PAGE_SIZE, "0x%lx\n", *dcp->bits);
-
- case DD_CLASS_TYPE_LEVEL_NAMES:
case DD_CLASS_TYPE_LEVEL_NUM:
return scnprintf(buffer, PAGE_SIZE, "%d\n", *dcp->lvl);
default:
diff --git a/lib/test_dynamic_debug.c b/lib/test_dynamic_debug.c
index 77c2a669b6af..74d183ebf3e0 100644
--- a/lib/test_dynamic_debug.c
+++ b/lib/test_dynamic_debug.c
@@ -74,13 +74,6 @@ DECLARE_DYNDBG_CLASSMAP(map_disjoint_bits, DD_CLASS_TYPE_DISJOINT_BITS, 0,
DD_SYS_WRAP(disjoint_bits, p);
DD_SYS_WRAP(disjoint_bits, T);
-/* symbolic input, independent bits */
-enum cat_disjoint_names { LOW = 11, MID, HI };
-DECLARE_DYNDBG_CLASSMAP(map_disjoint_names, DD_CLASS_TYPE_DISJOINT_NAMES, 10,
- "LOW", "MID", "HI");
-DD_SYS_WRAP(disjoint_names, p);
-DD_SYS_WRAP(disjoint_names, T);
-
/* numeric verbosity, V2 > V1 related */
enum cat_level_num { V0 = 14, V1, V2, V3, V4, V5, V6, V7 };
DECLARE_DYNDBG_CLASSMAP(map_level_num, DD_CLASS_TYPE_LEVEL_NUM, 14,
@@ -88,13 +81,6 @@ DECLARE_DYNDBG_CLASSMAP(map_level_num, DD_CLASS_TYPE_LEVEL_NUM, 14,
DD_SYS_WRAP(level_num, p);
DD_SYS_WRAP(level_num, T);
-/* symbolic verbosity */
-enum cat_level_names { L0 = 22, L1, L2, L3, L4, L5, L6, L7 };
-DECLARE_DYNDBG_CLASSMAP(map_level_names, DD_CLASS_TYPE_LEVEL_NAMES, 22,
- "L0", "L1", "L2", "L3", "L4", "L5", "L6", "L7");
-DD_SYS_WRAP(level_names, p);
-DD_SYS_WRAP(level_names, T);
-
/* stand-in for all pr_debug etc */
#define prdbg(SYM) __pr_debug_cls(SYM, #SYM " msg\n")
@@ -102,10 +88,6 @@ static void do_cats(void)
{
pr_debug("doing categories\n");
- prdbg(LOW);
- prdbg(MID);
- prdbg(HI);
-
prdbg(D2_CORE);
prdbg(D2_DRIVER);
prdbg(D2_KMS);
@@ -129,14 +111,6 @@ static void do_levels(void)
prdbg(V5);
prdbg(V6);
prdbg(V7);
-
- prdbg(L1);
- prdbg(L2);
- prdbg(L3);
- prdbg(L4);
- prdbg(L5);
- prdbg(L6);
- prdbg(L7);
}
static void do_prints(void)
--
2.54.0
^ permalink raw reply related
* [PATCH v3 10/24] dyndbg: reword "class unknown," to "class:_UNKNOWN_"
From: Jim Cromie @ 2026-06-01 12:04 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Arnd Bergmann, Jason Baron,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Andrew Morton, Shuah Khan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: linux-doc, linux-kernel, linux-arch, linux-modules,
linux-kselftest, dri-devel, Jim Cromie, Louis Chauvet
In-Reply-To: <20260601-dd-maint-2-v3-0-4a15b241bd3c@gmail.com>
When a dyndbg classname is unknown to a kernel module, the callsite is
un-addressable via >control queries.
The control-file displays this condition as "class unknown,"
currently. That spelling is sub-optimal/too-generic, so change it to
"class:_UNKNOWN_" to loudly announce the erroneous situation, and to
make it uniquely greppable.
NB: while this might be seen as a user-visible change, this shouldn't
disqualify the change:
a- it reports classmap coding error condition, which should be
detected before review.
b- SHOUTING the error makes it uniquely greppable.
c- the classmap feature is marked BROKEN for its only current user.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
Reviewed-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
lib/dynamic_debug.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c
index 6b1e983cfedc..a9caf84ddb22 100644
--- a/lib/dynamic_debug.c
+++ b/lib/dynamic_debug.c
@@ -1166,7 +1166,7 @@ static int ddebug_proc_show(struct seq_file *m, void *p)
if (class)
seq_printf(m, " class:%s", class);
else
- seq_printf(m, " class unknown, _id:%d", dp->class_id);
+ seq_printf(m, " class:_UNKNOWN_ _id:%d", dp->class_id);
}
seq_putc(m, '\n');
--
2.54.0
^ permalink raw reply related
* [PATCH v3 09/24] dyndbg: add stub macro for DECLARE_DYNDBG_CLASSMAP
From: Jim Cromie @ 2026-06-01 12:04 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Arnd Bergmann, Jason Baron,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Andrew Morton, Shuah Khan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: linux-doc, linux-kernel, linux-arch, linux-modules,
linux-kselftest, dri-devel, Jim Cromie
In-Reply-To: <20260601-dd-maint-2-v3-0-4a15b241bd3c@gmail.com>
Add the stub macro for !DYNAMIC_DEBUG builds, after moving the
original macro-defn down under the big ifdef. Do it now so future
changes have a cleaner starting point.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
include/linux/dynamic_debug.h | 43 ++++++++++++++++++++++---------------------
1 file changed, 22 insertions(+), 21 deletions(-)
diff --git a/include/linux/dynamic_debug.h b/include/linux/dynamic_debug.h
index 05743900a116..a10adac8e8f0 100644
--- a/include/linux/dynamic_debug.h
+++ b/include/linux/dynamic_debug.h
@@ -93,27 +93,6 @@ struct ddebug_class_map {
enum class_map_type map_type;
};
-/**
- * DECLARE_DYNDBG_CLASSMAP - declare classnames known by a module
- * @_var: a struct ddebug_class_map, passed to module_param_cb
- * @_type: enum class_map_type, chooses bits/verbose, numeric/symbolic
- * @_base: offset of 1st class-name. splits .class_id space
- * @classes: class-names used to control class'd prdbgs
- */
-#define DECLARE_DYNDBG_CLASSMAP(_var, _maptype, _base, ...) \
- static const char *_var##_classnames[] = { __VA_ARGS__ }; \
- static struct ddebug_class_map __aligned(8) __used \
- __section("__dyndbg_classes") _var = { \
- .mod = THIS_MODULE, \
- .mod_name = KBUILD_MODNAME, \
- .base = _base, \
- .map_type = _maptype, \
- .length = NUM_TYPE_ARGS(char*, __VA_ARGS__), \
- .class_names = _var##_classnames, \
- }
-#define NUM_TYPE_ARGS(eltype, ...) \
- (sizeof((eltype[]){__VA_ARGS__}) / sizeof(eltype))
-
/* encapsulate linker provided built-in (or module) dyndbg data */
struct _ddebug_info {
struct _ddebug *descs;
@@ -138,6 +117,27 @@ struct ddebug_class_param {
#if defined(CONFIG_DYNAMIC_DEBUG) || \
(defined(CONFIG_DYNAMIC_DEBUG_CORE) && defined(DYNAMIC_DEBUG_MODULE))
+/**
+ * DECLARE_DYNDBG_CLASSMAP - declare classnames known by a module
+ * @_var: a struct ddebug_class_map, passed to module_param_cb
+ * @_type: enum class_map_type, chooses bits/verbose, numeric/symbolic
+ * @_base: offset of 1st class-name. splits .class_id space
+ * @classes: class-names used to control class'd prdbgs
+ */
+#define DECLARE_DYNDBG_CLASSMAP(_var, _maptype, _base, ...) \
+ static const char *_var##_classnames[] = { __VA_ARGS__ }; \
+ static struct ddebug_class_map __aligned(8) __used \
+ __section("__dyndbg_classes") _var = { \
+ .mod = THIS_MODULE, \
+ .mod_name = KBUILD_MODNAME, \
+ .base = _base, \
+ .map_type = _maptype, \
+ .length = NUM_TYPE_ARGS(char*, __VA_ARGS__), \
+ .class_names = _var##_classnames, \
+ }
+#define NUM_TYPE_ARGS(eltype, ...) \
+ (sizeof((eltype[]) {__VA_ARGS__}) / sizeof(eltype))
+
extern __printf(2, 3)
void __dynamic_pr_debug(struct _ddebug *descriptor, const char *fmt, ...);
@@ -314,6 +314,7 @@ void __dynamic_ibdev_dbg(struct _ddebug *descriptor,
#define DEFINE_DYNAMIC_DEBUG_METADATA(name, fmt)
#define DYNAMIC_DEBUG_BRANCH(descriptor) false
+#define DECLARE_DYNDBG_CLASSMAP(...)
#define dynamic_pr_debug(fmt, ...) \
no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
--
2.54.0
^ permalink raw reply related
* [PATCH v3 08/24] dyndbg: factor ddebug_match_desc out from ddebug_change
From: Jim Cromie @ 2026-06-01 12:04 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Arnd Bergmann, Jason Baron,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Andrew Morton, Shuah Khan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: linux-doc, linux-kernel, linux-arch, linux-modules,
linux-kselftest, dri-devel, Jim Cromie, Louis Chauvet
In-Reply-To: <20260601-dd-maint-2-v3-0-4a15b241bd3c@gmail.com>
ddebug_change() is a big (~100 lines) function with a nested for loop.
The outer loop walks the per-module ddebug_tables list, and does
module stuff: it filters on a query's "module FOO*" and "class BAR",
failures here skip the entire inner loop.
The inner loop (60 lines) scans a module's descriptors. It starts
with a long block of filters on function, line, format, and the
validated "BAR" class (or the legacy/_DPRINTK_CLASS_DFLT).
These filters "continue" past pr_debugs that don't match the query
criteria, before it falls through the code below that counts matches,
then adjusts the flags and static-keys. This is unnecessarily hard to
think about.
So move the per-descriptor filter-block into a boolean function:
ddebug_match_desc(desc), and change each "continue" to "return false".
This puts a clear interface in place, so any future changes are either
inside, outside, or across this interface.
also fix checkpatch complaints about spaces and braces.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
Reviewed-by: Louis Chauvet <louis.chauvet@bootlin.com>
---
lib/dynamic_debug.c | 83 ++++++++++++++++++++++++++++++-----------------------
1 file changed, 47 insertions(+), 36 deletions(-)
diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c
index 18a71a9108d3..6b1e983cfedc 100644
--- a/lib/dynamic_debug.c
+++ b/lib/dynamic_debug.c
@@ -172,6 +172,52 @@ static struct ddebug_class_map *ddebug_find_valid_class(struct ddebug_table cons
* callsites, normally the same as number of changes. If verbose,
* logs the changes. Takes ddebug_lock.
*/
+static bool ddebug_match_desc(const struct ddebug_query *query,
+ struct _ddebug *dp,
+ int valid_class)
+{
+ /* match site against query-class */
+ if (dp->class_id != valid_class)
+ return false;
+
+ /* match against the source filename */
+ if (query->filename &&
+ !match_wildcard(query->filename, dp->filename) &&
+ !match_wildcard(query->filename,
+ kbasename(dp->filename)) &&
+ !match_wildcard(query->filename,
+ trim_prefix(dp->filename)))
+ return false;
+
+ /* match against the function */
+ if (query->function &&
+ !match_wildcard(query->function, dp->function))
+ return false;
+
+ /* match against the format */
+ if (query->format) {
+ if (*query->format == '^') {
+ char *p;
+ /* anchored search. match must be at beginning */
+ p = strstr(dp->format, query->format + 1);
+ if (p != dp->format)
+ return false;
+ } else if (!strstr(dp->format, query->format)) {
+ return false;
+ }
+ }
+
+ /* match against the line number range */
+ if (query->first_lineno &&
+ dp->lineno < query->first_lineno)
+ return false;
+ if (query->last_lineno &&
+ dp->lineno > query->last_lineno)
+ return false;
+
+ return true;
+}
+
static int ddebug_change(const struct ddebug_query *query,
struct flag_settings *modifiers)
{
@@ -204,42 +250,7 @@ static int ddebug_change(const struct ddebug_query *query,
for (i = 0; i < dt->num_ddebugs; i++) {
struct _ddebug *dp = &dt->ddebugs[i];
- /* match site against query-class */
- if (dp->class_id != valid_class)
- continue;
-
- /* match against the source filename */
- if (query->filename &&
- !match_wildcard(query->filename, dp->filename) &&
- !match_wildcard(query->filename,
- kbasename(dp->filename)) &&
- !match_wildcard(query->filename,
- trim_prefix(dp->filename)))
- continue;
-
- /* match against the function */
- if (query->function &&
- !match_wildcard(query->function, dp->function))
- continue;
-
- /* match against the format */
- if (query->format) {
- if (*query->format == '^') {
- char *p;
- /* anchored search. match must be at beginning */
- p = strstr(dp->format, query->format+1);
- if (p != dp->format)
- continue;
- } else if (!strstr(dp->format, query->format))
- continue;
- }
-
- /* match against the line number range */
- if (query->first_lineno &&
- dp->lineno < query->first_lineno)
- continue;
- if (query->last_lineno &&
- dp->lineno > query->last_lineno)
+ if (!ddebug_match_desc(query, dp, valid_class))
continue;
nfound++;
--
2.54.0
^ permalink raw reply related
* [PATCH v3 07/24] dyndbg.lds.S: fix lost dyndbg sections in modules
From: Jim Cromie @ 2026-06-01 12:04 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Arnd Bergmann, Jason Baron,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Andrew Morton, Shuah Khan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: linux-doc, linux-kernel, linux-arch, linux-modules,
linux-kselftest, dri-devel, Jim Cromie
In-Reply-To: <20260601-dd-maint-2-v3-0-4a15b241bd3c@gmail.com>
With CONFIG_DRM_USE_DYNAMIC_DEBUG=y, several build configs had
problems with __dyndbg* sections getting lost in drm drivers. Fix
this by following the model demonstrated in codetag.lds.h.
Introduce include/asm-generic/dyndbg.lds.h, to bundle dynamic-debug's
multiple sections together, into 2 macros:
vmlinux.lds.h DATA_DATA: move the 2 BOUNDED_SECTION_BY(__dyndbg*)
calls into dyndbg.lds.h DYNDBG_SECTIONS(). vmlinux.lds.h now includes
the new file and calls the new macro.
MOD_DYNDBG_SECTIONS keeps the 2 sections by name, aligns them and sets
the output address to 0 when the sections are empty.
dyndbg.lds.h includes (reuses) bounded-section.lds.h
scripts/module.lds.S: now calls MOD_DYNDBG_SECTIONS right before the
CODETAG macro (consistent with their placements in vmlinux.lds.h), and
also includes dyndbg.lds.h
This isolates vmlinux.lds.h from further __dyndbg section additions.
CC: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
---
v3: move #includes to top, drop extra ALIGN(8) in DYNDBG_SECTIONS, add RvBy
v2: Address linker script review feedback for relocatable modules.
MOD_DYNDBG_SECTIONS() used the BOUNDED_SECTION_BY() macro, which
proved problematic for kernel modules for two reasons:
1. Unwanted Empty Sections:
BOUNDED_SECTION_BY() automatically generates `__start` and `__stop`
symbols. When applied to `MOD_DYNDBG_SECTIONS()`, the linker assumes
the sections are populated due to the symbol definitions, forcing an
empty `__dyndbg` and `__dyndbg_classes` output section in every
compiled module, even those without dynamic debug configuration.
Since the module loader uses `section_objs()` to locate data via
ELF headers instead of relying on `__start`/`__stop` symbols, these
assignments are completely unnecessary.
2. Non-zero Output Addresses:
During relocatable linking (e.g., `ld.bfd -r`), omitting an explicit
base address causes the section to inherit the current location
counter. This results in non-zero sh_addr values in `.ko` files,
which is confusing, degrades compressibility, and can cause issues
with external tools parsing the ELF.
Fix both issues by dropping `BOUNDED_SECTION_BY()` in favor of a simple
`KEEP(*(...))` constraint and explicitly defining the sections with a `0`
base address: `__dyndbg 0 : ALIGN(8) { ... }`.
fixup-inc-vml
---
MAINTAINERS | 1 +
include/asm-generic/dyndbg.lds.h | 18 ++++++++++++++++++
include/asm-generic/vmlinux.lds.h | 6 ++----
scripts/module.lds.S | 2 ++
4 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 9ec290e38b44..6cf80e7ac039 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9083,6 +9083,7 @@ DYNAMIC DEBUG
M: Jason Baron <jbaron@akamai.com>
M: Jim Cromie <jim.cromie@gmail.com>
S: Maintained
+F: include/asm-generic/dyndbg.lds.h
F: include/linux/dynamic_debug.h
F: lib/dynamic_debug.c
F: lib/test_dynamic_debug.c
diff --git a/include/asm-generic/dyndbg.lds.h b/include/asm-generic/dyndbg.lds.h
new file mode 100644
index 000000000000..9d8951bef688
--- /dev/null
+++ b/include/asm-generic/dyndbg.lds.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __ASM_GENERIC_DYNDBG_LDS_H
+#define __ASM_GENERIC_DYNDBG_LDS_H
+
+#include <asm-generic/bounded_sections.lds.h>
+#define DYNDBG_SECTIONS() \
+ BOUNDED_SECTION_BY(__dyndbg, ___dyndbg) \
+ BOUNDED_SECTION_BY(__dyndbg_classes, ___dyndbg_classes)
+
+#define MOD_DYNDBG_SECTIONS() \
+ __dyndbg 0 : ALIGN(8) { \
+ KEEP(*(__dyndbg)) \
+ } \
+ __dyndbg_classes 0 : ALIGN(8) { \
+ KEEP(*(__dyndbg_classes)) \
+ }
+
+#endif /* __ASM_GENERIC_DYNDBG_LDS_H */
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 2b1becd809be..0a1994bb8793 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -52,6 +52,7 @@
#include <asm-generic/bounded_sections.lds.h>
#include <asm-generic/codetag.lds.h>
+#include <asm-generic/dyndbg.lds.h>
#ifndef LOAD_OFFSET
#define LOAD_OFFSET 0
@@ -344,10 +345,7 @@
*(.data..do_once) \
STRUCT_ALIGN(); \
*(__tracepoints) \
- /* implement dynamic printk debug */ \
- . = ALIGN(8); \
- BOUNDED_SECTION_BY(__dyndbg_classes, ___dyndbg_classes) \
- BOUNDED_SECTION_BY(__dyndbg, ___dyndbg) \
+ DYNDBG_SECTIONS() \
CODETAG_SECTIONS() \
LIKELY_PROFILE() \
BRANCH_PROFILE() \
diff --git a/scripts/module.lds.S b/scripts/module.lds.S
index b62683061d79..2e62dc5bd5d4 100644
--- a/scripts/module.lds.S
+++ b/scripts/module.lds.S
@@ -10,6 +10,7 @@
#endif
#include <asm-generic/codetag.lds.h>
+#include <asm-generic/dyndbg.lds.h>
SECTIONS {
/DISCARD/ : {
@@ -61,6 +62,7 @@ SECTIONS {
*(.rodata..L*)
}
+ MOD_DYNDBG_SECTIONS()
MOD_SEPARATE_CODETAG_SECTIONS()
}
--
2.54.0
^ permalink raw reply related
* [PATCH v3 06/24] vmlinux.lds.h: remove redundant ALIGN(8) directives
From: Jim Cromie @ 2026-06-01 12:04 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Arnd Bergmann, Jason Baron,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Andrew Morton, Shuah Khan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: linux-doc, linux-kernel, linux-arch, linux-modules,
linux-kselftest, dri-devel, Jim Cromie
In-Reply-To: <20260601-dd-maint-2-v3-0-4a15b241bd3c@gmail.com>
The BOUNDED_SECTION_PRE_LABEL and BOUNDED_SECTION_POST_LABEL macros
were recently updated to inherently enforce an 8-byte alignment. This
makes the explicit '. = ALIGN(8);' statements preceding 'naked' macro
calls in vmlinux.lds.h redundant.
Remove these redundant alignment directives to clean up the file and
clarify that the macros handle their own alignment padding.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
include/asm-generic/vmlinux.lds.h | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 145beb14b94b..2b1becd809be 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -228,7 +228,6 @@
#ifdef CONFIG_KPROBES
#define KPROBE_BLACKLIST() \
- . = ALIGN(8); \
BOUNDED_SECTION(_kprobe_blacklist)
#else
#define KPROBE_BLACKLIST()
@@ -244,7 +243,6 @@
#ifdef CONFIG_EVENT_TRACING
#define FTRACE_EVENTS() \
- . = ALIGN(8); \
BOUNDED_SECTION(_ftrace_events) \
BOUNDED_SECTION_BY(_ftrace_eval_map, _ftrace_eval_maps)
#else
@@ -261,7 +259,6 @@
#ifdef CONFIG_FTRACE_SYSCALLS
#define TRACE_SYSCALLS() \
- . = ALIGN(8); \
BOUNDED_SECTION_BY(__syscalls_metadata, _syscalls_metadata)
#else
#define TRACE_SYSCALLS()
@@ -276,7 +273,6 @@
#ifdef CONFIG_SERIAL_EARLYCON
#define EARLYCON_TABLE() \
- . = ALIGN(8); \
BOUNDED_SECTION_POST_LABEL(__earlycon_table, __earlycon_table, , _end)
#else
#define EARLYCON_TABLE()
@@ -284,11 +280,9 @@
#ifdef CONFIG_SECURITY
#define LSM_TABLE() \
- . = ALIGN(8); \
BOUNDED_SECTION_PRE_LABEL(.lsm_info.init, _lsm_info, __start, __end)
#define EARLY_LSM_TABLE() \
- . = ALIGN(8); \
BOUNDED_SECTION_PRE_LABEL(.early_lsm_info.init, _early_lsm_info, __start, __end)
#else
#define LSM_TABLE()
@@ -314,7 +308,6 @@
#ifdef CONFIG_ACPI
#define ACPI_PROBE_TABLE(name) \
- . = ALIGN(8); \
BOUNDED_SECTION_POST_LABEL(__##name##_acpi_probe_table, \
__##name##_acpi_probe_table,, _end)
#else
@@ -323,7 +316,6 @@
#ifdef CONFIG_THERMAL
#define THERMAL_TABLE(name) \
- . = ALIGN(8); \
BOUNDED_SECTION_POST_LABEL(__##name##_thermal_table, \
__##name##_thermal_table,, _end)
#else
@@ -403,12 +395,10 @@
__end_init_stack = .;
#define JUMP_TABLE_DATA \
- . = ALIGN(8); \
BOUNDED_SECTION_BY(__jump_table, ___jump_table)
#ifdef CONFIG_HAVE_STATIC_CALL_INLINE
#define STATIC_CALL_DATA \
- . = ALIGN(8); \
BOUNDED_SECTION_BY(.static_call_sites, _static_call_sites) \
BOUNDED_SECTION_BY(.static_call_tramp_key, _static_call_tramp_key)
#else
@@ -453,7 +443,6 @@
*(.rodata) *(.rodata.*) *(.data.rel.ro*) \
SCHED_DATA \
RO_AFTER_INIT_DATA /* Read only after init */ \
- . = ALIGN(8); \
BOUNDED_SECTION_BY(__tracepoints_ptrs, ___tracepoints_ptrs) \
*(__tracepoints_strings)/* Tracepoints: strings */ \
} \
@@ -947,12 +936,10 @@
/* Alignment must be consistent with (kunit_suite *) in include/kunit/test.h */
#define KUNIT_TABLE() \
- . = ALIGN(8); \
BOUNDED_SECTION_POST_LABEL(.kunit_test_suites, __kunit_suites, _start, _end)
/* Alignment must be consistent with (kunit_suite *) in include/kunit/test.h */
#define KUNIT_INIT_TABLE() \
- . = ALIGN(8); \
BOUNDED_SECTION_POST_LABEL(.kunit_init_test_suites, \
__kunit_init_suites, _start, _end)
--
2.54.0
^ permalink raw reply related
* [PATCH v3 05/24] vmlinux.lds.h: Fix ALIGN(8) omission causing NULL ptr on i386
From: Jim Cromie @ 2026-06-01 12:04 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Arnd Bergmann, Jason Baron,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Andrew Morton, Shuah Khan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: linux-doc, linux-kernel, linux-arch, linux-modules,
linux-kselftest, dri-devel, Jim Cromie, Louis Chauvet
In-Reply-To: <20260601-dd-maint-2-v3-0-4a15b241bd3c@gmail.com>
Almost all uses of the BOUNDED_SECTION macros are ALIGN(8), either
explicitly, or by being below an aligned section containing x*8 byte
objects. The noteworthy exception is BOUNDED_SECTION(__dyndbg), which
immediately follows BOUNDED_SECTION(__dyndbg_classes).
On i386, struct _ddebug_classmap is 28 bytes, so without an explicit
ALIGN(8) in the macro, the following __dyndbg section gets misaligned,
causing a NULL ptr deref in dynamic_debug_init().
So fix this with an explicit ALIGN(8) in the existing BOUNDED_SECTION
macros, and introduce _ALIGNED variants to handle the cases with an
explicit . = ALIGN(x)
Also add explicit alignments for: EXCEPTION_TABLE, ORC_UNWIND_TABLE,
TRACEDATA, INIT_SETUP, and NOTES.
update BOUNDED_SECTION uses inside . = ALIGN(x) stanzas to use
_ALIGNED variants, but keep the outer ALIGNs so the symbols between
them are not "re-aligned".
In particular, scripts/sorttable.c does not tolerate sloppy padding.
At the top of ORC_UNWIND_TABLE, add . = ALIGN(4) to match the struct
orc_header __align() call in the code:
commit b9f174c811e3 ("x86/unwind/orc: Add ELF section with ORC version identifier")
Suggested-by: Louis Chauvet <louis.chauvet@bootlin.com> # _ALIGNED variants.
Link: https://lore.kernel.org/lkml/177402491426.6181.12855763650074831089.b4-review@b4/
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
v3:
sashiko complained about NOTES and .BTF_ids.
gemini asserts that NOTES are natively 4-byte aligned, add comment repeating it.
.BTF_ids doesnt use BOUNDED_BY, since start/end isnt needed;
sashiko evidently got confused by immediately preceding usage.
v2:
sashiko picked up 2 cases, added to the explicit list above
https://sashiko.dev/#/patchset/20260515-asm-generic-1-v3-0-680b273666d4%40gmail.com
---
include/asm-generic/bounded_sections.lds.h | 17 ++++++++++++++---
include/asm-generic/vmlinux.lds.h | 18 ++++++++++--------
2 files changed, 24 insertions(+), 11 deletions(-)
diff --git a/include/asm-generic/bounded_sections.lds.h b/include/asm-generic/bounded_sections.lds.h
index 268cdc34389b..8ff3e3420f60 100644
--- a/include/asm-generic/bounded_sections.lds.h
+++ b/include/asm-generic/bounded_sections.lds.h
@@ -3,19 +3,30 @@
#ifndef _ASM_GENERIC_BOUNDED_SECTIONS_H
#define _ASM_GENERIC_BOUNDED_SECTIONS_H
-#define BOUNDED_SECTION_PRE_LABEL(_sec_, _label_, _BEGIN_, _END_) \
+#define BOUNDED_SECTION_PRE_LABEL_ALIGNED(_sec_, _label_, _BEGIN_, _END_, _ALIGNED_) \
+ . = ALIGN(_ALIGNED_); \
_BEGIN_##_label_ = .; \
KEEP(*(_sec_)) \
_END_##_label_ = .;
-#define BOUNDED_SECTION_POST_LABEL(_sec_, _label_, _BEGIN_, _END_) \
+#define BOUNDED_SECTION_PRE_LABEL(_sec_, _label_, _BEGIN_, _END_) \
+ BOUNDED_SECTION_PRE_LABEL_ALIGNED(_sec_, _label_, _BEGIN_, _END_, 8)
+
+#define BOUNDED_SECTION_POST_LABEL_ALIGNED(_sec_, _label_, _BEGIN_, _END_, _ALIGNED_) \
+ . = ALIGN(_ALIGNED_); \
_label_##_BEGIN_ = .; \
KEEP(*(_sec_)) \
_label_##_END_ = .;
+#define BOUNDED_SECTION_POST_LABEL(_sec_, _label_, _BEGIN_, _END_) \
+ BOUNDED_SECTION_POST_LABEL_ALIGNED(_sec_, _label_, _BEGIN_, _END_, 8)
+
#define BOUNDED_SECTION_BY(_sec_, _label_) \
BOUNDED_SECTION_PRE_LABEL(_sec_, _label_, __start, __stop)
-#define BOUNDED_SECTION(_sec) BOUNDED_SECTION_BY(_sec, _sec)
+#define BOUNDED_SECTION_BY_ALIGNED(_sec_, _label_, _ALIGNED_) \
+ BOUNDED_SECTION_PRE_LABEL_ALIGNED(_sec_, _label_, __start, __stop, _ALIGNED_)
+
+#define BOUNDED_SECTION(_sec) BOUNDED_SECTION_BY(_sec, _sec)
#endif /* _ASM_GENERIC_BOUNDED_SECTIONS_H */
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 9c61dd083f26..145beb14b94b 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -640,7 +640,7 @@
#define EXCEPTION_TABLE(align) \
. = ALIGN(align); \
__ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) { \
- BOUNDED_SECTION_BY(__ex_table, ___ex_table) \
+ BOUNDED_SECTION_BY_ALIGNED(__ex_table, ___ex_table, align) \
}
/*
@@ -650,7 +650,7 @@
#define BTF \
. = ALIGN(PAGE_SIZE); \
.BTF : AT(ADDR(.BTF) - LOAD_OFFSET) { \
- BOUNDED_SECTION_BY(.BTF, _BTF) \
+ BOUNDED_SECTION_BY_ALIGNED(.BTF, _BTF, PAGE_SIZE) \
} \
. = ALIGN(PAGE_SIZE); \
.BTF_ids : AT(ADDR(.BTF_ids) - LOAD_OFFSET) { \
@@ -832,16 +832,17 @@
#ifdef CONFIG_UNWINDER_ORC
#define ORC_UNWIND_TABLE \
+ . = ALIGN(4); \
.orc_header : AT(ADDR(.orc_header) - LOAD_OFFSET) { \
- BOUNDED_SECTION_BY(.orc_header, _orc_header) \
+ BOUNDED_SECTION_BY_ALIGNED(.orc_header, _orc_header, 4) \
} \
. = ALIGN(4); \
.orc_unwind_ip : AT(ADDR(.orc_unwind_ip) - LOAD_OFFSET) { \
- BOUNDED_SECTION_BY(.orc_unwind_ip, _orc_unwind_ip) \
+ BOUNDED_SECTION_BY_ALIGNED(.orc_unwind_ip, _orc_unwind_ip, 4)\
} \
. = ALIGN(2); \
.orc_unwind : AT(ADDR(.orc_unwind) - LOAD_OFFSET) { \
- BOUNDED_SECTION_BY(.orc_unwind, _orc_unwind) \
+ BOUNDED_SECTION_BY_ALIGNED(.orc_unwind, _orc_unwind, 2) \
} \
text_size = _etext - _stext; \
. = ALIGN(4); \
@@ -869,7 +870,7 @@
#define TRACEDATA \
. = ALIGN(4); \
.tracedata : AT(ADDR(.tracedata) - LOAD_OFFSET) { \
- BOUNDED_SECTION_POST_LABEL(.tracedata, __tracedata, _start, _end) \
+ BOUNDED_SECTION_POST_LABEL_ALIGNED(.tracedata, __tracedata, _start, _end, 4) \
}
#else
#define TRACEDATA
@@ -898,13 +899,14 @@
*(.note.gnu.property) \
} \
.notes : AT(ADDR(.notes) - LOAD_OFFSET) { \
- BOUNDED_SECTION_BY(.note.*, _notes) \
+ /* *(.note.*) are natively 4-byte aligned */ \
+ BOUNDED_SECTION_BY_ALIGNED(.note.*, _notes, 4) \
} NOTES_HEADERS \
NOTES_HEADERS_RESTORE
#define INIT_SETUP(initsetup_align) \
. = ALIGN(initsetup_align); \
- BOUNDED_SECTION_POST_LABEL(.init.setup, __setup, _start, _end)
+ BOUNDED_SECTION_POST_LABEL_ALIGNED(.init.setup, __setup, _start, _end, initsetup_align)
#define INIT_CALLS_LEVEL(level) \
__initcall##level##_start = .; \
--
2.54.0
^ permalink raw reply related
* [PATCH v3 04/24] vmlinux.lds.h: drop unused HEADERED_SECTION* macros
From: Jim Cromie @ 2026-06-01 12:04 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Arnd Bergmann, Jason Baron,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Andrew Morton, Shuah Khan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: linux-doc, linux-kernel, linux-arch, linux-modules,
linux-kselftest, dri-devel, Jim Cromie
In-Reply-To: <20260601-dd-maint-2-v3-0-4a15b241bd3c@gmail.com>
These macros are unused, no point in carrying them any more.
NB: these macros were just moved to bounded_sections.lds.h, from
vmlinux.lds.h, which is the known entity, and therefore more
meaningful in the 1-line summary, so thats what I used as the topic.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
include/asm-generic/bounded_sections.lds.h | 15 ---------------
1 file changed, 15 deletions(-)
diff --git a/include/asm-generic/bounded_sections.lds.h b/include/asm-generic/bounded_sections.lds.h
index 8c29293ca7fb..268cdc34389b 100644
--- a/include/asm-generic/bounded_sections.lds.h
+++ b/include/asm-generic/bounded_sections.lds.h
@@ -18,19 +18,4 @@
#define BOUNDED_SECTION(_sec) BOUNDED_SECTION_BY(_sec, _sec)
-#define HEADERED_SECTION_PRE_LABEL(_sec_, _label_, _BEGIN_, _END_, _HDR_) \
- _HDR_##_label_ = .; \
- KEEP(*(.gnu.linkonce.##_sec_)) \
- BOUNDED_SECTION_PRE_LABEL(_sec_, _label_, _BEGIN_, _END_)
-
-#define HEADERED_SECTION_POST_LABEL(_sec_, _label_, _BEGIN_, _END_, _HDR_) \
- _label_##_HDR_ = .; \
- KEEP(*(.gnu.linkonce.##_sec_)) \
- BOUNDED_SECTION_POST_LABEL(_sec_, _label_, _BEGIN_, _END_)
-
-#define HEADERED_SECTION_BY(_sec_, _label_) \
- HEADERED_SECTION_PRE_LABEL(_sec_, _label_, __start, __stop)
-
-#define HEADERED_SECTION(_sec) HEADERED_SECTION_BY(_sec, _sec)
-
#endif /* _ASM_GENERIC_BOUNDED_SECTIONS_H */
--
2.54.0
^ permalink raw reply related
* [PATCH v3 03/24] vmlinux.lds.h: refactor BOUNDED_SECTION_* macros into bounded_sections.lds.h
From: Jim Cromie @ 2026-06-01 12:04 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Arnd Bergmann, Jason Baron,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Andrew Morton, Shuah Khan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: linux-doc, linux-kernel, linux-arch, linux-modules,
linux-kselftest, dri-devel, Jim Cromie
In-Reply-To: <20260601-dd-maint-2-v3-0-4a15b241bd3c@gmail.com>
Move BOUNDED_SECTION_* macros to a new helper file:
include/asm-generic/bounded_sections.lds.h and include it back into
vmlinux.lds.h. This allows its reuse later to fix a failure to keep
dyndbg sections in some circumstances.
NOTES:
These macros are only for use in vmlinux.lds.h, where the _start &
_end symbols are needed. Modules keep sections separate in ELF
sections, with their boundaries known, so the _start and _end are not
useful, and may confuse tools not expecting them.
This patch ignores a checkpatch warning, because new file is covered
by "GENERIC INCLUDE/ASM HEADER FILES" in MAINTAINERS
CC: Arnd Bergmann <arnd@arndb.de>
CC: linux-arch@vger.kernel.org
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
v3: move include to top
---
include/asm-generic/bounded_sections.lds.h | 36 ++++++++++++++++++++++++++++++
include/asm-generic/vmlinux.lds.h | 31 +------------------------
2 files changed, 37 insertions(+), 30 deletions(-)
diff --git a/include/asm-generic/bounded_sections.lds.h b/include/asm-generic/bounded_sections.lds.h
new file mode 100644
index 000000000000..8c29293ca7fb
--- /dev/null
+++ b/include/asm-generic/bounded_sections.lds.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _ASM_GENERIC_BOUNDED_SECTIONS_H
+#define _ASM_GENERIC_BOUNDED_SECTIONS_H
+
+#define BOUNDED_SECTION_PRE_LABEL(_sec_, _label_, _BEGIN_, _END_) \
+ _BEGIN_##_label_ = .; \
+ KEEP(*(_sec_)) \
+ _END_##_label_ = .;
+
+#define BOUNDED_SECTION_POST_LABEL(_sec_, _label_, _BEGIN_, _END_) \
+ _label_##_BEGIN_ = .; \
+ KEEP(*(_sec_)) \
+ _label_##_END_ = .;
+
+#define BOUNDED_SECTION_BY(_sec_, _label_) \
+ BOUNDED_SECTION_PRE_LABEL(_sec_, _label_, __start, __stop)
+
+#define BOUNDED_SECTION(_sec) BOUNDED_SECTION_BY(_sec, _sec)
+
+#define HEADERED_SECTION_PRE_LABEL(_sec_, _label_, _BEGIN_, _END_, _HDR_) \
+ _HDR_##_label_ = .; \
+ KEEP(*(.gnu.linkonce.##_sec_)) \
+ BOUNDED_SECTION_PRE_LABEL(_sec_, _label_, _BEGIN_, _END_)
+
+#define HEADERED_SECTION_POST_LABEL(_sec_, _label_, _BEGIN_, _END_, _HDR_) \
+ _label_##_HDR_ = .; \
+ KEEP(*(.gnu.linkonce.##_sec_)) \
+ BOUNDED_SECTION_POST_LABEL(_sec_, _label_, _BEGIN_, _END_)
+
+#define HEADERED_SECTION_BY(_sec_, _label_) \
+ HEADERED_SECTION_PRE_LABEL(_sec_, _label_, __start, __stop)
+
+#define HEADERED_SECTION(_sec) HEADERED_SECTION_BY(_sec, _sec)
+
+#endif /* _ASM_GENERIC_BOUNDED_SECTIONS_H */
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 60c8c22fd3e4..9c61dd083f26 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -50,6 +50,7 @@
* [__nosave_begin, __nosave_end] for the nosave data
*/
+#include <asm-generic/bounded_sections.lds.h>
#include <asm-generic/codetag.lds.h>
#ifndef LOAD_OFFSET
@@ -211,36 +212,6 @@
# endif
#endif
-#define BOUNDED_SECTION_PRE_LABEL(_sec_, _label_, _BEGIN_, _END_) \
- _BEGIN_##_label_ = .; \
- KEEP(*(_sec_)) \
- _END_##_label_ = .;
-
-#define BOUNDED_SECTION_POST_LABEL(_sec_, _label_, _BEGIN_, _END_) \
- _label_##_BEGIN_ = .; \
- KEEP(*(_sec_)) \
- _label_##_END_ = .;
-
-#define BOUNDED_SECTION_BY(_sec_, _label_) \
- BOUNDED_SECTION_PRE_LABEL(_sec_, _label_, __start, __stop)
-
-#define BOUNDED_SECTION(_sec) BOUNDED_SECTION_BY(_sec, _sec)
-
-#define HEADERED_SECTION_PRE_LABEL(_sec_, _label_, _BEGIN_, _END_, _HDR_) \
- _HDR_##_label_ = .; \
- KEEP(*(.gnu.linkonce.##_sec_)) \
- BOUNDED_SECTION_PRE_LABEL(_sec_, _label_, _BEGIN_, _END_)
-
-#define HEADERED_SECTION_POST_LABEL(_sec_, _label_, _BEGIN_, _END_, _HDR_) \
- _label_##_HDR_ = .; \
- KEEP(*(.gnu.linkonce.##_sec_)) \
- BOUNDED_SECTION_POST_LABEL(_sec_, _label_, _BEGIN_, _END_)
-
-#define HEADERED_SECTION_BY(_sec_, _label_) \
- HEADERED_SECTION_PRE_LABEL(_sec_, _label_, __start, __stop)
-
-#define HEADERED_SECTION(_sec) HEADERED_SECTION_BY(_sec, _sec)
-
#ifdef CONFIG_TRACE_BRANCH_PROFILING
#define LIKELY_PROFILE() \
BOUNDED_SECTION_BY(_ftrace_annotated_branch, _annotated_branch_profile)
--
2.54.0
^ permalink raw reply related
* [PATCH v3 02/24] docs/dyndbg: explain flags parse 1st
From: Jim Cromie @ 2026-06-01 12:04 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Arnd Bergmann, Jason Baron,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Andrew Morton, Shuah Khan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: linux-doc, linux-kernel, linux-arch, linux-modules,
linux-kselftest, dri-devel, Jim Cromie, Louis Chauvet
In-Reply-To: <20260601-dd-maint-2-v3-0-4a15b241bd3c@gmail.com>
When writing queries to >control, flags are parsed 1st, since they are
the only required field, and they require specific compositions. So
if the flags draw an error (on those specifics), then keyword errors
aren't reported. This can be mildly confusing/annoying, so explain it
instead.
cc: linux-doc@vger.kernel.org
Reviewed-by: Louis Chauvet <louis.chauvet@bootlin.com>
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
Documentation/admin-guide/dynamic-debug-howto.rst | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/Documentation/admin-guide/dynamic-debug-howto.rst b/Documentation/admin-guide/dynamic-debug-howto.rst
index 4b14d9fd0300..9c2f096ed1d8 100644
--- a/Documentation/admin-guide/dynamic-debug-howto.rst
+++ b/Documentation/admin-guide/dynamic-debug-howto.rst
@@ -109,10 +109,19 @@ The match-spec's select *prdbgs* from the catalog, upon which to apply
the flags-spec, all constraints are ANDed together. An absent keyword
is the same as keyword "*".
-
-A match specification is a keyword, which selects the attribute of
-the callsite to be compared, and a value to compare against. Possible
-keywords are:::
+Note that since the match-spec can be empty, the flags are checked 1st,
+then the pairs of keyword and value. Flag errs will hide keyword errs::
+
+ bash-5.2# ddcmd mod bar +foo
+ dyndbg: read 13 bytes from userspace
+ dyndbg: query 0: "mod bar +foo" mod:*
+ dyndbg: unknown flag 'o'
+ dyndbg: flags parse failed
+ dyndbg: processed 1 queries, with 0 matches, 1 errs
+
+So a match-spec is a keyword, which selects the attribute of the
+callsite to be compared, and a value to compare against. Possible
+keywords are::
match-spec ::= 'func' string |
'file' string |
--
2.54.0
^ permalink raw reply related
* [PATCH v3 01/24] docs/dyndbg: update examples \012 to \n
From: Jim Cromie @ 2026-06-01 12:04 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Arnd Bergmann, Jason Baron,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Andrew Morton, Shuah Khan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: linux-doc, linux-kernel, linux-arch, linux-modules,
linux-kselftest, dri-devel, Jim Cromie, Louis Chauvet
In-Reply-To: <20260601-dd-maint-2-v3-0-4a15b241bd3c@gmail.com>
commit 47ea6f99d06e ("dyndbg: use ESCAPE_SPACE for cat control")
changed the control-file to display format strings with "\n" rather
than "\012". Update the docs to match the new reality.
Reviewed-by: Louis Chauvet <louis.chauvet@bootlin.com>
Tested-by: Louis Chauvet <louis.chauvet@bootlin.com>
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
Documentation/admin-guide/dynamic-debug-howto.rst | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/Documentation/admin-guide/dynamic-debug-howto.rst b/Documentation/admin-guide/dynamic-debug-howto.rst
index 095a63892257..4b14d9fd0300 100644
--- a/Documentation/admin-guide/dynamic-debug-howto.rst
+++ b/Documentation/admin-guide/dynamic-debug-howto.rst
@@ -38,12 +38,12 @@ You can view the currently configured behaviour in the *prdbg* catalog::
:#> head -n7 /proc/dynamic_debug/control
# filename:lineno [module]function flags format
- init/main.c:1179 [main]initcall_blacklist =_ "blacklisting initcall %s\012
- init/main.c:1218 [main]initcall_blacklisted =_ "initcall %s blacklisted\012"
- init/main.c:1424 [main]run_init_process =_ " with arguments:\012"
- init/main.c:1426 [main]run_init_process =_ " %s\012"
- init/main.c:1427 [main]run_init_process =_ " with environment:\012"
- init/main.c:1429 [main]run_init_process =_ " %s\012"
+ init/main.c:1179 [main]initcall_blacklist =_ "blacklisting initcall %s\n"
+ init/main.c:1218 [main]initcall_blacklisted =_ "initcall %s blacklisted\n"
+ init/main.c:1424 [main]run_init_process =_ " with arguments:\n"
+ init/main.c:1426 [main]run_init_process =_ " %s\n"
+ init/main.c:1427 [main]run_init_process =_ " with environment:\n"
+ init/main.c:1429 [main]run_init_process =_ " %s\n"
The 3rd space-delimited column shows the current flags, preceded by
a ``=`` for easy use with grep/cut. ``=p`` shows enabled callsites.
@@ -59,10 +59,10 @@ query/commands to the control file. Example::
:#> ddcmd '-p; module main func run* +p'
:#> grep =p /proc/dynamic_debug/control
- init/main.c:1424 [main]run_init_process =p " with arguments:\012"
- init/main.c:1426 [main]run_init_process =p " %s\012"
- init/main.c:1427 [main]run_init_process =p " with environment:\012"
- init/main.c:1429 [main]run_init_process =p " %s\012"
+ init/main.c:1424 [main]run_init_process =p " with arguments:\n"
+ init/main.c:1426 [main]run_init_process =p " %s\n"
+ init/main.c:1427 [main]run_init_process =p " with environment:\n"
+ init/main.c:1429 [main]run_init_process =p " %s\n"
Error messages go to console/syslog::
--
2.54.0
^ permalink raw reply related
* [PATCH v3 00/24] dynamic-debug cleanups refactors maintenance + alignment fix
From: Jim Cromie @ 2026-06-01 12:04 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Arnd Bergmann, Jason Baron,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Andrew Morton, Shuah Khan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter
Cc: linux-doc, linux-kernel, linux-arch, linux-modules,
linux-kselftest, dri-devel, Jim Cromie, Louis Chauvet,
Łukasz Bartosik
This series starts with 2 doc-only patches updating the current state
of dynamic-debug, they were posted separately here, without pushback.
https://lore.kernel.org/linux-doc/20260502-dyndbg-doc-v1-0-67cc4a93a77e@gmail.com/
Next 5 are a fix to a linker-script alignment problem in 32bit arches
causing a null-ptr scanning dyndbg-descriptor section on i386. These
were reviewed by Petr Pavlu.
The remaining patches are cleanups, refactors in preparation for an
API change needed to fix a regression in DRM when it uses classmaps.
I split these out for easier review, I will follow up with the API
change afterwards.
The biggest revision vs V2 is the new patch: 25. It addresses a flaw
detected by sashiko which is best described by example.
Dyndbg uses KBUILD_MODNAME to provide module-name, this works well for
loadable modules (module loader requires unique module names), but for
builtin modules, is effectively kbasename, and not unique.
So we get 4 modules named "main": init/main, kernel/power/main,
kernel/base/poser/main. This ambiguity is visible in user-space since
the beginning of dyndbg.
Now suppose kernel/{,base}/power/main want to define classmaps to
categorize the various pr-debugs they have. The current code finds a
module's classmaps by strcmp on modname, so init/main will match
against classmaps defined by both kernel/{,base}/power/main.
The current code will also map "main" classes to kernel/*/power/main,
so they will probably work at first, but 2 independent classmaps can
both use class-ids 0-N, but will conflict if they're both used by a
module. Then we have classmap overlaps and unpredictable results.
patch-25 eliminates the ambiguity by using KBUILD_MODFILE to provide a
unique module-name, then adds matching against kbasename(modname) to
restore the legacy query behavior. It *does* change the modname
exposed in /proc/dynamic_debug/control, but not the result of a query
like "module main +p".
OLDER VERSIONS:
V2 primarily revises:
https://lore.kernel.org/lkml/20260504-dd-cleanups-2-v1-0-6fdd24040642@gmail.com/
V2 addressed most of sashiko's feedback on V1:
https://sashiko.dev/#/patchset/20260504-dd-cleanups-2-v1-0-6fdd24040642%40gmail.com
It dropped the pr-fmt patch, as not reproducible,
advanced the drop-NAMES patch to reduce subsequent churn,
and fixed the classmaps PARAMs to u64 to avoid 32bit flags on 32bit arches
For easy one-stop-shopping, V2 also included 2 smaller series:
1st fixes a section alignment problem, with Reviewed-by from Petr Pavlu
https://lore.kernel.org/lkml/20260515-asm-generic-1-v3-0-680b273666d4@gmail.com/
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
Changes in v3:
- EDITME: describe what is new in this series revision.
- EDITME: use bulletpoints and terse descriptions.
- Link to v2: https://lore.kernel.org/r/20260523-dd-maint-2-v2-0-b937312aa083@gmail.com
---
Jim Cromie (24):
docs/dyndbg: update examples \012 to \n
docs/dyndbg: explain flags parse 1st
vmlinux.lds.h: refactor BOUNDED_SECTION_* macros into bounded_sections.lds.h
vmlinux.lds.h: drop unused HEADERED_SECTION* macros
vmlinux.lds.h: Fix ALIGN(8) omission causing NULL ptr on i386
vmlinux.lds.h: remove redundant ALIGN(8) directives
dyndbg.lds.S: fix lost dyndbg sections in modules
dyndbg: factor ddebug_match_desc out from ddebug_change
dyndbg: add stub macro for DECLARE_DYNDBG_CLASSMAP
dyndbg: reword "class unknown," to "class:_UNKNOWN_"
dyndbg-API: remove DD_CLASS_TYPE_(DISJOINT|LEVEL)_NAMES and code
dyndbg: drop NUM_TYPE_ARGS
dyndbg: reduce verbose/debug clutter
dyndbg: refactor param_set_dyndbg_classes and below
dyndbg: tighten fn-sig of ddebug_apply_class_bitmap
dyndbg: replace classmap list with an array-slice
dyndbg: macrofy a 2-index for-loop pattern
dyndbg: Upgrade class param storage to u64 for 64-bit classmaps
dyndbg,module: make proper substructs in _ddebug_info
dyndbg: move mod_name down from struct ddebug_table to _ddebug_info
dyndbg: hoist classmap-filter-by-modname up to ddebug_add_module
selftests-dyndbg: add a dynamic_debug run_tests target
dyndbg: change __dynamic_func_call_cls* macros into expressions
dynamic_debug: use KBUILD_MODFILE for unique builtin module names
Documentation/admin-guide/dynamic-debug-howto.rst | 55 ++-
MAINTAINERS | 2 +
drivers/gpu/drm/drm_print.c | 6 +-
include/asm-generic/bounded_sections.lds.h | 32 ++
include/asm-generic/dyndbg.lds.h | 18 +
include/asm-generic/vmlinux.lds.h | 68 +--
include/drm/drm_print.h | 2 +-
include/linux/dynamic_debug.h | 133 +++---
kernel/module/main.c | 12 +-
lib/dynamic_debug.c | 501 ++++++++++-----------
lib/test_dynamic_debug.c | 30 +-
scripts/module.lds.S | 2 +
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/dynamic_debug/Makefile | 9 +
tools/testing/selftests/dynamic_debug/config | 8 +
.../selftests/dynamic_debug/dyndbg_selftest.sh | 294 ++++++++++++
16 files changed, 736 insertions(+), 437 deletions(-)
---
base-commit: e43ffb69e0438cddd72aaa30898b4dc446f664f8
change-id: 20260521-dd-maint-2-76c542079420
Best regards,
--
Jim Cromie <jim.cromie@gmail.com>
^ permalink raw reply
* Re: [PATCH mm-unstable v18 11/14] mm/khugepaged: Introduce mTHP collapse support
From: Nico Pache @ 2026-06-01 12:01 UTC (permalink / raw)
To: Lance Yang
Cc: linux-doc, linux-kernel, linux-mm, linux-trace-kernel, aarcange,
akpm, anshuman.khandual, apopple, baohua, baolin.wang, byungchul,
catalin.marinas, cl, corbet, dave.hansen, david, dev.jain, gourry,
hannes, hughd, jack, jackmanb, jannh, jglisse, joshua.hahnjy, kas,
liam, ljs, mathieu.desnoyers, matthew.brost, mhiramat, mhocko,
peterx, pfalcato, rakie.kim, raquini, rdunlap, richard.weiyang,
rientjes, rostedt, rppt, ryan.roberts, shivankg, sunnanyong,
surenb, thomas.hellstrom, tiwai, usamaarif642, vbabka,
vishal.moola, wangkefeng.wang, will, willy, yang, ying.huang, ziy,
zokeefe
In-Reply-To: <6a9f062c-8376-4f83-90a9-8b167f925dc6@linux.dev>
On Sun, May 31, 2026 at 2:48 AM Lance Yang <lance.yang@linux.dev> wrote:
>
>
>
> On 2026/5/31 15:18, Lance Yang wrote:
> >
> > On Fri, May 22, 2026 at 09:00:06AM -0600, Nico Pache wrote:
> > [...]
> >> @@ -1587,10 +1749,11 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
> >> if (result == SCAN_SUCCEED) {
> >> /* collapse_huge_page expects the lock to be dropped before calling */
> >> mmap_read_unlock(mm);
> >> - result = collapse_huge_page(mm, start_addr, referenced,
> >> - unmapped, cc, HPAGE_PMD_ORDER);
> >> - /* collapse_huge_page will return with the mmap_lock released */
> >> + nr_collapsed = mthp_collapse(mm, vma, start_addr, referenced,
> >> + unmapped, cc, enabled_orders);
> >> + /* mmap_lock was released above, set lock_dropped */
> >> *lock_dropped = true;
> >> + result = nr_collapsed ? SCAN_SUCCEED : SCAN_FAIL;
> >
> > Hmm ... don't we lose the allocation-failure result here?
> >
> > Previously collapse_scan_pmd() propagated SCAN_ALLOC_HUGE_PAGE_FAIL from
> > collapse_huge_page(), so khugepaged would call khugepaged_alloc_sleep()
> > in khugepaged_do_scan().
> >
> > Now if allocation fails and nr_collapsed stays 0, we just return
> > SCAN_FAIL. So we won't back off via khugepaged_alloc_sleep() anymore?
>
> Looks like this is a more general issue with mthp_collapse() only
> returning nr_collapsed.
>
> For example, SCAN_PMD_MAPPED used to be propagated too, and
> madvise_collapse() treats that as success. With the new code, if
> nothing was collapsed by this call, that can also become SCAN_FAIL ...
>
> So I think we should keep both.
Yeah I thought about this before, but more regarding the "incorrect"
propagation of errors; I didn't consider that those results were
actually being considered.
I actually had a patch to track the last_failure (with some
prioritization on certain results). I think that would solve this
issue.
Thanks for reminding me to improve this.
Depending on how the rest of the reviews go, I can either send up a
follow up series to do some more cleanups and improvements of the
current approach or we can send out a v19.
Cheers,
-- Nico
>
> Cheers, Lance
>
^ permalink raw reply
* RE: [PATCH 03/11] net: wwan: t9xx: Add control DMA interface
From: Jagielski, Jedrzej @ 2026-06-01 11:54 UTC (permalink / raw)
To: jackbb_wu@compal.com, Loic Poulain, Sergey Ryazanov,
Johannes Berg, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Wen-Zhi Huang, Shi-Wei Yeh,
Minano Tseng, Matthias Brugger, AngeloGioacchino Del Regno,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-mediatek@lists.infradead.org, linux-doc@vger.kernel.org
In-Reply-To: <20260529-t9xx_driver_v1-v1-3-bdbfe2c01e57@compal.com>
From: Jack Wu via B4 Relay <devnull+jackbb_wu.compal.com@kernel.org>
Sent: Friday, May 29, 2026 12:32 PM
>From: Jack Wu <jackbb_wu@compal.com>
>
>Cross Layer Direct Memory Access(CLDMA) is the hardware
>interface used by the control plane and designated to
>translate data between the host and the device. It supports
>8 hardware queues for the device AP and modem respectively.
>
>CLDMA driver uses General Purpose Descriptor (GPD) to
>describe transaction information that can be recognized by
>CLDMA hardware. Once CLDMA hardware transaction is started,
>it would fetch and parse GPD to transfer data correctly.
>To facilitate the CLDMA transaction, a GPD ring for each
>queue is used. Once the transaction is started, CLDMA
>hardware will traverse the GPD ring to transfer data between
>the host and the device until no GPD is available.
>
>CLDMA TX flow:
>Once a TX service receives the TX data from the port layer,
>it uses APIs exported by the CLDMA driver to configure GPD
>with the DMA address of TX data. After that, the service
>triggers CLDMA to fetch the first available GPD to transfer
>data.
>
>CLDMA RX flow:
>When there is RX data from the MD, CLDMA hardware asserts an
>interrupt to notify the host to fetch data and dispatch it
>to FSM (for handshake messages) or the port layer.
>After CLDMA opening is finished, All RX GPDs are fulfilled
>and ready to receive data from the device.
>
>Signed-off-by: Jack Wu <jackbb_wu@compal.com>
>---
> drivers/net/wwan/t9xx/mtk_ctrl_plane.c | 3 +-
> drivers/net/wwan/t9xx/mtk_ctrl_plane.h | 52 +-
> drivers/net/wwan/t9xx/pcie/Makefile | 7 +-
> drivers/net/wwan/t9xx/pcie/mtk_cldma.c | 1220 +++++++++++++++++++++++
> drivers/net/wwan/t9xx/pcie/mtk_cldma.h | 170 ++++
> drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c | 373 +++++++
> drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h | 177 ++++
> drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c | 182 ++++
> drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h | 103 ++
> drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c | 23 +
> drivers/net/wwan/t9xx/pcie/mtk_pci.c | 38 +
> drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h | 1 +
> drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c | 569 +++++++++++
> drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h | 84 ++
> 14 files changed, 2998 insertions(+), 4 deletions(-)
>
>diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
>index ae5e1797b817..ca32827c1a20 100644
>--- a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
>+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
>@@ -8,7 +8,7 @@
>
> #include "mtk_ctrl_plane.h"
>
>-int mtk_ctrl_init(struct mtk_md_dev *mdev)
>+int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops)
> {
> struct mtk_ctrl_blk *ctrl_blk;
>
>@@ -18,6 +18,7 @@ int mtk_ctrl_init(struct mtk_md_dev *mdev)
>
> ctrl_blk->mdev = mdev;
> mdev->ctrl_blk = ctrl_blk;
>+ ctrl_blk->ops = ops;
>
> return 0;
> }
>diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
>index 8276be19b456..6d4be89680d6 100644
>--- a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
>+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
>@@ -11,12 +11,60 @@
>
> #include "mtk_dev.h"
>
>+enum mtk_trb_cmd_type {
>+ TRB_CMD_MIN,
>+ TRB_CMD_ENABLE,
>+ TRB_CMD_TX,
>+ TRB_CMD_DISABLE,
>+ TRB_CMD_STOP,
>+ TRB_CMD_RECOVER,
>+ TRB_CMD_MAX,
>+};
>+
>+enum mtk_hif_dev_ctrl_cmd {
>+ HIF_CTRL_CMD_CHECK_TX_FULL,
>+};
>+
>+struct trb_open_priv {
>+ u8 log_rg_offset;
>+ u32 tx_mtu;
>+ u32 rx_mtu;
>+ u32 tx_frag_size;
>+ u32 rx_frag_size;
>+ int (*rx_done)(struct sk_buff *skb, void *priv, bool force_recv);
>+};
>+
>+struct trb {
>+ u32 channel_id;
>+ enum mtk_trb_cmd_type cmd;
>+ int status;
>+ struct kref kref;
>+ void *priv;
>+ int (*trb_complete)(struct sk_buff *skb);
>+};
>+
>+union ctrl_hif_cmd_data {
>+ u32 rx_ch;
>+};
>+
>+struct mtk_ctrl_hif_ops {
>+ int (*init)(struct mtk_md_dev *mdev);
>+ int (*exit)(struct mtk_md_dev *mdev);
>+ int (*submit_skb)(struct mtk_md_dev *mdev, struct sk_buff *skb, bool force_send);
>+ int (*send_cmd)(struct mtk_md_dev *mdev, int cmd, void *data);
>+};
>+
>+struct mtk_ctrl_cfg;
>+struct mtk_ctrl_trans;
>+
> struct mtk_ctrl_blk {
> struct mtk_md_dev *mdev;
>- struct mtk_ctrl_trans *trans;
>+ struct mtk_ctrl_hif_ops *ops;
>+ void *ctrl_hw_priv;
>+ struct mtk_ctrl_cfg *cfg;
> };
>
>-int mtk_ctrl_init(struct mtk_md_dev *mdev);
>+int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops);
> int mtk_ctrl_exit(struct mtk_md_dev *mdev);
>
> #endif /* __MTK_CTRL_PLANE_H__ */
>diff --git a/drivers/net/wwan/t9xx/pcie/Makefile b/drivers/net/wwan/t9xx/pcie/Makefile
>index 7410d1796d27..5252f158b058 100644
>--- a/drivers/net/wwan/t9xx/pcie/Makefile
>+++ b/drivers/net/wwan/t9xx/pcie/Makefile
>@@ -7,4 +7,9 @@ obj-$(CONFIG_MTK_T9XX_PCI) += mtk_t9xx_pcie.o
>
> mtk_t9xx_pcie-y := \
> mtk_pci_drv_m9xx.o \
>- mtk_pci.o
>+ mtk_cldma_drv_m9xx.o \
>+ mtk_ctrl_cfg_m9xx.o \
>+ mtk_pci.o \
>+ mtk_trans_ctrl.o \
>+ mtk_cldma.o \
>+ mtk_cldma_drv.o
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma.c
>new file mode 100644
>index 000000000000..48067a010890
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma.c
>@@ -0,0 +1,1220 @@
>+// SPDX-License-Identifier: GPL-2.0-only
>+/*
>+ * Copyright (c) 2022, MediaTek Inc.
>+ */
>+
>+#include <linux/delay.h>
>+#include <linux/device.h>
>+#include <linux/dma-mapping.h>
>+#include <linux/dmapool.h>
>+#include <linux/err.h>
>+#include <linux/interrupt.h>
>+#include <linux/kdev_t.h>
>+#include <linux/kernel.h>
>+#include <linux/kthread.h>
>+#include <linux/list.h>
>+#include <linux/module.h>
>+#include <linux/mutex.h>
>+#include <linux/netdevice.h>
>+#include <linux/sched.h>
>+#include <linux/skbuff.h>
>+#include <linux/slab.h>
>+#include <linux/timer.h>
>+#include <linux/wait.h>
>+#include <linux/workqueue.h>
>+#include "mtk_pci.h"
>+#include "mtk_cldma.h"
>+#include "mtk_cldma_drv.h"
>+#include "mtk_dev.h"
>+
>+#define cldma_drv_ops_null NULL
>+#define DMA_POOL_NAME_LEN (64)
>+#define WAIT_HWO_ROUND (10)
>+#define WAIT_HWO_TIME (5)
>+#define CLDMA_RETRY_DELAY_MS (100)
>+#define NO_BUDGET (0)
>+
>+static const int mtk_cldma_hw_id_tbl[NR_CLDMA] = {
>+ [CLDMA0] = CLDMA0_HW_ID,
>+ [CLDMA1] = CLDMA1_HW_ID,
>+ [CLDMA4] = CLDMA4_HW_ID,
>+};
>+
>+static inline void mtk_cldma_clr_bd_dsc(struct cldma_drv_info *drv_info,
>+ struct bd_dsc *bd_dsc_pool, int nr_bds)
>+{
>+ struct bd_dsc *bd_dsc;
>+ int i;
>+
>+ for (i = 0; i < nr_bds; i++) {
>+ bd_dsc = bd_dsc_pool + i;
>+ dma_unmap_single(drv_info->mdev->dev, bd_dsc->data_dma_addr,
>+ bd_dsc->data_len, DMA_TO_DEVICE);
>+ bd_dsc->data_dma_addr = 0;
>+ bd_dsc->data_len = 0;
>+ if (bd_dsc->bd->tx_bd.bd_flags & CLDMA_BD_FLAG_EOL) {
>+ bd_dsc->bd->tx_bd.bd_flags &= ~CLDMA_BD_FLAG_EOL;
>+ break;
>+ }
>+ }
>+}
>+
>+static void mtk_cldma_tx_done_work(struct work_struct *work)
>+{
>+ struct txq *txq = container_of(work, struct txq, tx_done_work);
>+ struct cldma_drv_info *drv_info;
>+ struct cldma_drv_ops *drv_ops;
>+ struct mtk_ctrl_trans *trans;
>+ struct mtk_md_dev *mdev;
>+ struct tx_req *req;
>+ unsigned int state;
>+ int i, hif_id;
>+ struct trb *trb;
>+ u32 txqno;
please stick to RCT
>+
>+ drv_info = txq->drv_info;
>+ hif_id = drv_info->hif_id;
>+ txqno = txq->txqno;
>+ mdev = drv_info->mdev;
>+ drv_ops = drv_info->drv_ops;
>+ trans = drv_info->cd->trans;
>+
>+again:
>+ for (i = 0; i < txq->nr_gpds; i++) {
>+ req = txq->req_pool + txq->free_idx;
>+
>+ rmb(); /* ensure HWO setup done before HWO read */
>+
>+ if (!req->data_vm_addr || (req->gpd->tx_gpd.gpd_flags & CLDMA_GPD_FLAG_HWO))
>+ break;
>+
>+ if (txq->nr_bds)
>+ mtk_cldma_clr_bd_dsc(drv_info, req->bd_dsc_pool, txq->nr_bds);
>+ else
>+ dma_unmap_single(mdev->dev, req->data_dma_addr,
>+ req->data_len, DMA_TO_DEVICE);
>+
>+ trb = (struct trb *)req->skb->cb;
>+ trb->status = 0;
>+ trb->trb_complete(req->skb);
>+
>+ req->data_vm_addr = NULL;
>+ req->data_dma_addr = 0;
>+ req->data_len = 0;
>+ req->skb = NULL;
>+
>+ txq->free_idx = (txq->free_idx + 1) % txq->nr_gpds;
>+ if (atomic_fetch_inc(&txq->req_budget) == NO_BUDGET)
>+ wake_up(&trans->trb_srv[trans->srv_cfg[hif_id][txqno]]->trb_waitq);
>+ }
>+
>+ state = drv_ops->cldma_check_intr_status(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);
>+ if (state) {
>+ if (unlikely(state == LINK_ERROR_VAL))
>+ goto out;
>+
>+ drv_ops->cldma_clr_intr_status(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);
>+
>+ cond_resched();
>+
>+ goto again;
are we sure we won't be locked here?
>+ }
>+
>+out:
>+ drv_ops->cldma_unmask_intr(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);
>+}
>+
>+static void mtk_cldma_rx_skb_adjust(struct mtk_md_dev *mdev, struct rxq *rxq,
>+ struct rx_req *req)
>+{
>+ struct bd_dsc *bd_dsc;
>+ int i;
>+
>+ for (i = 0; i < rxq->nr_bds; i++) {
>+ bd_dsc = req->bd_dsc_pool + i;
>+ if (bd_dsc->data_dma_addr) {
>+ dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
>+ req->frag_size, DMA_FROM_DEVICE);
>+ bd_dsc->data_dma_addr = 0;
>+ }
>+ bd_dsc->skb->len = 0;
>+ skb_reset_tail_pointer(bd_dsc->skb);
>+ skb_put(bd_dsc->skb,
>+ le16_to_cpu(bd_dsc->bd->rx_bd.data_recv_len));
>+ if (req->skb != bd_dsc->skb) {
>+ req->skb->len += bd_dsc->skb->len;
>+ req->skb->data_len += bd_dsc->skb->len;
>+ }
>+ bd_dsc->bd->rx_bd.data_recv_len = 0;
>+ bd_dsc->skb = NULL;
>+ }
>+ if (!rxq->nr_bds) {
>+ if (req->data_dma_addr) {
>+ dma_unmap_single(mdev->dev, req->data_dma_addr,
>+ req->mtu, DMA_FROM_DEVICE);
>+ req->data_dma_addr = 0;
>+ }
>+ req->skb->len = 0;
>+ skb_reset_tail_pointer(req->skb);
>+ skb_put(req->skb, le16_to_cpu(req->gpd->rx_gpd.data_recv_len));
>+ }
>+
>+ req->gpd->rx_gpd.data_recv_len = 0;
>+}
>+
>+static int mtk_cldma_reload_rx_skb(struct mtk_md_dev *mdev, struct rxq *rxq,
>+ struct rx_req *req)
>+{
>+ struct sk_buff *tail = NULL;
>+ struct bd_dsc *bd_dsc;
>+ int nr_bds;
>+ int i, err;
>+
>+ nr_bds = rxq->nr_bds;
>+
>+ for (i = 0; i < nr_bds; i++) {
>+ bd_dsc = req->bd_dsc_pool + i;
>+ bd_dsc->skb = __dev_alloc_skb(req->frag_size, GFP_KERNEL);
>+ if (!bd_dsc->skb) {
>+ dev_warn((mdev)->dev, "Failed to alloc SKB\n");
>+ err = -ENOMEM;
>+ goto err_free_skb;
>+ }
>+ bd_dsc->skb->next = NULL;
>+ bd_dsc->data_dma_addr = dma_map_single(mdev->dev, bd_dsc->skb->data,
>+ req->frag_size, DMA_FROM_DEVICE);
>+ err = dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr);
>+ if (unlikely(err)) {
>+ dev_warn((mdev)->dev, "Failed to map SKB data\n");
>+ err = -EFAULT;
>+ goto err_free_skb;
>+ }
>+ bd_dsc->bd->rx_bd.data_buff_ptr_h =
>+ cpu_to_le32((u64)(bd_dsc->data_dma_addr) >> 32);
>+ bd_dsc->bd->rx_bd.data_buff_ptr_l =
>+ cpu_to_le32(bd_dsc->data_dma_addr);
>+ if (tail) {
>+ tail->next = bd_dsc->skb;
>+ tail = bd_dsc->skb;
>+ continue;
>+ }
>+ if (!req->skb) {
>+ req->skb = bd_dsc->skb;
>+ } else {
>+ skb_shinfo(req->skb)->frag_list = bd_dsc->skb;
>+ tail = bd_dsc->skb;
>+ }
>+ }
>+ if (!nr_bds) {
>+ req->skb = __dev_alloc_skb(req->mtu, GFP_KERNEL);
>+ if (!req->skb) {
>+ err = -ENOMEM;
>+ goto err_free_skb;
>+ }
>+
>+ req->data_dma_addr = dma_map_single(mdev->dev, req->skb->data,
>+ req->mtu, DMA_FROM_DEVICE);
>+ err = dma_mapping_error(mdev->dev, req->data_dma_addr);
>+ if (unlikely(err)) {
>+ dev_warn((mdev)->dev, "Failed to map SKB data\n");
>+ err = -EFAULT;
>+ goto err_free_skb;
>+ }
>+ req->gpd->rx_gpd.data_buff_ptr_h = cpu_to_le32((u64)req->data_dma_addr >> 32);
>+ req->gpd->rx_gpd.data_buff_ptr_l = cpu_to_le32(req->data_dma_addr);
>+ }
>+ return 0;
>+
>+err_free_skb:
>+ if (nr_bds) {
>+ if (req->skb)
>+ skb_shinfo(req->skb)->frag_list = NULL;
>+ for (i = 0; i < nr_bds; i++) {
>+ bd_dsc = req->bd_dsc_pool + i;
>+ if (!bd_dsc->skb)
>+ break;
>+ if (!dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr))
>+ dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
>+ req->frag_size, DMA_FROM_DEVICE);
>+ bd_dsc->data_dma_addr = 0;
>+ bd_dsc->skb->next = NULL;
>+ dev_kfree_skb_any(bd_dsc->skb);
>+ }
>+ } else {
>+ req->data_dma_addr = 0;
>+ if (req->skb)
>+ dev_kfree_skb_any(req->skb);
>+ }
>+ req->skb = NULL;
>+
>+ return err;
>+}
>+
>+static int mtk_cldma_check_rx_req(struct cldma_drv_info *drv_info, struct rxq *rxq)
>+{
>+ struct rx_req *req = rxq->req_pool + rxq->free_idx;
>+ u64 curr_addr;
>+ int i;
>+
>+ curr_addr = drv_info->drv_ops->cldma_get_rx_curr_addr(drv_info, rxq->rxqno);
>+ if (unlikely(!curr_addr))
>+ return -ENXIO;
>+
>+ if (req->gpd_dma_addr == curr_addr)
>+ return -EAGAIN;
>+ for (i = 0; i < WAIT_HWO_ROUND; i++) {
>+ udelay(WAIT_HWO_TIME);
>+ if (!(req->gpd->rx_gpd.gpd_flags & CLDMA_GPD_FLAG_HWO))
>+ break;
>+ }
>+ if (i == WAIT_HWO_ROUND) {
>+ dev_err((drv_info->mdev)->dev, "Failed to check HWO=0\n");
>+ return -EAGAIN;
>+ }
>+
>+ return 0;
>+}
>+
>+static bool mtk_cldma_rx_check_again(struct rxq *rxq)
>+{
>+ struct cldma_drv_info *drv_info;
>+ struct cldma_drv_ops *drv_ops;
>+ bool need_check_again = false;
>+ struct mtk_md_dev *mdev;
>+ int rxqno;
>+ u32 state;
>+
>+ drv_info = rxq->drv_info;
>+ drv_ops = drv_info->drv_ops;
>+ mdev = drv_info->mdev;
>+ rxqno = rxq->rxqno;
>+
>+ do {
>+ state = drv_ops->cldma_check_intr_status(drv_info, DIR_RX,
>+ rxqno, QUEUE_XFER_DONE);
>+ if (state) {
>+ if (unlikely(state == LINK_ERROR_VAL))
>+ break;
>+
>+ drv_ops->cldma_clr_intr_status(drv_info, DIR_RX,
>+ rxqno, QUEUE_XFER_DONE);
>+ cond_resched();
>+ return true;
>+ }
>+ } while (need_check_again);
>+
>+ return false;
>+}
>+
>+static void mtk_cldma_rx_done_work(struct work_struct *work)
>+{
>+ struct rxq *rxq = container_of(work, struct rxq, rx_done_work);
>+ struct rx_req *req = NULL, *pre_req = NULL;
>+ struct cldma_drv_info *drv_info;
>+ struct cldma_drv_ops *drv_ops;
>+ struct mtk_md_dev *mdev;
>+ int i, err, idx;
>+
>+ drv_info = rxq->drv_info;
>+ mdev = drv_info->mdev;
>+ drv_ops = drv_info->drv_ops;
>+
>+again:
>+ for (i = 0; i < rxq->nr_gpds; i++) {
>+ req = rxq->req_pool + rxq->free_idx;
>+ if (!req->skb) {
>+ dev_err((mdev)->dev,
>+ "Failed to get valid req cldma%d rxq%d req%d\n",
>+ drv_info->hw_id, rxq->rxqno, rxq->free_idx);
>+ goto err_out;
>+ }
>+
>+ if (req->gpd->rx_gpd.gpd_flags & CLDMA_GPD_FLAG_HWO)
>+ break;
>+
>+ mtk_cldma_rx_skb_adjust(mdev, rxq, req);
>+ do {
>+ err = rxq->rx_done(req->skb, rxq->arg,
>+ atomic_read(&rxq->need_exit) ? true : false);
>+ if (err == -EAGAIN)
>+ usleep_range(1000, 2000);
>+ else
>+ req->skb = NULL;
>+ } while (err == -EAGAIN);
>+
>+ err = mtk_cldma_reload_rx_skb(mdev, rxq, req);
>+ if (err)
>+ goto err_out;
>+
>+ wmb(); /* ensure addr set done before HWO setup done */
>+
>+ idx = rxq->free_idx == 0 ? rxq->nr_gpds - 1 : rxq->free_idx - 1;
>+ pre_req = rxq->req_pool + idx;
>+ pre_req->gpd->rx_gpd.gpd_flags |= CLDMA_GPD_FLAG_HWO;
>+ rxq->free_idx = (rxq->free_idx + 1) % rxq->nr_gpds;
>+ }
>+
>+ err = mtk_cldma_check_rx_req(drv_info, rxq);
>+ if (!err)
>+ goto again;
unclear for me
repeat when 0 is returned
do not repeat when -EAGAIN is returned by mtk_cldma_check_rx_req?
>+ else if (err == -ENXIO)
>+ goto out;
>+
>+ if (!atomic_read(&rxq->need_exit))
>+ drv_ops->cldma_resume_queue(drv_info, DIR_RX, rxq->rxqno);
>+
>+ if (mtk_cldma_rx_check_again(rxq))
>+ goto again;
>+
>+out:
>+ drv_ops->cldma_unmask_intr(drv_info, DIR_RX, rxq->rxqno, QUEUE_XFER_DONE);
>+ drv_ops->cldma_clear_ip_busy(drv_info);
>+err_out:
>+ ;
>+}
>+
>+static int mtk_cldma_alloc_tx_bd(struct cldma_drv_info *drv_info, struct txq *txq,
>+ struct tx_req *req)
>+{
>+ struct bd_dsc *bd_dsc, *last_bd_dsc = NULL;
>+ int i;
>+
>+ req->bd_dsc_pool = devm_kcalloc(drv_info->mdev->dev, txq->nr_bds,
>+ sizeof(*bd_dsc), GFP_KERNEL);
>+ if (!req->bd_dsc_pool)
>+ return -ENOMEM;
>+
>+ for (i = 0; i < txq->nr_bds; i++) {
>+ bd_dsc = req->bd_dsc_pool + i;
>+ bd_dsc->bd = dma_pool_zalloc(drv_info->bd_dma_pool, GFP_KERNEL,
>+ &bd_dsc->bd_dma_addr);
>+ if (!bd_dsc->bd)
>+ return -ENOMEM;
>+ if (!last_bd_dsc) {
>+ req->gpd->tx_gpd.data_buff_ptr_h =
>+ cpu_to_le32((u64)(bd_dsc->bd_dma_addr) >> 32);
>+ req->gpd->tx_gpd.data_buff_ptr_l =
>+ cpu_to_le32(bd_dsc->bd_dma_addr);
>+ } else {
>+ last_bd_dsc->bd->tx_bd.next_bd_ptr_h =
>+ cpu_to_le32((u64)(bd_dsc->bd_dma_addr) >> 32);
>+ last_bd_dsc->bd->tx_bd.next_bd_ptr_l =
>+ cpu_to_le32(bd_dsc->bd_dma_addr);
>+ }
>+ last_bd_dsc = bd_dsc;
>+ }
>+ return 0;
>+}
>+
>+static struct txq *mtk_cldma_txq_alloc(struct cldma_drv_info *drv_info, struct sk_buff *skb)
>+{
>+ struct trb *trb = (struct trb *)skb->cb;
>+ struct cldma_drv_ops *drv_ops;
>+ struct mtk_ctrl_blk *ctrl_blk;
>+ struct mtk_ctrl_trans *trans;
>+ struct mtk_md_dev *mdev;
>+ struct bd_dsc *bd_dsc;
>+ struct tx_req *next;
>+ struct tx_req *req;
>+ u16 tx_frag_size;
>+ struct txq *txq;
>+ int i, j, err;
>+
>+ mdev = drv_info->mdev;
>+ ctrl_blk = mdev->ctrl_blk;
>+ trans = ctrl_blk->ctrl_hw_priv;
>+ drv_ops = drv_info->drv_ops;
>+
>+ txq = devm_kzalloc(mdev->dev, sizeof(*txq), GFP_KERNEL);
>+ if (!txq)
>+ return NULL;
>+
>+ txq->que = radix_tree_lookup(&trans->queue_tbl, trb->channel_id & 0xFFFF);
>+ txq->drv_info = drv_info;
>+ txq->txqno = txq->que->txqno;
>+ txq->nr_gpds = txq->que->tx_nr_gpds;
>+ atomic_set(&txq->req_budget, txq->que->tx_nr_gpds);
>+ txq->is_stopping = false;
>+ tx_frag_size = txq->que->tx_frag_size;
>+ if (txq->que->tx_mtu > tx_frag_size && tx_frag_size)
>+ txq->nr_bds = (txq->que->tx_mtu + tx_frag_size - 1) / tx_frag_size;
>+
>+ txq->req_pool = devm_kcalloc(mdev->dev, txq->nr_gpds, sizeof(*req), GFP_KERNEL);
>+ if (!txq->req_pool)
>+ goto err_free_txq;
>+
>+ for (i = 0; i < txq->nr_gpds; i++) {
>+ req = txq->req_pool + i;
>+ req->mtu = txq->que->tx_mtu;
>+ req->frag_size = tx_frag_size;
>+ req->gpd = dma_pool_zalloc(drv_info->gpd_dma_pool, GFP_KERNEL, &req->gpd_dma_addr);
>+ if (!req->gpd)
>+ goto err_free_req;
>+ if (txq->nr_bds) {
>+ err = mtk_cldma_alloc_tx_bd(drv_info, txq, req);
>+ if (err)
>+ goto err_free_req;
>+ req->gpd->tx_gpd.gpd_flags |= CLDMA_GPD_FLAG_BDP;
>+ }
>+ }
>+
>+ for (i = 0; i < txq->nr_gpds; i++) {
>+ req = txq->req_pool + i;
>+ next = txq->req_pool + ((i + 1) % txq->nr_gpds);
>+ req->gpd->tx_gpd.gpd_flags |= CLDMA_GPD_FLAG_IOC;
>+ req->gpd->tx_gpd.next_gpd_ptr_h = cpu_to_le32((u64)(next->gpd_dma_addr) >> 32);
>+ req->gpd->tx_gpd.next_gpd_ptr_l = cpu_to_le32(next->gpd_dma_addr);
>+ }
>+
>+ INIT_WORK(&txq->tx_done_work, mtk_cldma_tx_done_work);
>+
>+ drv_ops->cldma_stop_queue(drv_info, DIR_TX, txq->txqno);
>+ txq->tx_started = false;
>+ drv_ops->cldma_setup_start_addr(drv_info, DIR_TX, txq->txqno,
>+ txq->req_pool[0].gpd_dma_addr);
>+ drv_ops->cldma_unmask_intr(drv_info, DIR_TX, txq->txqno, QUEUE_ERROR);
>+ drv_ops->cldma_unmask_intr(drv_info, DIR_TX, txq->txqno, QUEUE_XFER_DONE);
>+
>+ drv_info->txq[txq->txqno] = txq;
>+ return txq;
>+
>+err_free_req:
>+ for (i = 0; i < txq->nr_gpds; i++) {
>+ req = txq->req_pool + i;
>+ if (!req->gpd)
>+ break;
>+ if (req->bd_dsc_pool) {
>+ for (j = 0; j < txq->nr_bds; j++) {
>+ bd_dsc = req->bd_dsc_pool + j;
>+ if (!bd_dsc->bd)
>+ break;
>+ dma_pool_free(drv_info->bd_dma_pool, bd_dsc->bd,
>+ bd_dsc->bd_dma_addr);
>+ }
>+ devm_kfree(mdev->dev, req->bd_dsc_pool);
>+ }
>+ dma_pool_free(drv_info->gpd_dma_pool, req->gpd, req->gpd_dma_addr);
>+ }
>+ devm_kfree(mdev->dev, txq->req_pool);
>+err_free_txq:
>+ devm_kfree(mdev->dev, txq);
>+ return NULL;
>+}
>+
>+static int mtk_cldma_txq_free(struct cldma_drv_info *drv_info, u32 txqno)
>+{
>+ struct cldma_drv_ops *drv_ops;
>+ struct mtk_ctrl_blk *ctrl_blk;
>+ struct mtk_ctrl_trans *trans;
>+ struct mtk_md_dev *mdev;
>+ struct bd_dsc *bd_dsc;
>+ struct tx_req *req;
>+ struct txq *txq;
>+ struct trb *trb;
>+ int irq_id;
>+ int i, j;
>+
>+ mdev = drv_info->mdev;
>+ ctrl_blk = mdev->ctrl_blk;
>+ trans = ctrl_blk->ctrl_hw_priv;
>+ drv_ops = drv_info->drv_ops;
>+
>+ txq = drv_info->txq[txqno];
>+ drv_info->txq[txqno] = NULL;
>+ /* stop HW tx transaction */
>+ drv_ops->cldma_stop_queue(drv_info, DIR_TX, txqno);
>+ txq->tx_started = false;
>+
>+ irq_id = mtk_pci_get_virq_id(mdev, drv_info->pci_ext_irq_id);
>+ synchronize_irq(irq_id);
>+ /* flush on-going work */
>+ flush_work(&txq->tx_done_work);
>+ drv_ops->cldma_mask_intr(drv_info, DIR_TX, txqno, QUEUE_XFER_DONE);
>+ drv_ops->cldma_mask_intr(drv_info, DIR_TX, txqno, QUEUE_ERROR);
>+
>+ /* free tx req resource */
>+ for (i = 0; i < txq->nr_gpds; i++) {
>+ req = txq->req_pool + txq->free_idx;
>+ if (req->skb && req->data_len) {
>+ if (!txq->nr_bds)
>+ dma_unmap_single(mdev->dev, req->data_dma_addr,
>+ req->data_len, DMA_TO_DEVICE);
>+ for (j = 0; j < txq->nr_bds; j++) {
>+ bd_dsc = req->bd_dsc_pool + j;
>+ dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
>+ bd_dsc->data_len, DMA_TO_DEVICE);
>+ }
>+ trb = (struct trb *)req->skb->cb;
>+ trb->status = -EPIPE;
>+ trb->trb_complete(req->skb);
>+ }
>+ for (j = 0; j < txq->nr_bds; j++) {
>+ bd_dsc = req->bd_dsc_pool + j;
>+ dma_pool_free(drv_info->bd_dma_pool, bd_dsc->bd,
>+ bd_dsc->bd_dma_addr);
>+ }
>+ if (req->bd_dsc_pool)
>+ devm_kfree(mdev->dev, req->bd_dsc_pool);
>+ dma_pool_free(drv_info->gpd_dma_pool, req->gpd, req->gpd_dma_addr);
>+ txq->free_idx = (txq->free_idx + 1) % txq->nr_gpds;
>+ }
>+
>+ devm_kfree(mdev->dev, txq->req_pool);
>+ devm_kfree(mdev->dev, txq);
>+
>+ return 0;
>+}
>+
>+static int mtk_cldma_alloc_rx_bd(struct cldma_drv_info *drv_info, struct rx_req *req,
>+ int nr_bds)
>+{
>+ struct bd_dsc *bd_dsc, *last_bd_dsc = NULL;
>+ struct sk_buff *tail = NULL;
>+ struct mtk_md_dev *mdev;
>+ u32 left_size;
>+ int err;
>+ int i;
>+
>+ mdev = drv_info->mdev;
>+ left_size = req->mtu;
>+
>+ req->bd_dsc_pool = devm_kcalloc(mdev->dev, nr_bds,
>+ sizeof(*bd_dsc), GFP_KERNEL);
>+ if (!req->bd_dsc_pool)
>+ return -ENOMEM;
>+ for (i = 0; i < nr_bds; i++) {
>+ bd_dsc = req->bd_dsc_pool + i;
>+ bd_dsc->bd = dma_pool_zalloc(drv_info->bd_dma_pool, GFP_KERNEL,
>+ &bd_dsc->bd_dma_addr);
>+ if (!bd_dsc->bd)
>+ return -ENOMEM;
>+
>+ bd_dsc->skb = __dev_alloc_skb(req->frag_size, GFP_KERNEL);
>+ if (!bd_dsc->skb)
>+ return -ENOMEM;
>+ bd_dsc->skb->next = NULL;
>+ bd_dsc->data_dma_addr =
>+ dma_map_single(mdev->dev, bd_dsc->skb->data,
>+ req->frag_size, DMA_FROM_DEVICE);
>+ err = dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr);
>+ if (unlikely(err))
>+ return -ENOMEM;
>+
>+ bd_dsc->bd->rx_bd.data_buff_ptr_h =
>+ cpu_to_le32((u64)(bd_dsc->data_dma_addr) >> 32);
>+ bd_dsc->bd->rx_bd.data_buff_ptr_l =
>+ cpu_to_le32(bd_dsc->data_dma_addr);
>+ bd_dsc->bd->rx_bd.data_allow_len =
>+ cpu_to_le16(min(req->frag_size, left_size));
>+ left_size -= min(req->frag_size, left_size);
>+ if (!last_bd_dsc) {
>+ req->gpd->rx_gpd.data_buff_ptr_h =
>+ cpu_to_le32((u64)(bd_dsc->bd_dma_addr) >> 32);
>+ req->gpd->rx_gpd.data_buff_ptr_l =
>+ cpu_to_le32(bd_dsc->bd_dma_addr);
>+ } else {
>+ last_bd_dsc->bd->rx_bd.next_bd_ptr_h =
>+ cpu_to_le32((u64)(bd_dsc->bd_dma_addr) >> 32);
>+ last_bd_dsc->bd->rx_bd.next_bd_ptr_l =
>+ cpu_to_le32(bd_dsc->bd_dma_addr);
>+ }
>+ last_bd_dsc = bd_dsc;
>+ if (tail) {
>+ tail->next = bd_dsc->skb;
>+ tail = bd_dsc->skb;
>+ continue;
>+ }
>+ if (!req->skb) {
>+ req->skb = bd_dsc->skb;
>+ } else {
>+ skb_shinfo(req->skb)->frag_list = bd_dsc->skb;
>+ tail = bd_dsc->skb;
>+ }
>+ }
>+ last_bd_dsc->bd->rx_bd.bd_flags |= CLDMA_BD_FLAG_EOL;
>+ return 0;
>+}
>+
>+static void mtk_cldma_rxq_alloc_cancel(struct cldma_drv_info *drv_info, struct rx_req *req,
>+ int nr_bds)
>+{
>+ struct mtk_md_dev *mdev;
>+ struct bd_dsc *bd_dsc;
>+ int i;
>+
>+ mdev = drv_info->mdev;
>+
>+ if (nr_bds) {
>+ if (req->skb)
>+ skb_shinfo(req->skb)->frag_list = NULL;
>+ if (req->bd_dsc_pool) {
>+ for (i = 0; i < nr_bds; i++) {
>+ bd_dsc = req->bd_dsc_pool + i;
>+ if (!bd_dsc->bd)
>+ break;
>+ if (bd_dsc->skb) {
>+ if (!dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr))
>+ dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
>+ req->frag_size, DMA_FROM_DEVICE);
>+ bd_dsc->data_dma_addr = 0;
>+ bd_dsc->skb->next = NULL;
>+ dev_kfree_skb_any(bd_dsc->skb);
>+ }
>+ dma_pool_free(drv_info->bd_dma_pool, bd_dsc->bd,
>+ bd_dsc->bd_dma_addr);
>+ }
>+ devm_kfree(mdev->dev, req->bd_dsc_pool);
>+ }
>+ } else {
>+ if (req->skb) {
>+ if (!dma_mapping_error(mdev->dev, req->data_dma_addr))
>+ dma_unmap_single(mdev->dev, req->data_dma_addr,
>+ req->mtu, DMA_FROM_DEVICE);
>+ req->data_dma_addr = 0;
>+ dev_kfree_skb_any(req->skb);
>+ }
>+ }
>+ dma_pool_free(drv_info->gpd_dma_pool, req->gpd, req->gpd_dma_addr);
>+}
>+
>+static struct rxq *mtk_cldma_rxq_alloc(struct cldma_drv_info *drv_info, struct sk_buff *skb)
>+{
>+ struct trb_open_priv *trb_open_priv = (struct trb_open_priv *)skb->data;
>+ struct trb *trb = (struct trb *)skb->cb;
>+ struct cldma_drv_ops *drv_ops;
>+ struct mtk_ctrl_blk *ctrl_blk;
>+ struct mtk_ctrl_trans *trans;
>+ struct mtk_md_dev *mdev;
>+ struct rx_req *next;
>+ struct rx_req *req;
>+ u16 rx_frag_size;
>+ struct rxq *rxq;
>+ int err;
>+ int i;
>+
>+ mdev = drv_info->mdev;
>+ ctrl_blk = mdev->ctrl_blk;
>+ trans = ctrl_blk->ctrl_hw_priv;
>+ drv_ops = drv_info->drv_ops;
>+
>+ rxq = devm_kzalloc(mdev->dev, sizeof(*rxq), GFP_KERNEL);
>+ if (!rxq)
>+ return NULL;
>+
>+ rxq->que = radix_tree_lookup(&trans->queue_tbl, trb->channel_id & 0xFFFF);
>+ if (rxq->que->rx_nr_gpds < MIN_GPD_NUM) {
>+ dev_err((mdev)->dev,
>+ "Failed to alloc cldma%d rxq%d due to gpd number < 2\n",
>+ drv_info->hw_id, rxq->rxqno);
>+ goto err_free_rxq;
>+ }
>+ rxq->drv_info = drv_info;
>+ rxq->rxqno = rxq->que->rxqno;
>+ rxq->nr_gpds = rxq->que->rx_nr_gpds;
>+ rxq->arg = trb->priv;
>+ rxq->rx_done = trb_open_priv->rx_done;
>+ atomic_set(&rxq->need_exit, 0);
>+ rx_frag_size = rxq->que->rx_frag_size;
>+ if (rxq->que->rx_mtu > rx_frag_size && rx_frag_size)
>+ rxq->nr_bds = (rxq->que->rx_mtu + rx_frag_size - 1) / rx_frag_size;
>+
>+ rxq->req_pool = devm_kcalloc(mdev->dev, rxq->nr_gpds, sizeof(*req), GFP_KERNEL);
>+ if (!rxq->req_pool)
>+ goto err_free_rxq;
>+
>+ /* setup rx request */
>+ for (i = 0; i < rxq->nr_gpds; i++) {
>+ req = rxq->req_pool + i;
>+ req->mtu = rxq->que->rx_mtu;
>+ req->frag_size = rx_frag_size;
>+ req->gpd = dma_pool_zalloc(drv_info->gpd_dma_pool, GFP_KERNEL, &req->gpd_dma_addr);
>+ if (!req->gpd)
>+ goto err_free_req;
>+ if (rxq->nr_bds) {
>+ err = mtk_cldma_alloc_rx_bd(drv_info, req, rxq->nr_bds);
>+ if (err)
>+ goto err_free_req;
>+ req->gpd->rx_gpd.gpd_flags |= CLDMA_GPD_FLAG_BDP;
>+ } else {
>+ req->skb = __dev_alloc_skb(req->mtu, GFP_KERNEL);
>+ if (!req->skb)
>+ goto err_free_req;
>+ req->data_dma_addr = dma_map_single(mdev->dev, req->skb->data,
>+ req->mtu, DMA_FROM_DEVICE);
>+ err = dma_mapping_error(mdev->dev, req->data_dma_addr);
>+ if (unlikely(err))
>+ goto err_free_req;
>+ }
>+ }
>+
>+ for (i = 0; i < rxq->nr_gpds; i++) {
>+ req = rxq->req_pool + i;
>+ next = rxq->req_pool + ((i + 1) % rxq->nr_gpds);
>+ req->gpd->rx_gpd.gpd_flags |= CLDMA_GPD_FLAG_IOC;
>+ req->gpd->rx_gpd.data_allow_len = cpu_to_le16(req->mtu);
>+ req->gpd->rx_gpd.next_gpd_ptr_h = cpu_to_le32((u64)(next->gpd_dma_addr) >> 32);
>+ req->gpd->rx_gpd.next_gpd_ptr_l = cpu_to_le32(next->gpd_dma_addr);
>+ if (!rxq->nr_bds) {
>+ req->gpd->rx_gpd.data_buff_ptr_h =
>+ cpu_to_le32((u64)(req->data_dma_addr) >> 32);
>+ req->gpd->rx_gpd.data_buff_ptr_l = cpu_to_le32(req->data_dma_addr);
>+ }
>+ if (i != rxq->nr_gpds - 1)
>+ req->gpd->rx_gpd.gpd_flags |= CLDMA_GPD_FLAG_HWO;
>+ }
>+
>+ INIT_WORK(&rxq->rx_done_work, mtk_cldma_rx_done_work);
>+
>+ drv_info->rxq[rxq->rxqno] = rxq;
>+ drv_ops->cldma_stop_queue(drv_info, DIR_RX, rxq->rxqno);
>+ drv_ops->cldma_setup_start_addr(drv_info, DIR_RX,
>+ rxq->rxqno, rxq->req_pool[0].gpd_dma_addr);
>+ drv_ops->cldma_start_queue(drv_info, DIR_RX, rxq->rxqno);
>+ drv_ops->cldma_unmask_intr(drv_info, DIR_RX, rxq->rxqno, QUEUE_ERROR);
>+ drv_ops->cldma_unmask_intr(drv_info, DIR_RX, rxq->rxqno, QUEUE_XFER_DONE);
>+
>+ return rxq;
>+
>+err_free_req:
>+ for (i = 0; i < rxq->nr_gpds; i++) {
>+ req = rxq->req_pool + i;
>+ if (!req->gpd)
>+ break;
>+ mtk_cldma_rxq_alloc_cancel(drv_info, req, rxq->nr_bds);
>+ }
>+
>+ devm_kfree(mdev->dev, rxq->req_pool);
>+err_free_rxq:
>+ devm_kfree(mdev->dev, rxq);
>+ return NULL;
>+}
>+
>+static int mtk_cldma_rxq_free(struct cldma_drv_info *drv_info, u32 rxqno)
please make it void
>+{
>+ struct cldma_drv_ops *drv_ops;
>+ struct mtk_ctrl_blk *ctrl_blk;
>+ struct mtk_ctrl_trans *trans;
>+ struct mtk_md_dev *mdev;
>+ struct bd_dsc *bd_dsc;
>+ struct rx_req *req;
>+ struct rxq *rxq;
>+ int irq_id;
>+ int i, j;
>+
>+ mdev = drv_info->mdev;
>+ ctrl_blk = mdev->ctrl_blk;
>+ trans = ctrl_blk->ctrl_hw_priv;
>+ drv_ops = drv_info->drv_ops;
>+
>+ rxq = drv_info->rxq[rxqno];
>+ drv_info->rxq[rxqno] = NULL;
>+
>+ /* stop HW rx transaction */
>+ atomic_set(&rxq->need_exit, 1);
>+ drv_ops->cldma_stop_queue(drv_info, DIR_RX, rxqno);
>+
>+ irq_id = mtk_pci_get_virq_id(mdev, drv_info->pci_ext_irq_id);
>+ synchronize_irq(irq_id);
>+ /* flush on-going work */
>+ flush_work(&rxq->rx_done_work);
>+ /* mask L2 RX interrupt again to avoid race condition causing use-after-free issue */
>+ drv_ops->cldma_mask_intr(drv_info, DIR_RX, rxqno, QUEUE_XFER_DONE);
>+ drv_ops->cldma_mask_intr(drv_info, DIR_RX, rxqno, QUEUE_ERROR);
>+
>+ /* free rx req resource */
>+ for (i = 0; i < rxq->nr_gpds; i++) {
>+ req = rxq->req_pool + rxq->free_idx;
>+ if (!(req->gpd->rx_gpd.gpd_flags & CLDMA_GPD_FLAG_HWO) &&
>+ le16_to_cpu(req->gpd->rx_gpd.data_recv_len)) {
>+ mtk_cldma_rx_skb_adjust(mdev, rxq, req);
>+ rxq->rx_done(req->skb, rxq->arg, true);
>+ req->skb = NULL;
>+ }
>+ if (req->skb) {
>+ if (rxq->nr_bds) {
>+ skb_shinfo(req->skb)->frag_list = NULL;
>+ } else {
>+ if (req->data_dma_addr)
>+ dma_unmap_single(mdev->dev, req->data_dma_addr,
>+ req->mtu, DMA_FROM_DEVICE);
>+ dev_kfree_skb_any(req->skb);
>+ }
>+ }
>+ for (j = 0; j < rxq->nr_bds; j++) {
>+ bd_dsc = req->bd_dsc_pool + j;
>+ if (bd_dsc->skb) {
>+ if (bd_dsc->data_dma_addr)
>+ dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
>+ req->frag_size, DMA_FROM_DEVICE);
>+ bd_dsc->skb->next = NULL;
>+ dev_kfree_skb_any(bd_dsc->skb);
>+ }
>+ dma_pool_free(drv_info->bd_dma_pool,
>+ bd_dsc->bd, bd_dsc->bd_dma_addr);
>+ }
>+ if (req->bd_dsc_pool)
>+ devm_kfree(mdev->dev, req->bd_dsc_pool);
>+ dma_pool_free(drv_info->gpd_dma_pool, req->gpd, req->gpd_dma_addr);
>+ rxq->free_idx = (rxq->free_idx + 1) % rxq->nr_gpds;
>+ }
>+
>+ devm_kfree(mdev->dev, rxq->req_pool);
>+ devm_kfree(mdev->dev, rxq);
>+
>+ return 0;
>+}
>+
>+static int mtk_cldma_start_xfer(struct cldma_drv_info *drv_info, u32 qno)
>+{
>+ struct cldma_drv_ops *drv_ops;
>+ struct txq *txq;
>+ int ret = 0;
>+ u32 val;
>+
>+ txq = drv_info->txq[qno];
>+ drv_ops = drv_info->drv_ops;
>+
>+ val = drv_ops->cldma_get_tx_start_addr(drv_info, qno);
>+ if (unlikely(!val)) {
>+ drv_ops->cldma_drv_init(drv_info);
>+ txq = drv_info->txq[qno];
>+ drv_ops->cldma_setup_start_addr(drv_info, DIR_TX, qno,
>+ txq->req_pool[txq->free_idx].gpd_dma_addr);
>+ drv_ops->cldma_start_queue(drv_info, DIR_TX, qno);
>+ txq->tx_started = true;
>+ } else if (unlikely(val == LINK_ERROR_VAL)) {
>+ ret = -EIO;
>+ } else {
>+ if (unlikely(!txq->tx_started)) {
>+ drv_ops->cldma_start_queue(drv_info, DIR_TX, qno);
>+ txq->tx_started = true;
>+ } else {
>+ drv_ops->cldma_resume_queue(drv_info, DIR_TX, qno);
>+ }
>+ }
>+
>+ return ret;
just return 0, no need to zeroinit ret
>+}
>+
>+int mtk_cldma_init(struct mtk_ctrl_trans *trans)
>+{
>+ struct cldma_dev *cd;
>+
>+ cd = devm_kzalloc(trans->mdev->dev, sizeof(*cd), GFP_KERNEL);
>+ if (!cd)
>+ return -ENOMEM;
>+
>+ cd->trans = trans;
>+ trans->dev = cd;
>+
>+ return 0;
>+}
>+
>+int mtk_cldma_exit(struct mtk_ctrl_trans *trans)
void?
>+{
>+ if (!trans->dev)
>+ return 0;
>+
>+ devm_kfree(trans->mdev->dev, trans->dev);
>+ trans->dev = NULL;
>+
>+ return 0;
>+}
>+
>+static int mtk_cldma_open(struct cldma_dev *cd, struct sk_buff *skb)
>+{
>+ struct trb_open_priv *trb_open_priv = (struct trb_open_priv *)skb->data;
>+ struct trb *trb = (struct trb *)skb->cb;
>+ struct cldma_drv_info *drv_info;
>+ struct queue_info *que;
>+ struct txq *txq;
>+ struct rxq *rxq;
>+ int err = 0;
please be consistent within the series
either you name 'ret' either 'err'
>+
>+ que = radix_tree_lookup(&cd->trans->queue_tbl, trb->channel_id & 0xFFFF);
>+ drv_info = cd->cldma_drv_info[que->hif_id];
>+ if (!drv_info) {
>+ err = -EIO;
>+ goto out;
>+ }
>+
>+ if (que->tx_mtu == 0 || que->rx_mtu == 0) {
>+ dev_err((cd->trans->mdev)->dev,
>+ "Failed to enable cldma%d txq%d rxq%d due to wrong mtu\n",
>+ drv_info->hw_id, que->txqno, que->rxqno);
>+ err = -EINVAL;
>+ goto out;
>+ }
>+
>+ trb_open_priv->tx_mtu = que->tx_mtu;
>+ trb_open_priv->rx_mtu = que->rx_mtu;
>+ trb_open_priv->tx_frag_size = que->tx_frag_size;
>+ trb_open_priv->rx_frag_size = que->rx_frag_size;
>+
>+ if (drv_info->txq[que->txqno] || drv_info->rxq[que->rxqno]) {
>+ err = -EBUSY;
>+ goto out;
>+ }
>+
>+ txq = mtk_cldma_txq_alloc(drv_info, skb);
>+ if (!txq) {
>+ err = -ENOMEM;
>+ goto out;
>+ }
>+
>+ rxq = mtk_cldma_rxq_alloc(drv_info, skb);
>+ if (!rxq) {
>+ err = -ENOMEM;
>+ mtk_cldma_txq_free(drv_info, txq->txqno);
>+ goto out;
>+ }
>+
>+out:
>+ trb->status = err;
>+ trb->trb_complete(skb);
>+
>+ return err;
>+}
>+
>+static int mtk_cldma_tx(struct cldma_dev *cd, struct sk_buff *skb)
>+{
>+ struct trb *trb = (struct trb *)skb->cb;
>+ struct cldma_drv_info *drv_info;
>+ struct mtk_md_dev *mdev;
>+ struct queue_info *que;
>+ struct txq *txq;
>+ int err = 0;
no need to zeroinit
>+
>+ que = radix_tree_lookup(&cd->trans->queue_tbl, trb->channel_id & 0xFFFF);
>+ drv_info = cd->cldma_drv_info[que->hif_id];
>+ if (unlikely(!drv_info))
>+ return -EPIPE;
>+ txq = drv_info->txq[que->txqno];
>+ if (unlikely(!txq) || txq->is_stopping)
>+ return -EPIPE;
>+
>+ mdev = drv_info->mdev;
>+
>+ err = mtk_cldma_start_xfer(drv_info, que->txqno);
>+ if (unlikely(err))
>+ dev_err((mdev)->dev, "Failed to trigger cldma tx\n");
>+
>+ return err;
>+}
>+
>+static int mtk_cldma_close(struct cldma_dev *cd, struct sk_buff *skb)
>+{
>+ struct trb *trb = (struct trb *)skb->cb;
>+ struct cldma_drv_info *drv_info;
>+ struct queue_info *que;
>+
>+ que = radix_tree_lookup(&cd->trans->queue_tbl, trb->channel_id & 0xFFFF);
>+ drv_info = cd->cldma_drv_info[que->hif_id];
>+ if (unlikely(!drv_info))
>+ return -EPIPE;
>+
>+ if (drv_info->txq[que->txqno])
>+ mtk_cldma_txq_free(drv_info, que->txqno);
>+ if (drv_info->rxq[que->rxqno])
>+ mtk_cldma_rxq_free(drv_info, que->rxqno);
>+
>+ trb->status = 0;
>+ trb->trb_complete(skb);
>+
>+ return 0;
>+}
>+
>+static int mtk_cldma_txbuf_set(struct cldma_drv_info *drv_info, struct sk_buff *skb,
>+ struct tx_req *req, int nr_bds)
>+{
>+ struct sk_buff *curr_skb, *next_skb;
>+ struct mtk_md_dev *mdev;
>+ struct bd_dsc *bd_dsc;
>+ int err;
>+ int i;
>+
>+ mdev = drv_info->mdev;
>+
>+ if (nr_bds) {
>+ bd_dsc = req->bd_dsc_pool;
>+ curr_skb = skb;
>+ for (i = 0; i < nr_bds && curr_skb; i++) {
>+ bd_dsc = req->bd_dsc_pool + i;
>+ if (req->bd_dsc_pool == bd_dsc) {
>+ bd_dsc->data_len = skb->len - skb->data_len;
>+ next_skb = skb_shinfo(skb)->frag_list;
>+ } else {
>+ bd_dsc->data_len = curr_skb->len;
>+ next_skb = curr_skb->next;
>+ }
>+ bd_dsc->data_dma_addr = dma_map_single(mdev->dev, curr_skb->data,
>+ bd_dsc->data_len, DMA_TO_DEVICE);
>+ err = dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr);
>+ if (unlikely(err))
>+ goto err_unmap_buffer;
>+
>+ bd_dsc->bd->tx_bd.data_buff_ptr_h =
>+ cpu_to_le32((u64)(bd_dsc->data_dma_addr) >> 32);
>+ bd_dsc->bd->tx_bd.data_buff_ptr_l = cpu_to_le32(bd_dsc->data_dma_addr);
>+ bd_dsc->bd->tx_bd.data_buffer_len = cpu_to_le16(bd_dsc->data_len);
>+ curr_skb = next_skb;
>+ }
>+ bd_dsc->bd->tx_bd.bd_flags = CLDMA_BD_FLAG_EOL;
>+ } else {
>+ req->data_dma_addr = dma_map_single(mdev->dev, skb->data,
>+ skb->len, DMA_TO_DEVICE);
>+ err = dma_mapping_error(mdev->dev, req->data_dma_addr);
>+ if (unlikely(err)) {
>+ req->data_dma_addr = 0;
>+ goto err_exit;
>+ }
>+
>+ req->gpd->tx_gpd.data_buff_ptr_h = cpu_to_le32((u64)(req->data_dma_addr) >> 32);
>+ req->gpd->tx_gpd.data_buff_ptr_l = cpu_to_le32(req->data_dma_addr);
>+ }
>+
>+ return 0;
>+
>+err_unmap_buffer:
>+ for (i = 0; i < nr_bds; i++) {
>+ bd_dsc = req->bd_dsc_pool + i;
>+ if (dma_mapping_error(mdev->dev, bd_dsc->data_dma_addr)) {
>+ bd_dsc->data_dma_addr = 0;
>+ break;
>+ }
>+ dma_unmap_single(mdev->dev, bd_dsc->data_dma_addr,
>+ bd_dsc->data_len, DMA_TO_DEVICE);
>+ bd_dsc->data_dma_addr = 0;
>+ }
>+err_exit:
>+ dev_err((mdev)->dev, "Failed to map dma! error:%d\n", err);
>+ return -EAGAIN;
>+}
>+
>+int mtk_cldma_submit_tx(void *dev, struct sk_buff *skb)
>+{
>+ struct trb *trb = (struct trb *)skb->cb;
>+ struct cldma_drv_info *drv_info;
>+ struct cldma_dev *cd = dev;
>+ struct queue_info *que;
>+ struct tx_req *req;
>+ struct txq *txq;
>+ int ret;
>+
>+ que = radix_tree_lookup(&cd->trans->queue_tbl, trb->channel_id & 0xFFFF);
>+ drv_info = cd->cldma_drv_info[que->hif_id];
>+ if (unlikely(!drv_info)) {
>+ ret = -EINVAL;
>+ goto out;
why cannot return directly?
>+ }
>+
>+ txq = drv_info->txq[que->txqno];
>+ if (unlikely(!txq)) {
>+ ret = -EINVAL;
>+ goto out;
>+ }
>+
>+ if (!atomic_read(&txq->req_budget)) {
>+ ret = -EAGAIN;
>+ goto out;
>+ }
>+
>+ req = txq->req_pool + txq->wr_idx;
>+ req->gpd->tx_gpd.debug_id = 0x01;
>+ ret = mtk_cldma_txbuf_set(drv_info, skb, req, txq->nr_bds);
>+ if (ret)
>+ goto out;
>+ req->gpd->tx_gpd.data_buff_len = cpu_to_le16(skb->len);
>+
>+ wmb(); /* ensure data msg set done before HWO setup */
>+
>+ req->gpd->tx_gpd.gpd_flags |= CLDMA_GPD_FLAG_HWO;
>+
>+ wmb(); /* ensure HWO setup done before req msg setup */
>+
>+ req->data_len = skb->len;
>+ req->skb = skb;
>+ req->data_vm_addr = skb->data;
>+ txq->wr_idx = (txq->wr_idx + 1) % txq->nr_gpds;
>+ atomic_dec(&txq->req_budget);
>+
>+out:
>+ return ret;
>+}
>+
>+int mtk_cldma_get_tx_budget(void *dev, enum mtk_hif_id hif_id, u32 qno)
>+{
>+ struct cldma_drv_info *drv_info;
>+ struct cldma_dev *cd = dev;
>+ struct txq *txq;
>+
>+ if (unlikely(hif_id >= NR_CLDMA || qno >= HW_QUE_NUM || !cd))
>+ return -EINVAL;
>+
>+ drv_info = cd->cldma_drv_info[hif_id];
>+ if (!drv_info)
>+ return -EINVAL;
>+ txq = drv_info->txq[qno];
>+ if (!txq)
>+ return -EINVAL;
>+ return atomic_read(&txq->req_budget);
>+}
>+
>+static int (*trb_act_tbl[TRB_CMD_MAX])(struct cldma_dev *cd, struct sk_buff *skb) = {
>+ [TRB_CMD_ENABLE] = mtk_cldma_open,
>+ [TRB_CMD_TX] = mtk_cldma_tx,
>+ [TRB_CMD_DISABLE] = mtk_cldma_close,
>+};
>+
>+int mtk_cldma_trb_process(void *dev, struct sk_buff *skb)
>+{
>+ struct cldma_dev *cd;
>+ struct trb *trb;
>+
>+ if (!dev || !skb)
>+ return -EINVAL;
>+
>+ cd = (struct cldma_dev *)dev;
>+ trb = (struct trb *)skb->cb;
>+
>+ if (!(trb->cmd > TRB_CMD_MIN && trb->cmd < TRB_CMD_STOP))
>+ return -EINVAL;
>+
>+ return trb_act_tbl[trb->cmd](cd, skb);
>+}
>+
>+int mtk_cldma_check_ch_cfg(void *dev, struct queue_info *que)
>+{
>+ struct cldma_drv_info *drv_info;
>+ struct cldma_dev *cd = dev;
>+ struct mtk_md_dev *mdev;
>+ struct txq *txq;
>+ struct rxq *rxq;
>+
>+ mdev = cd->trans->mdev;
>+ drv_info = cd->cldma_drv_info[que->hif_id];
>+
>+ if (unlikely(!drv_info)) {
what's te benefit of using unlikely here?
>+ dev_err((mdev)->dev, "CLDMA%d has not been initialized\n",
>+ mtk_cldma_hw_id_tbl[que->hif_id]);
>+ return -EINVAL;
>+ }
>+
>+ txq = drv_info->txq[que->txqno];
>+ rxq = drv_info->rxq[que->rxqno];
>+ if (unlikely(!txq || !rxq)) {
>+ dev_err((mdev)->dev,
>+ "CLDMA%d txq%d rxq%d has not been enabled\n",
>+ mtk_cldma_hw_id_tbl[que->hif_id], que->txqno, que->rxqno);
>+ return -EINVAL;
>+ }
>+
>+ if (que->tx_mtu != txq->que->tx_mtu || que->rx_mtu != rxq->que->rx_mtu) {
>+ dev_err((mdev)->dev,
>+ "Channel:%08x tx_mtu:%08x rx_mtu:%08x do not match ch cfg\n",
>+ que->tx_chl, que->tx_mtu, que->rx_mtu);
>+ return -EINVAL;
>+ }
>+
>+ return 0;
>+}
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma.h
>new file mode 100644
>index 000000000000..246d28d3d798
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma.h
>@@ -0,0 +1,170 @@
>+/* SPDX-License-Identifier: GPL-2.0-only
>+ *
>+ * Copyright (c) 2022, MediaTek Inc.
>+ */
>+
>+#ifndef __MTK_CLDMA_H__
>+#define __MTK_CLDMA_H__
>+
>+#include <linux/dma-mapping.h>
>+#include <linux/dmapool.h>
>+#include <linux/interrupt.h>
>+#include <linux/list.h>
>+#include <linux/spinlock.h>
>+#include <linux/types.h>
>+
>+#include "mtk_ctrl_plane.h"
>+#include "mtk_trans_ctrl.h"
>+
>+struct mtk_fsm_param;
>+
>+#define TXQ(N) (N)
>+#define RXQ(N) (N)
>+
>+#define CLDMA_GPD_FLAG_HWO BIT(0)
>+#define CLDMA_GPD_FLAG_BDP BIT(1)
>+#define CLDMA_GPD_FLAG_BPS BIT(2)
>+#define CLDMA_GPD_FLAG_IOC BIT(7)
>+#define CLDMA_BD_FLAG_EOL BIT(0)
>+
>+union gpd {
>+ struct {
>+ u8 gpd_flags;
>+ u8 non_used1;
>+ __le16 data_allow_len;
>+ __le32 next_gpd_ptr_h;
>+ __le32 next_gpd_ptr_l;
>+ __le32 data_buff_ptr_h;
>+ __le32 data_buff_ptr_l;
>+ __le16 data_recv_len;
>+ u8 non_used2;
>+ u8 debug_id;
>+ } rx_gpd;
>+
>+ struct {
>+ u8 gpd_flags;
>+ u8 non_used1;
>+ u8 non_used2;
>+ u8 debug_id;
>+ __le32 next_gpd_ptr_h;
>+ __le32 next_gpd_ptr_l;
>+ __le32 data_buff_ptr_h;
>+ __le32 data_buff_ptr_l;
>+ __le16 data_buff_len;
>+ __le16 non_used3;
>+ } tx_gpd;
>+} __packed;
>+
>+union bd {
>+ struct {
>+ u8 bd_flags;
>+ u8 non_used1;
>+ __le16 data_allow_len;
>+ __le32 next_bd_ptr_h;
>+ __le32 next_bd_ptr_l;
>+ __le32 data_buff_ptr_h;
>+ __le32 data_buff_ptr_l;
>+ __le16 data_recv_len;
>+ __le16 non_used2;
>+ } rx_bd;
>+
>+ struct {
>+ u8 bd_flags;
>+ u8 non_used1;
>+ __le16 non_used2;
>+ __le32 next_bd_ptr_h;
>+ __le32 next_bd_ptr_l;
>+ __le32 data_buff_ptr_h;
>+ __le32 data_buff_ptr_l;
>+ __le16 data_buffer_len;
>+ u8 extension_len;
>+ u8 non_used3;
>+ } tx_bd;
>+} __packed;
>+
>+struct bd_dsc {
>+ union bd *bd;
>+ struct sk_buff *skb;
>+ dma_addr_t bd_dma_addr;
>+ dma_addr_t data_dma_addr;
>+ size_t data_len;
>+};
>+
>+struct rx_req {
>+ union gpd *gpd;
>+ u32 mtu;
>+ struct sk_buff *skb;
>+ size_t data_len;
>+ dma_addr_t gpd_dma_addr;
>+ dma_addr_t data_dma_addr;
>+ u32 frag_size;
>+ struct bd_dsc *bd_dsc_pool;
>+};
>+
>+struct rxq {
>+ struct cldma_drv_info *drv_info;
>+ u32 rxqno;
>+ struct queue_info *que;
>+ struct work_struct rx_done_work;
>+ struct rx_req *req_pool;
>+ u32 nr_gpds;
>+ u32 free_idx;
>+ unsigned short rx_done_cnt;
>+ void *arg;
>+ int (*rx_done)(struct sk_buff *skb, void *priv, bool force_recv);
>+ u32 nr_bds;
>+ atomic_t need_exit;
>+};
>+
>+struct tx_req {
>+ union gpd *gpd;
>+ u32 mtu;
>+ void *data_vm_addr;
>+ size_t data_len;
>+ dma_addr_t data_dma_addr;
>+ dma_addr_t gpd_dma_addr;
>+ struct sk_buff *skb;
>+ int (*trb_complete)(struct sk_buff *skb);
>+ u32 frag_size;
>+ struct bd_dsc *bd_dsc_pool;
>+};
>+
>+struct txq {
>+ struct cldma_drv_info *drv_info;
>+ u32 txqno;
>+ struct queue_info *que;
>+ struct work_struct tx_done_work;
>+ struct tx_req *req_pool;
>+ u32 nr_gpds;
>+ atomic_t req_budget;
>+ u32 wr_idx;
>+ u32 free_idx;
>+ bool tx_started;
>+ bool is_stopping;
>+ unsigned short tx_done_cnt;
>+ u32 nr_bds;
>+};
>+
>+struct cldma_dev {
>+ struct cldma_drv_info *cldma_drv_info[NR_CLDMA];
>+ struct mtk_ctrl_trans *trans;
>+};
>+
>+struct cldma_drv_info_desc {
>+ u32 hw_ver;
>+ struct cldma_drv_ops *drv_ops;
>+ struct cldma_hw_regs *hw_regs;
>+};
>+
>+int mtk_cldma_init(struct mtk_ctrl_trans *trans);
>+int mtk_cldma_exit(struct mtk_ctrl_trans *trans);
>+int mtk_cldma_submit_tx(void *dev, struct sk_buff *skb);
>+int mtk_cldma_get_tx_budget(void *dev, enum mtk_hif_id hif_id, u32 qno);
>+int mtk_cldma_trb_process(void *dev, struct sk_buff *skb);
>+void mtk_cldma_fsm_state_listener(struct mtk_fsm_param *param, struct mtk_ctrl_trans *trans);
>+int mtk_cldma_check_ch_cfg(void *dev, struct queue_info *que);
>+
>+#define drv_ops_name(NAME) cldma_drv_ops_##NAME
>+#define cldma_regs_name(NAME) mtk_cldma_regs_##NAME
>+
>+#endif
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c
>new file mode 100644
>index 000000000000..d5eb2ab9a425
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c
>@@ -0,0 +1,373 @@
>+// SPDX-License-Identifier: GPL-2.0-only
>+/*
>+ * Copyright (c) 2023, MediaTek Inc.
>+ */
>+
>+#include <linux/delay.h>
>+#include <linux/device.h>
>+#include <linux/dma-mapping.h>
>+#include <linux/dmapool.h>
>+#include <linux/err.h>
>+#include <linux/interrupt.h>
>+#include <linux/kdev_t.h>
>+#include <linux/kernel.h>
>+#include <linux/kthread.h>
>+#include <linux/list.h>
>+#include <linux/module.h>
>+#include <linux/mutex.h>
>+#include <linux/netdevice.h>
>+#include <linux/sched.h>
>+#include <linux/skbuff.h>
>+#include <linux/slab.h>
>+#include <linux/timer.h>
>+#include <linux/wait.h>
>+#include <linux/workqueue.h>
>+
>+#include "mtk_cldma_drv.h"
>+#include "mtk_dev.h"
>+#include "mtk_pci.h"
>+#include "mtk_pci_reg.h"
>+
>+#define WAIT_QUEUE_STOP (70)
>+
>+void mtk_cldma_drv_init(struct cldma_drv_info *drv_info)
>+{
>+ struct cldma_hw_regs *hw_regs;
>+ struct mtk_md_dev *mdev;
>+ int base;
>+ u32 val;
>+
>+ mdev = drv_info->mdev;
>+ base = drv_info->base_addr;
>+ hw_regs = drv_info->hw_regs;
>+
>+ /* set CLDMA to 64 bit mode GPD */
>+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_ul_cfg);
>+ val = (val & (~(0x7 << 5))) | ((0x4) << 5);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ul_cfg, val);
>+
>+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_so_cfg);
>+ val = (val & (~(0x7 << 10))) | ((0x4) << 10) | (1 << 2);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_so_cfg, val);
>+
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_rx_work_to_reg_mask_set, ALLQ);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ip_busy_to_pcie_mask_set,
>+ ALLQ << 16);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ip_busy_to_pcie_mask_clr,
>+ ALLQ << 24);
>+
>+ /* enable interrupt to PCIe */
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_int_mask, 0);
>+
>+ /* disable illegal memory check */
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ul_dummy_0, 1);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_so_dummy_0, 1);
>+}
>+
>+void mtk_cldma_setup_start_addr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, dma_addr_t addr)
>+{
>+ struct cldma_hw_regs *hw_regs;
>+ unsigned int addr_l;
>+ unsigned int addr_h;
>+ int base;
>+
>+ hw_regs = drv_info->hw_regs;
>+ base = drv_info->base_addr;
>+
>+ if (dir == DIR_TX) {
>+ addr_l = base + hw_regs->reg_cldma_ul_start_addrl_0 + qno * HW_QUEUE_NUM;
>+ addr_h = base + hw_regs->reg_cldma_ul_start_addrh_0 + qno * HW_QUEUE_NUM;
>+ } else {
>+ addr_l = base + hw_regs->reg_cldma_so_start_addrl_0 + qno * HW_QUEUE_NUM;
>+ addr_h = base + hw_regs->reg_cldma_so_start_addrh_0 + qno * HW_QUEUE_NUM;
>+ }
>+
>+ mtk_pci_write32(drv_info->mdev, addr_l, (u32)addr);
>+ mtk_pci_write32(drv_info->mdev, addr_h, (u32)((u64)addr >> 32));
>+}
>+
>+void mtk_cldma_mask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, enum mtk_intr_type type)
>+{
>+ struct cldma_hw_regs *hw_regs;
>+ int base;
>+ u32 addr;
>+ u32 val;
>+
>+ hw_regs = drv_info->hw_regs;
>+ base = drv_info->base_addr;
>+
>+ if (dir == DIR_TX)
>+ addr = base + hw_regs->reg_cldma_l2timsr0;
>+ else
>+ addr = base + hw_regs->reg_cldma_l2rimsr0;
>+
>+ if (qno == ALLQ)
>+ val = qno << type;
>+ else
>+ val = BIT(qno) << type;
>+
>+ mtk_pci_write32(drv_info->mdev, addr, val);
>+}
>+
>+void mtk_cldma_unmask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, enum mtk_intr_type type)
>+{
>+ struct cldma_hw_regs *hw_regs;
>+ int base;
>+ u32 addr;
>+ u32 val;
>+
>+ hw_regs = drv_info->hw_regs;
>+ base = drv_info->base_addr;
>+
>+ if (dir == DIR_TX)
>+ addr = base + hw_regs->reg_cldma_l2timcr0;
>+ else
>+ addr = base + hw_regs->reg_cldma_l2rimcr0;
>+
>+ if (qno == ALLQ)
>+ val = qno << type;
>+ else
>+ val = BIT(qno) << type;
>+
>+ mtk_pci_write32(drv_info->mdev, addr, val);
>+}
>+
>+void mtk_cldma_clr_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, enum mtk_intr_type type)
>+{
>+ struct cldma_hw_regs *hw_regs;
>+ struct mtk_md_dev *mdev;
>+ int base;
>+ u32 addr;
>+ u32 val;
>+
>+ hw_regs = drv_info->hw_regs;
>+ base = drv_info->base_addr;
>+ mdev = drv_info->mdev;
>+
>+ if (type == QUEUE_ERROR) {
>+ if (dir == DIR_TX) {
>+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3tisar0);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3tisar0, val);
>+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3tisar1);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3tisar1, val);
>+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3tisar2);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3tisar2, val);
>+ } else {
>+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3risar0);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3risar0, val);
>+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l3risar1);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l3risar1, val);
>+ }
>+ }
>+
>+ if (dir == DIR_TX)
>+ addr = base + hw_regs->reg_cldma_l2tisar0;
>+ else
>+ addr = base + hw_regs->reg_cldma_l2risar0;
>+
>+ if (qno == ALLQ)
>+ val = qno << type;
>+ else
>+ val = BIT(qno) << type;
>+
>+ mtk_pci_write32(mdev, addr, val);
>+ val = mtk_pci_read32(mdev, addr);
>+}
>+
>+u32 mtk_cldma_check_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, enum mtk_intr_type type)
>+{
>+ struct cldma_hw_regs *hw_regs;
>+ int base;
>+ u32 addr;
>+ u32 val;
>+ u32 sta;
please squash
>+
>+ hw_regs = drv_info->hw_regs;
>+ base = drv_info->base_addr;
>+
>+ if (dir == DIR_TX)
>+ addr = base + hw_regs->reg_cldma_l2tisar0;
>+ else
>+ addr = base + hw_regs->reg_cldma_l2risar0;
>+
>+ val = mtk_pci_read32(drv_info->mdev, addr);
>+ if (val == LINK_ERROR_VAL)
>+ sta = val;
>+ else if (qno == ALLQ)
>+ sta = (val >> type) & 0xFF;
>+ else
>+ sta = (val >> type) & BIT(qno);
>+
>+ return sta;
>+}
>+
>+void mtk_cldma_start_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)
>+{
>+ struct cldma_hw_regs *hw_regs;
>+ u32 val = BIT(qno);
>+ int base;
>+ u32 addr;
>+
>+ hw_regs = drv_info->hw_regs;
>+ base = drv_info->base_addr;
>+
>+ if (dir == DIR_TX)
>+ addr = base + hw_regs->reg_cldma_ul_start_cmd;
>+ else
>+ addr = base + hw_regs->reg_cldma_so_start_cmd;
>+
>+ mtk_pci_write32(drv_info->mdev, addr, val);
>+}
>+
>+void mtk_cldma_resume_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)
>+{
>+ struct cldma_hw_regs *hw_regs;
>+ u32 val = BIT(qno);
>+ int base;
>+ u32 addr;
>+
>+ hw_regs = drv_info->hw_regs;
>+ base = drv_info->base_addr;
>+
>+ if (dir == DIR_TX)
>+ addr = base + hw_regs->reg_cldma_ul_resume_cmd;
>+ else
>+ addr = base + hw_regs->reg_cldma_so_resume_cmd;
>+
>+ mtk_pci_write32(drv_info->mdev, addr, val);
>+}
>+
>+u32 mtk_cldma_queue_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)
>+{
>+ struct cldma_hw_regs *hw_regs;
>+ int base;
>+ u32 addr;
>+ u32 val;
>+
>+ hw_regs = drv_info->hw_regs;
>+ base = drv_info->base_addr;
>+
>+ if (dir == DIR_TX)
>+ addr = base + hw_regs->reg_cldma_ul_status;
>+ else
>+ addr = base + hw_regs->reg_cldma_so_status;
>+
>+ val = mtk_pci_read32(drv_info->mdev, addr);
>+
>+ if (qno == ALLQ || val == LINK_ERROR_VAL)
>+ return val;
>+
>+ return val & BIT(qno);
>+}
>+
>+u32 mtk_cldma_stop_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno)
>+{
>+ u32 val = (qno == ALLQ) ? qno : BIT(qno);
>+ struct cldma_hw_regs *hw_regs;
>+ unsigned int active;
>+ int cnt = 0;
>+ int base;
>+ u32 addr;
>+
>+ hw_regs = drv_info->hw_regs;
>+ base = drv_info->base_addr;
>+
>+ if (dir == DIR_TX)
>+ addr = base + hw_regs->reg_cldma_ul_stop_cmd;
>+ else
>+ addr = base + hw_regs->reg_cldma_so_stop_cmd;
>+
>+ mtk_pci_write32(drv_info->mdev, addr, val);
>+
>+ do {
>+ active = drv_info->drv_ops->cldma_queue_status(drv_info, dir, qno);
>+ if (active == LINK_ERROR_VAL || !active)
>+ break;
>+ usleep_range(WAIT_QUEUE_STOP, 2 * WAIT_QUEUE_STOP);
>+ } while (++cnt < 10);
>+
>+ return active;
>+}
>+
>+void mtk_cldma_clear_ip_busy(struct cldma_drv_info *drv_info)
>+{
>+ mtk_pci_write32(drv_info->mdev, drv_info->base_addr +
>+ drv_info->hw_regs->reg_cldma_ip_busy, 0x01);
>+}
>+
>+void mtk_cldma_get_intr_status(struct cldma_drv_info *drv_info, u32 *tx_sta, u32 *rx_sta)
>+{
>+ struct cldma_hw_regs *hw_regs;
>+ struct mtk_md_dev *mdev;
>+ u32 tx_mask, rx_mask;
>+ int base;
>+
>+ mdev = drv_info->mdev;
>+ base = drv_info->base_addr;
>+ hw_regs = drv_info->hw_regs;
>+
>+ *tx_sta = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l2tisar0);
>+ tx_mask = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l2timr0);
>+ *rx_sta = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l2risar0);
>+ rx_mask = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_l2rimr0);
>+
>+ *tx_sta = (*tx_sta) & (~tx_mask);
>+ *rx_sta = (*rx_sta) & (~rx_mask);
>+
>+ if (*tx_sta) {
>+ /* TX XFER_DONE and QUEUE_ERROR mask */
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l2timsr0, *tx_sta);
>+ /* TX XFER_DONE clear */
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l2tisar0,
>+ (*tx_sta) & (0xFF << QUEUE_XFER_DONE));
>+ }
>+
>+ if (*rx_sta) {
>+ /* RX XFER_DONE and QUEUE_ERROR mask */
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l2rimsr0, *rx_sta);
>+ /* RX XFER_DONE clear */
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_l2risar0,
>+ (*rx_sta) & (0xFF << QUEUE_XFER_DONE));
>+ }
>+}
>+
>+u32 mtk_cldma_get_tx_start_addr(struct cldma_drv_info *drv_info, u32 qno)
>+{
>+ u32 addr, val;
>+
>+ addr = drv_info->base_addr + drv_info->hw_regs->reg_cldma_ul_start_addrl_0 +
>+ qno * HW_QUEUE_NUM;
>+ val = mtk_pci_read32(drv_info->mdev, addr);
>+
>+ return val;
>+}
>+
>+u64 mtk_cldma_get_rx_curr_addr(struct cldma_drv_info *drv_info, u32 qno)
>+{
>+ struct cldma_hw_regs *hw_regs;
>+ u32 curr_addr_h, curr_addr_l;
>+ struct mtk_md_dev *mdev;
>+ u64 curr_addr;
>+ int base;
>+ u64 addr;
>+
>+ hw_regs = drv_info->hw_regs;
>+ base = drv_info->base_addr;
>+ mdev = drv_info->mdev;
>+
>+ addr = base + hw_regs->reg_cldma_so_current_addrh_0 +
>+ (u64)qno * HW_QUEUE_NUM;
>+ curr_addr_h = mtk_pci_read32(mdev, addr);
>+ addr = base + hw_regs->reg_cldma_so_current_addrl_0 +
>+ (u64)qno * HW_QUEUE_NUM;
>+ curr_addr_l = mtk_pci_read32(mdev, addr);
>+ curr_addr = ((u64)curr_addr_h << 32) | curr_addr_l;
>+ if (curr_addr_h == LINK_ERROR_VAL && curr_addr_l == LINK_ERROR_VAL)
>+ curr_addr = 0;
>+ return curr_addr;
>+}
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h
>new file mode 100644
>index 000000000000..8763c23abf54
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h
>@@ -0,0 +1,177 @@
>+/* SPDX-License-Identifier: GPL-2.0-only
>+ *
>+ * Copyright (c) 2023, MediaTek Inc.
>+ */
>+
>+#ifndef __MTK_CLDMA_DRV_H__
>+#define __MTK_CLDMA_DRV_H__
>+
>+#define HW_QUEUE_NUM (8)
>+#define ALLQ (0xFF)
>+#define LINK_ERROR_VAL (0xFFFFFFFF)
>+#define CLDMA0_HW_ID (0)
>+#define CLDMA1_HW_ID (1)
>+#define CLDMA4_HW_ID (4)
>+
>+struct cldma_hw_regs {
>+ u8 cldma_rx_skb_pool_max_size;
>+ u8 cldma_rx_skb_reload_threshold;
>+ u8 tq_err_int_offset;
>+ u8 tq_active_start_err_int_offset;
>+ u8 rq_err_int_offset;
>+ u8 rq_active_start_err_int_offset;
>+ u16 reg_cldma_so_cfg;
>+ u16 reg_cldma_so_start_addrl_0;
>+ u16 reg_cldma_so_start_addrh_0;
>+ u16 reg_cldma_so_current_addrl_0;
>+ u16 reg_cldma_so_current_addrh_0;
>+ u16 reg_cldma_so_status;
>+ u16 reg_cldma_debug_id_en;
>+ u16 reg_cldma_so_last_update_addrl_0;
>+ u16 reg_cldma_so_last_update_addrh_0;
>+ u16 reg_cldma_l2rimr0;
>+ u16 reg_cldma_l2rimr1;
>+ u16 reg_cldma_l2rimcr0;
>+ u16 reg_cldma_l2rimcr1;
>+ u16 reg_cldma_l2rimsr0;
>+ u16 reg_cldma_l2rimsr1;
>+ u16 reg_cldma_int_mask;
>+ u16 reg_cldma4_int_mask;
>+ u16 reg_cldma_slp_mem_ctl;
>+ u16 reg_cldma_busy_mask;
>+ u16 reg_cldma_ip_busy_to_pcie_mask;
>+ u16 reg_cldma_ip_busy_to_pcie_mask_set;
>+ u16 reg_cldma_ip_busy_to_pcie_mask_clr;
>+ u16 reg_cldma_ip_busy_to_ap_mask;
>+ u16 reg_cldma_ip_busy_to_ap_mask_set;
>+ u16 reg_cldma_ip_busy_to_ap_mask_clr;
>+ u16 reg_cldma_ip_busy_to_md_mask_set;
>+ u16 reg_cldma_rx_work_to_reg_mask_set;
>+ u16 reg_infra_rst4_set;
>+ u16 reg_infra_rst4_clr;
>+ u16 reg_infra_rst2_set;
>+ u16 reg_infra_rst2_clr;
>+ u16 reg_infra_rst0_set;
>+ u16 reg_infra_rst0_clr;
>+ u32 tq_err_int_bitmask;
>+ u32 tq_active_start_err_int_bitmask;
>+ u32 rq_err_int_bitmask;
>+ u32 cldma0_base_addr;
>+ u32 cldma1_base_addr;
>+ u32 cldma4_base_addr;
>+ u32 rq_active_start_err_int_bitmask;
>+ u32 reg_cldma_ul_start_addrl_0;
>+ u32 reg_cldma_ul_start_addrh_0;
>+ u32 reg_cldma_ul_current_addrl_0;
>+ u32 reg_cldma_ul_current_addrh_0;
>+ u32 reg_cldma_ul_status;
>+ u32 reg_cldma_ul_start_cmd;
>+ u32 reg_cldma_ul_resume_cmd;
>+ u32 reg_cldma_ul_stop_cmd;
>+ u32 reg_cldma_ul_error;
>+ u32 reg_cldma_ul_cfg;
>+ u32 reg_cldma_ul_dummy_0;
>+ u32 reg_cldma_so_error;
>+ u32 reg_cldma_so_start_cmd;
>+ u32 reg_cldma_so_resume_cmd;
>+ u32 reg_cldma_so_stop_cmd;
>+ u32 reg_cldma_so_dummy_0;
>+ u32 reg_cldma_l2tisar0;
>+ u32 reg_cldma_l2tisar1;
>+ u32 reg_cldma_l2timr0;
>+ u32 reg_cldma_l2timr1;
>+ u32 reg_cldma_l2timcr0;
>+ u32 reg_cldma_l2timcr1;
>+ u32 reg_cldma_l2timsr0;
>+ u32 reg_cldma_l2timsr1;
>+ u32 reg_cldma_l2risar0;
>+ u32 reg_cldma_l2risar1;
>+ u32 reg_cldma_l3tisar0;
>+ u32 reg_cldma_l3tisar1;
>+ u32 reg_cldma_l3tisar2;
>+ u32 reg_cldma_l3risar0;
>+ u32 reg_cldma_l3risar1;
>+ u32 reg_cldma_ip_busy;
>+};
>+
>+enum mtk_ip_busy_src {
>+ IP_BUSY_TXDONE = 0,
>+ IP_BUSY_TXEMPTY = 8,
>+ IP_BUSY_TXACTIVE = 16,
>+ IP_BUSY_RXDONE = 24
>+};
>+
>+enum mtk_intr_type {
>+ QUEUE_XFER_DONE = 0,
>+ QUEUE_EMPTY = 8,
>+ QUEUE_ERROR = 16,
>+ QUEUE_ACTIVE_START = 24,
>+ INVALID_TYPE
>+};
>+
>+enum mtk_tx_rx {
>+ DIR_TX,
>+ DIR_RX,
>+ DIR_MAX
>+};
>+
>+struct cldma_drv_info {
>+ int hif_id;
>+ int hw_id;
>+ int base_addr;
>+ int pci_ext_irq_id;
>+ struct mtk_md_dev *mdev;
>+ struct cldma_dev *cd;
>+ struct txq *txq[HW_QUEUE_NUM];
>+ struct rxq *rxq[HW_QUEUE_NUM];
>+ struct dma_pool *gpd_dma_pool;
>+ struct dma_pool *bd_dma_pool;
>+ struct workqueue_struct *wq;
>+ struct cldma_hw_regs *hw_regs;
>+ struct cldma_drv_ops *drv_ops;
>+};
>+
>+struct cldma_drv_ops {
>+ void (*cldma_drv_init)(struct cldma_drv_info *drv_info);
>+ void (*cldma_drv_reset)(struct cldma_drv_info *drv_info);
>+ void (*cldma_setup_start_addr)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, dma_addr_t addr);
>+ void (*cldma_mask_intr)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, enum mtk_intr_type type);
>+ void (*cldma_unmask_intr)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, enum mtk_intr_type type);
>+ void (*cldma_clr_intr_status)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, enum mtk_intr_type type);
>+ u32 (*cldma_check_intr_status)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, enum mtk_intr_type type);
>+ void (*cldma_start_queue)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
>+ void (*cldma_resume_queue)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
>+ u32 (*cldma_queue_status)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
>+ u32 (*cldma_stop_queue)(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
>+ void (*cldma_clear_ip_busy)(struct cldma_drv_info *drv_info);
>+ void (*cldma_get_intr_status)(struct cldma_drv_info *drv_info, u32 *tx_sta, u32 *rx_sta);
>+ u32 (*cldma_get_tx_start_addr)(struct cldma_drv_info *drv_info, u32 qno);
>+ u64 (*cldma_get_rx_curr_addr)(struct cldma_drv_info *drv_info, u32 qno);
>+};
>+
>+void mtk_cldma_drv_init(struct cldma_drv_info *drv_info);
>+void mtk_cldma_setup_start_addr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, dma_addr_t addr);
>+void mtk_cldma_mask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, enum mtk_intr_type type);
>+void mtk_cldma_unmask_intr(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, enum mtk_intr_type type);
>+void mtk_cldma_clr_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, enum mtk_intr_type type);
>+u32 mtk_cldma_check_intr_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir,
>+ u32 qno, enum mtk_intr_type type);
>+void mtk_cldma_start_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
>+void mtk_cldma_resume_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
>+u32 mtk_cldma_queue_status(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
>+u32 mtk_cldma_stop_queue(struct cldma_drv_info *drv_info, enum mtk_tx_rx dir, u32 qno);
>+void mtk_cldma_clear_ip_busy(struct cldma_drv_info *drv_info);
>+void mtk_cldma_get_intr_status(struct cldma_drv_info *drv_info, u32 *tx_sta, u32 *rx_sta);
>+u32 mtk_cldma_get_tx_start_addr(struct cldma_drv_info *drv_info, u32 qno);
>+u64 mtk_cldma_get_rx_curr_addr(struct cldma_drv_info *drv_info, u32 qno);
>+
>+#endif
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c
>new file mode 100644
>index 000000000000..240a9f58f658
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c
>@@ -0,0 +1,182 @@
>+// SPDX-License-Identifier: GPL-2.0-only
>+/*
>+ * Copyright (c) 2023, MediaTek Inc.
>+ */
>+
>+#include <linux/delay.h>
>+#include <linux/device.h>
>+#include <linux/dma-mapping.h>
>+#include <linux/dmapool.h>
>+#include <linux/err.h>
>+#include <linux/interrupt.h>
>+#include <linux/kdev_t.h>
>+#include <linux/kernel.h>
>+#include <linux/kthread.h>
>+#include <linux/list.h>
>+#include <linux/module.h>
>+#include <linux/mutex.h>
>+#include <linux/netdevice.h>
>+#include <linux/sched.h>
>+#include <linux/skbuff.h>
>+#include <linux/slab.h>
>+#include <linux/timer.h>
>+#include <linux/wait.h>
>+#include <linux/workqueue.h>
>+
>+#include "mtk_cldma_drv.h"
>+#include "mtk_cldma_drv_m9xx.h"
>+#include "mtk_dev.h"
>+#include "mtk_pci.h"
>+#include "mtk_pci_reg.h"
>+#include "mtk_trans_ctrl.h"
>+
>+struct cldma_hw_regs mtk_cldma_regs_m9xx = {
>+ .cldma0_base_addr = CLDMA0_BASE_ADDR,
>+ .cldma1_base_addr = CLDMA1_BASE_ADDR,
>+ .cldma4_base_addr = CLDMA4_BASE_ADDR,
>+ .cldma_rx_skb_pool_max_size = CLDMA_RX_SKB_POOL_MAX_SIZE,
>+ .cldma_rx_skb_reload_threshold = CLDMA_RX_SKB_RELOAD_THRESHOLD,
>+ .tq_err_int_offset = TQ_ERR_INT_OFFSET,
>+ .tq_err_int_bitmask = TQ_ERR_INT_BITMASK,
>+ .tq_active_start_err_int_offset = TQ_ACTIVE_START_ERR_INT_OFFSET,
>+ .tq_active_start_err_int_bitmask = TQ_ACTIVE_START_ERR_INT_BITMASK,
>+ .rq_err_int_offset = RQ_ERR_INT_OFFSET,
>+ .rq_err_int_bitmask = RQ_ERR_INT_BITMASK,
>+ .rq_active_start_err_int_offset = RQ_ACTIVE_START_ERR_INT_OFFSET,
>+ .rq_active_start_err_int_bitmask = RQ_ACTIVE_START_ERR_INT_BITMASK,
>+ .reg_cldma_ul_start_addrl_0 = REG_CLDMA_UL_START_ADDRL_0,
>+ .reg_cldma_ul_start_addrh_0 = REG_CLDMA_UL_START_ADDRH_0,
>+ .reg_cldma_ul_current_addrl_0 = REG_CLDMA_UL_CURRENT_ADDRL_0,
>+ .reg_cldma_ul_current_addrh_0 = REG_CLDMA_UL_CURRENT_ADDRH_0,
>+ .reg_cldma_ul_status = REG_CLDMA_UL_STATUS,
>+ .reg_cldma_ul_start_cmd = REG_CLDMA_UL_START_CMD,
>+ .reg_cldma_ul_resume_cmd = REG_CLDMA_UL_RESUME_CMD,
>+ .reg_cldma_ul_stop_cmd = REG_CLDMA_UL_STOP_CMD,
>+ .reg_cldma_ul_error = REG_CLDMA_UL_ERROR,
>+ .reg_cldma_ul_cfg = REG_CLDMA_UL_CFG,
>+ .reg_cldma_ul_dummy_0 = REG_CLDMA_UL_DUMMY_0,
>+ .reg_cldma_so_error = REG_CLDMA_SO_ERROR,
>+ .reg_cldma_so_start_cmd = REG_CLDMA_SO_START_CMD,
>+ .reg_cldma_so_resume_cmd = REG_CLDMA_SO_RESUME_CMD,
>+ .reg_cldma_so_stop_cmd = REG_CLDMA_SO_STOP_CMD,
>+ .reg_cldma_so_dummy_0 = REG_CLDMA_SO_DUMMY_0,
>+ .reg_cldma_so_cfg = REG_CLDMA_SO_CFG,
>+ .reg_cldma_so_start_addrl_0 = REG_CLDMA_SO_START_ADDRL_0,
>+ .reg_cldma_so_start_addrh_0 = REG_CLDMA_SO_START_ADDRH_0,
>+ .reg_cldma_so_current_addrl_0 = REG_CLDMA_SO_CUR_ADDRL_0,
>+ .reg_cldma_so_current_addrh_0 = REG_CLDMA_SO_CUR_ADDRH_0,
>+ .reg_cldma_so_status = REG_CLDMA_SO_STATUS,
>+ .reg_cldma_debug_id_en = REG_CLDMA_DEBUG_ID_EN,
>+ .reg_cldma_so_last_update_addrl_0 = REG_CLDMA_SO_LAST_UPDATE_ADDRL_0,
>+ .reg_cldma_so_last_update_addrh_0 = REG_CLDMA_SO_LAST_UPDATE_ADDRH_0,
>+ .reg_cldma_l2tisar0 = REG_CLDMA_L2TISAR0,
>+ .reg_cldma_l2tisar1 = REG_CLDMA_L2TISAR1,
>+ .reg_cldma_l2timr0 = REG_CLDMA_L2TIMR0,
>+ .reg_cldma_l2timr1 = REG_CLDMA_L2TIMR1,
>+ .reg_cldma_l2timcr0 = REG_CLDMA_L2TIMCR0,
>+ .reg_cldma_l2timcr1 = REG_CLDMA_L2TIMCR1,
>+ .reg_cldma_l2timsr0 = REG_CLDMA_L2TIMSR0,
>+ .reg_cldma_l2timsr1 = REG_CLDMA_L2TIMSR1,
>+ .reg_cldma_l3tisar0 = REG_CLDMA_L3TISAR0,
>+ .reg_cldma_l3tisar1 = REG_CLDMA_L3TISAR1,
>+ .reg_cldma_l3tisar2 = REG_CLDMA_L3TISAR2,
>+ .reg_cldma_l2risar0 = REG_CLDMA_L2RISAR0,
>+ .reg_cldma_l2risar1 = REG_CLDMA_L2RISAR1,
>+ .reg_cldma_l2rimr0 = REG_CLDMA_L2RIMR0,
>+ .reg_cldma_l2rimr1 = REG_CLDMA_L2RIMR1,
>+ .reg_cldma_l2rimcr0 = REG_CLDMA_L2RIMCR0,
>+ .reg_cldma_l2rimcr1 = REG_CLDMA_L2RIMCR1,
>+ .reg_cldma_l2rimsr0 = REG_CLDMA_L2RIMSR0,
>+ .reg_cldma_l2rimsr1 = REG_CLDMA_L2RIMSR1,
>+ .reg_cldma_l3risar0 = REG_CLDMA_L3RISAR0,
>+ .reg_cldma_l3risar1 = REG_CLDMA_L3RISAR1,
>+ .reg_cldma_ip_busy = REG_CLDMA_IP_BUSY,
>+ .reg_cldma_int_mask = REG_CLDMA_INT_EAP_USIP_MASK,
>+ .reg_cldma4_int_mask = REG_CLDMA_INT_WF_MASK,
>+ .reg_cldma_ip_busy_to_pcie_mask = REG_CLDMA_IP_BUSY_TO_PCIE_MASK,
>+ .reg_cldma_ip_busy_to_pcie_mask_set = REG_CLDMA_IP_BUSY_TO_PCIE_MASK_SET,
>+ .reg_cldma_ip_busy_to_pcie_mask_clr = REG_CLDMA_IP_BUSY_TO_PCIE_MASK_CLR,
>+ .reg_cldma_ip_busy_to_ap_mask = REG_CLDMA_IP_BUSY_TO_AP_MASK,
>+ .reg_cldma_ip_busy_to_ap_mask_set = REG_CLDMA_IP_BUSY_TO_AP_MASK_SET,
>+ .reg_cldma_ip_busy_to_ap_mask_clr = REG_CLDMA_IP_BUSY_TO_AP_MASK_CLR,
>+ .reg_cldma_ip_busy_to_md_mask_set = REG_CLDMA_IP_BUSY_TO_MD_MASK_SET,
>+ .reg_cldma_rx_work_to_reg_mask_set = REG_CLDMA_RX_WORK_TO_REG_MASK_SET,
>+ .reg_infra_rst0_set = REG_INFRA_RST0_SET,
>+ .reg_infra_rst0_clr = REG_INFRA_RST0_CLR,
>+};
>+
>+static void mtk_cldma_drv_init_m9xx(struct cldma_drv_info *drv_info)
>+{
>+ struct cldma_hw_regs *hw_regs;
>+ struct mtk_md_dev *mdev;
>+ int base;
>+ u32 val;
>+
>+ mdev = drv_info->mdev;
>+ base = drv_info->base_addr;
>+ hw_regs = drv_info->hw_regs;
>+
>+ /* set CLDMA to 64 bit mode GPD */
>+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_ul_cfg);
>+
>+ val = (val & (~(0x7 << 5))) | ((0x4) << 5);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ul_cfg, val);
>+
>+ val = mtk_pci_read32(mdev, base + hw_regs->reg_cldma_so_cfg);
>+ val = (val & (~(0x7 << 10))) | ((0x4) << 10) | (1 << 2);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_so_cfg, val);
>+
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_rx_work_to_reg_mask_set, ALLQ);
>+
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ip_busy_to_pcie_mask_set,
>+ ALLQ << 16);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ip_busy_to_pcie_mask_clr,
>+ ALLQ << 24);
>+
>+ /* enable interrupt to PCIe */
>+ if (drv_info->hw_id == CLDMA4_HW_ID)
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma4_int_mask, 0);
>+ else
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_int_mask, 0);
>+
>+ /* disable illegal memory check */
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ul_dummy_0, 1);
>+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_so_dummy_0, 1);
>+}
>+
>+static void mtk_cldma_drv_reset_m9xx(struct cldma_drv_info *drv_info)
>+{
>+ struct cldma_hw_regs *hw_regs;
>+ struct mtk_md_dev *mdev;
>+ u32 val;
>+
>+ mdev = drv_info->mdev;
>+ hw_regs = drv_info->hw_regs;
>+
>+ val = mtk_pci_read32(mdev, REG_DEV_INFRA_BASE + hw_regs->reg_infra_rst0_set);
>+
>+ val |= 1 << (REG_CLDMA0_RST_SET_BIT + drv_info->hw_id);
>+ mtk_pci_write32(mdev, REG_DEV_INFRA_BASE + hw_regs->reg_infra_rst0_set, val);
>+ udelay(1);
>+ val = mtk_pci_read32(mdev, REG_DEV_INFRA_BASE + hw_regs->reg_infra_rst0_clr);
>+ val |= 1 << (REG_CLDMA0_RST_CLR_BIT + drv_info->hw_id);
>+ mtk_pci_write32(mdev, REG_DEV_INFRA_BASE + hw_regs->reg_infra_rst0_clr, val);
>+}
>+
>+struct cldma_drv_ops cldma_drv_ops_m9xx = {
>+ .cldma_drv_init = mtk_cldma_drv_init_m9xx,
>+ .cldma_drv_reset = mtk_cldma_drv_reset_m9xx,
>+ .cldma_setup_start_addr = mtk_cldma_setup_start_addr,
>+ .cldma_mask_intr = mtk_cldma_mask_intr,
>+ .cldma_unmask_intr = mtk_cldma_unmask_intr,
>+ .cldma_clr_intr_status = mtk_cldma_clr_intr_status,
>+ .cldma_check_intr_status = mtk_cldma_check_intr_status,
>+ .cldma_start_queue = mtk_cldma_start_queue,
>+ .cldma_resume_queue = mtk_cldma_resume_queue,
>+ .cldma_queue_status = mtk_cldma_queue_status,
>+ .cldma_stop_queue = mtk_cldma_stop_queue,
>+ .cldma_clear_ip_busy = mtk_cldma_clear_ip_busy,
>+ .cldma_get_intr_status = mtk_cldma_get_intr_status,
>+ .cldma_get_tx_start_addr = mtk_cldma_get_tx_start_addr,
>+ .cldma_get_rx_curr_addr = mtk_cldma_get_rx_curr_addr,
>+};
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h
>new file mode 100644
>index 000000000000..2c63c43ff065
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h
>@@ -0,0 +1,103 @@
>+/* SPDX-License-Identifier: GPL-2.0-only
>+ *
>+ * Copyright (c) 2023, MediaTek Inc.
>+ */
>+
>+#ifndef __MTK_CLDMA_DRV_M9XX_H__
>+#define __MTK_CLDMA_DRV_M9XX_H__
>+
>+#define CLDMA0_BASE_ADDR (0x1021C000)
>+#define CLDMA1_BASE_ADDR (0x1021E000)
>+#define CLDMA4_BASE_ADDR (0x10224000)
>+
>+#define CLDMA_RX_SKB_POOL_MAX_SIZE (64)
>+#define CLDMA_RX_SKB_RELOAD_THRESHOLD (16)
>+
>+/* L2TISAR0 */
>+#define TQ_ERR_INT_OFFSET (16)
>+#define TQ_ERR_INT_BITMASK (0x00FF0000)
>+#define TQ_ACTIVE_START_ERR_INT_OFFSET (24)
>+#define TQ_ACTIVE_START_ERR_INT_BITMASK (0xFF000000)
>+
>+/* L2RISAR0 */
>+#define RQ_ERR_INT_OFFSET (16)
>+#define RQ_ERR_INT_BITMASK (0x00FF0000)
>+#define RQ_ACTIVE_START_ERR_INT_OFFSET (24)
>+#define RQ_ACTIVE_START_ERR_INT_BITMASK (0xFF000000)
>+
>+/* CLDMA IN(Tx) */
>+#define REG_CLDMA_UL_START_ADDRL_0 (0x0004)
>+#define REG_CLDMA_UL_START_ADDRH_0 (0x0008)
>+#define REG_CLDMA_UL_CURRENT_ADDRL_0 (0x0044)
>+#define REG_CLDMA_UL_CURRENT_ADDRH_0 (0x0048)
>+#define REG_CLDMA_UL_STATUS (0x0084)
>+#define REG_CLDMA_UL_START_CMD (0x0088)
>+#define REG_CLDMA_UL_RESUME_CMD (0x008C)
>+#define REG_CLDMA_UL_STOP_CMD (0x0090)
>+#define REG_CLDMA_UL_ERROR (0x0094)
>+#define REG_CLDMA_UL_CFG (0x0098)
>+#define REG_CLDMA_UL_DUMMY_0 (0x009C)
>+
>+/* CLDMA OUT(Rx) */
>+#define REG_CLDMA_SO_ERROR (0x0400 + 0x0100)
>+#define REG_CLDMA_SO_START_CMD (0x0400 + 0x01BC)
>+#define REG_CLDMA_SO_RESUME_CMD (0x0400 + 0x01C0)
>+#define REG_CLDMA_SO_STOP_CMD (0x0400 + 0x01C4)
>+#define REG_CLDMA_SO_DUMMY_0 (0x0400 + 0x0108)
>+#define REG_CLDMA_SO_CFG (0x0400 + 0x0004)
>+#define REG_CLDMA_SO_START_ADDRL_0 (0x0400 + 0x0078)
>+#define REG_CLDMA_SO_START_ADDRH_0 (0x0400 + 0x007C)
>+#define REG_CLDMA_SO_CUR_ADDRL_0 (0x0400 + 0x00B8)
>+#define REG_CLDMA_SO_CUR_ADDRH_0 (0x0400 + 0x00BC)
>+#define REG_CLDMA_SO_STATUS (0x0400 + 0x00F8)
>+#define REG_CLDMA_DEBUG_ID_EN (0x0400 + 0x00FC)
>+#define REG_CLDMA_SO_LAST_UPDATE_ADDRL_0 (0x0400 + 0x01C8)
>+#define REG_CLDMA_SO_LAST_UPDATE_ADDRH_0 (0x0400 + 0x01CC)
>+
>+/* CLDMA MISC */
>+#define REG_CLDMA_L2TISAR0 (0x0800 + 0x0010)
>+#define REG_CLDMA_L2TISAR1 (0x0800 + 0x0014)
>+#define REG_CLDMA_L2TIMR0 (0x0800 + 0x0018)
>+#define REG_CLDMA_L2TIMR1 (0x0800 + 0x001C)
>+#define REG_CLDMA_L2TIMCR0 (0x0800 + 0x0020)
>+#define REG_CLDMA_L2TIMCR1 (0x0800 + 0x0024)
>+#define REG_CLDMA_L2TIMSR0 (0x0800 + 0x0028)
>+#define REG_CLDMA_L2TIMSR1 (0x0800 + 0x002C)
>+#define REG_CLDMA_L3TISAR0 (0x0800 + 0x0030)
>+#define REG_CLDMA_L3TISAR1 (0x0800 + 0x0034)
>+#define REG_CLDMA_L2RISAR0 (0x0800 + 0x0050)
>+#define REG_CLDMA_L2RISAR1 (0x0800 + 0x0054)
>+#define REG_CLDMA_L3RISAR0 (0x0800 + 0x0070)
>+#define REG_CLDMA_L3RISAR1 (0x0800 + 0x0074)
>+#define REG_CLDMA_IP_BUSY (0x0800 + 0x00B4)
>+#define REG_CLDMA_L3TISAR2 (0x0800 + 0x00C0)
>+
>+#define REG_CLDMA_L2RIMR0 (0x0800 + 0x00E8)
>+#define REG_CLDMA_L2RIMR1 (0x0800 + 0x00EC)
>+#define REG_CLDMA_L2RIMCR0 (0x0800 + 0x00F0)
>+#define REG_CLDMA_L2RIMCR1 (0x0800 + 0x00F4)
>+#define REG_CLDMA_L2RIMSR0 (0x0800 + 0x00F8)
>+#define REG_CLDMA_L2RIMSR1 (0x0800 + 0x00FC)
>+
>+#define REG_CLDMA_INT_EAP_USIP_MASK (0x0800 + 0x011C)
>+#define REG_CLDMA_INT_WF_MASK (0x0800 + 0x0120)
>+#define REG_CLDMA_RQ1_GPD_DONE_CNT (0x0800 + 0x0174)
>+#define REG_CLDMA_TQ1_GPD_DONE_CNT (0x0800 + 0x0184)
>+
>+#define REG_CLDMA_IP_BUSY_TO_PCIE_MASK (0x0800 + 0x0194)
>+#define REG_CLDMA_IP_BUSY_TO_PCIE_MASK_SET (0x0800 + 0x0198)
>+#define REG_CLDMA_IP_BUSY_TO_PCIE_MASK_CLR (0x0800 + 0x019C)
>+
>+#define REG_CLDMA_IP_BUSY_TO_AP_MASK (0x0800 + 0x0200)
>+#define REG_CLDMA_IP_BUSY_TO_AP_MASK_SET (0x0800 + 0x0204)
>+#define REG_CLDMA_IP_BUSY_TO_AP_MASK_CLR (0x0800 + 0x0208)
>+#define REG_CLDMA_IP_BUSY_TO_MD_MASK_SET (0x0800 + 0x0210)
>+#define REG_CLDMA_RX_WORK_TO_REG_MASK_SET (0x0800 + 0x021C)
>+
>+/* CLDMA RESET */
>+#define REG_INFRA_RST0_SET (0x120)
>+#define REG_INFRA_RST0_CLR (0x124)
>+#define REG_CLDMA0_RST_SET_BIT (8)
>+#define REG_CLDMA0_RST_CLR_BIT (8)
>+
>+#endif
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
>new file mode 100644
>index 000000000000..bf3f87723167
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
>@@ -0,0 +1,23 @@
>+// SPDX-License-Identifier: GPL-2.0-only
>+/*
>+ * Copyright (c) 2022, MediaTek Inc.
>+ */
>+
>+#include "mtk_cldma.h"
>+#include "mtk_trans_ctrl.h"
>+
>+#define TRB_SRV_NUM (1)
>+
>+static const int mtk_srv_cfg_m9xx[NR_CLDMA][HW_QUE_NUM] = {
>+ {0},
>+ {0},
>+};
>+
>+static const struct queue_info mtk_queue_info_m9xx[] = {
>+};
>+
>+struct mtk_ctrl_info mtk_ctrl_info_m9xx = {
>+ .queue_info = (struct queue_info *)mtk_queue_info_m9xx,
>+ .queue_info_num = ARRAY_SIZE(mtk_queue_info_m9xx),
>+ .trb_srv_num = TRB_SRV_NUM,
>+};
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.c b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
>index 518c32d55643..d604c9cb06ea 100644
>--- a/drivers/net/wwan/t9xx/pcie/mtk_pci.c
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
>@@ -760,6 +760,28 @@ static void mtk_pci_free_irq(struct mtk_md_dev *mdev)
> pci_free_irq_vectors(pdev);
> }
>
>+static int mtk_pci_dev_init(struct mtk_md_dev *mdev)
>+{
>+ int ret;
>+
>+ ret = mtk_trans_ctrl_init(mdev);
>+ if (ret) {
>+ dev_err(mdev->dev, "Failed to initialize control plane: %d\n", ret);
>+ return ret;
>+ }
>+
>+ return 0;
>+}
>+
>+static void mtk_pci_dev_exit(struct mtk_md_dev *mdev)
>+{
>+ mtk_trans_ctrl_exit(mdev);
>+}
>+
>+static int mtk_pci_dev_start(struct mtk_md_dev *mdev)
>+{
>+ return 0;
>+}
> static const struct mtk_dev_ops pci_hw_ops = {
> .get_dev_state = mtk_pci_get_dev_state,
> .ack_dev_state = mtk_pci_ack_dev_state,
>@@ -834,6 +856,12 @@ static int mtk_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> if (ret)
> goto free_mhccif;
>
>+ ret = mtk_pci_dev_init(mdev);
>+ if (ret) {
>+ dev_err((mdev)->dev, "Failed to init dev.\n");
>+ goto free_irq;
>+ }
>+
> pci_set_master(pdev);
> mtk_pci_unmask_irq(mdev, priv->mhccif_irq_id);
>
>@@ -850,10 +878,20 @@ static int mtk_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> goto clear_master;
> }
>
>+ ret = mtk_pci_dev_start(mdev);
>+ if (ret) {
>+ dev_err((mdev)->dev, "Failed to start dev.\n");
>+ goto free_saved_state;
>+ }
>+
> return 0;
>
>+free_saved_state:
>+ pci_load_and_free_saved_state(pdev, &priv->saved_state);
> clear_master:
> pci_clear_master(pdev);
>+ mtk_pci_dev_exit(mdev);
>+free_irq:
> mtk_pci_free_irq(mdev);
> free_mhccif:
> mtk_mhccif_exit(mdev);
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h b/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h
>index d033dbf4b0af..0f16e6954397 100644
>--- a/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h
>@@ -21,6 +21,7 @@
> #define REG_IMASK_HOST_MSIX_SET_GRP0_0 0x3000
> #define REG_IMASK_HOST_MSIX_CLR_GRP0_0 0x3080
> #define REG_IMASK_HOST_MSIX_GRP0_0 0x3100
>+#define REG_DEV_INFRA_BASE 0x10001000
>
> /* mhccif registers */
> #define MHCCIF_RC2EP_SW_BSY 0x4
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
>new file mode 100644
>index 000000000000..7fad64d214aa
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
>@@ -0,0 +1,569 @@
>+// SPDX-License-Identifier: GPL-2.0-only
>+/*
>+ * Copyright (c) 2022, MediaTek Inc.
>+ */
>+
>+#include <linux/device.h>
>+#include <linux/freezer.h>
>+#include <linux/hashtable.h>
>+#include <linux/kthread.h>
>+#include <linux/list.h>
>+#include <linux/nospec.h>
>+#include <linux/sched.h>
>+#include <linux/wait.h>
>+
>+#include "mtk_cldma.h"
>+#include "mtk_ctrl_plane.h"
>+#include "mtk_dev.h"
>+#include "mtk_pci.h"
>+#include "mtk_trans_ctrl.h"
>+
>+#define MTK_DFLT_PORT_NAME_LEN (20)
>+extern struct mtk_ctrl_info ctrl_info_name(m9xx);
>+
>+static struct mtk_ctrl_info_desc mtk_ctrl_info_tbl[] = {
>+ {2304, &ctrl_info_name(m9xx)},
>+ {0, NULL},
>+};
>+
>+#define RX_CH_ID_SHIFT 16
>+#define PORT_MTU_MASK 0xFFFF
>+#define QUEUE_CHL_MASK 0xFFFF
>+
>+static bool mtk_queue_list_is_full(struct mtk_ctrl_trans *trans, struct queue_info *que)
>+{
>+ return trans->trans_list[que->hif_id].skb_list[que->txqno].qlen >= SKB_LIST_MAX_LEN;
>+}
>+
>+static bool mtk_ctrl_chs_is_busy_or_empty(struct trb_srv *srv)
>+{
>+ struct srv_que *srv_que;
>+ int i;
>+
>+ for (i = 0; i < NR_CLDMA; i++)
>+ list_for_each_entry(srv_que, &srv->srv_q_list[i], list)
>+ if (!skb_queue_empty(&srv->trans->trans_list[i].skb_list[srv_que->qno]) &&
>+ mtk_cldma_get_tx_budget(srv->trans->dev, i, srv_que->qno))
>+ return false;
>+
>+ return true;
>+}
>+
>+static void mtk_ctrl_ch_flush(struct sk_buff_head *skb_list)
>+{
>+ struct sk_buff *skb;
>+ struct trb *trb;
>+
>+ while (!skb_queue_empty(skb_list)) {
>+ skb = skb_dequeue(skb_list);
>+ trb = (struct trb *)skb->cb;
>+ trb->status = -EIO;
>+ trb->trb_complete(skb);
>+ }
>+}
>+
>+static void mtk_ctrl_chs_flush(struct trb_srv *srv)
>+{
>+ struct srv_que *srv_que;
>+ int i;
>+
>+ for (i = 0; i < NR_CLDMA; i++)
>+ list_for_each_entry(srv_que, &srv->srv_q_list[i], list)
>+ mtk_ctrl_ch_flush(&srv->trans->trans_list[i].skb_list[srv_que->qno]);
>+}
>+
>+static int mtk_ch_status_check(struct mtk_ctrl_trans *trans, struct sk_buff *skb)
>+{
>+ struct trb *trb = (struct trb *)skb->cb;
>+ struct trb_open_priv *trb_open_priv;
>+ struct queue_info *que;
>+ int ret = 0;
>+
>+ que = radix_tree_lookup(&trans->queue_tbl, trb->channel_id & QUEUE_CHL_MASK);
>+
>+ switch (trb->cmd) {
>+ case TRB_CMD_ENABLE:
>+ trb_open_priv = (struct trb_open_priv *)skb->data;
>+ trb_open_priv->log_rg_offset = que->log_rg_offset;
>+ trans->usr_cnt[que->hif_id][que->txqno]++;
>+ if (trans->usr_cnt[que->hif_id][que->txqno] == 1)
>+ break;
>+ trb_open_priv->tx_mtu = que->tx_mtu;
>+ trb_open_priv->rx_mtu = que->rx_mtu;
>+ trb_open_priv->tx_frag_size = que->tx_frag_size;
>+ trb_open_priv->rx_frag_size = que->rx_frag_size;
>+ if (mtk_cldma_check_ch_cfg(trans->dev, que)) {
>+ trb->status = -EINVAL;
>+ ret = -EINVAL;
>+ } else {
>+ trb->status = -EBUSY;
>+ ret = -EBUSY;
>+ }
>+ trb->trb_complete(skb);
>+ break;
>+ case TRB_CMD_DISABLE:
>+ if (trans->usr_cnt[que->hif_id][que->txqno] > 0) {
>+ trans->usr_cnt[que->hif_id][que->txqno]--;
>+ if (!trans->usr_cnt[que->hif_id][que->txqno])
>+ break;
>+ }
>+ trb->status = -EBUSY;
>+ trb->trb_complete(skb);
>+ ret = -EBUSY;
>+ break;
>+ default:
>+ dev_err((trans->mdev)->dev, "Invalid trb command(%d)\n", trb->cmd);
>+ ret = -EINVAL;
>+ break;
>+ }
>+ return ret;
>+}
>+
>+static void mtk_ctrl_trb_handler(struct trb_srv *srv, struct trans_list *trans_list, u32 qno)
>+{
>+ struct sk_buff_head *skb_list = &trans_list->skb_list[qno];
>+ struct mtk_ctrl_trans *trans = srv->trans;
>+ struct sk_buff *skb, *skb_next;
>+ struct trb *trb, *trb_next;
>+ bool kick = false;
>+ int loop = 0;
>+ int err;
>+
>+ do {
>+ skb = skb_peek(skb_list);
>+ if (!skb)
>+ break;
>+ trb = (struct trb *)skb->cb;
>+
>+ switch (trb->cmd) {
>+ case TRB_CMD_ENABLE:
>+ case TRB_CMD_DISABLE:
>+ skb_unlink(skb, skb_list);
>+ err = mtk_ch_status_check(trans, skb);
>+ if (!err) {
>+ kick = true;
>+ if (trb->cmd == TRB_CMD_DISABLE)
>+ mtk_ctrl_ch_flush(skb_list);
>+ }
>+ break;
>+ case TRB_CMD_TX:
>+ err = mtk_cldma_submit_tx(trans->dev, skb);
>+ if (err) {
>+ if (trans_list->tx_burst_cnt[qno])
>+ kick = true;
>+ else if (err == -EAGAIN)
so how EAGAIN is actually used here?
>+ return;
>+ break;
>+ }
>+
>+ trans_list->tx_burst_cnt[qno]++;
>+ if (trans_list->tx_burst_cnt[qno] >= TX_BURST_MAX_CNT ||
>+ skb_queue_is_last(skb_list, skb)) {
>+ kick = true;
>+ } else {
>+ skb_next = skb_peek_next(skb, skb_list);
>+ trb_next = (struct trb *)skb_next->cb;
>+ if (trb_next->cmd != TRB_CMD_TX)
>+ kick = true;
>+ }
>+
>+ skb_unlink(skb, skb_list);
>+ break;
>+ default:
>+ skb_unlink(skb, skb_list);
>+ }
>+
>+ if (kick) {
>+ mtk_cldma_trb_process(trans->dev, skb);
>+ trans_list->tx_burst_cnt[qno] = 0;
>+ kick = false;
>+ }
>+
>+ loop++;
>+ } while (loop < TRB_NUM_PER_ROUND);
>+}
>+
>+static void mtk_ctrl_trb_process(struct trb_srv *srv)
>+{
>+ struct mtk_ctrl_trans *trans = srv->trans;
>+ struct srv_que *srv_que;
>+ int i;
>+
>+ for (i = 0; i < NR_CLDMA; i++)
>+ list_for_each_entry(srv_que, &srv->srv_q_list[i], list)
>+ mtk_ctrl_trb_handler(srv, &trans->trans_list[i], srv_que->qno);
>+}
>+
>+static int mtk_ctrl_trb_thread(void *args)
>+{
>+ struct trb_srv *srv = args;
>+
>+ for (;;) {
>+ wait_event_interruptible(srv->trb_waitq,
>+ !mtk_ctrl_chs_is_busy_or_empty(srv) ||
>+ kthread_should_stop() || kthread_should_park());
>+ if (kthread_should_stop())
>+ break;
>+
>+ if (kthread_should_park())
>+ kthread_parkme();
>+
>+ do {
>+ mtk_ctrl_trb_process(srv);
>+ cond_resched();
>+ } while (!mtk_ctrl_chs_is_busy_or_empty(srv) && !kthread_should_stop() &&
>+ !kthread_should_park());
>+ }
>+ mtk_ctrl_chs_flush(srv);
>+ return 0;
>+}
>+
>+static int mtk_ctrl_trb_srv_init(struct mtk_ctrl_trans *trans)
>+{
>+ struct srv_que *srv_que;
>+ struct trb_srv *srv;
>+ int i, j;
>+ int ret;
>+
>+ for (i = 0; i < trans->trb_srv_num; i++) {
>+ srv = devm_kzalloc(trans->mdev->dev, sizeof(*srv), GFP_KERNEL);
>+ if (!srv) {
>+ ret = -ENOMEM;
>+ goto err_free_srv;
>+ }
>+
>+ srv->trans = trans;
>+ srv->srv_id = i;
>+ trans->trb_srv[i] = srv;
>+
>+ init_waitqueue_head(&srv->trb_waitq);
>+ for (j = 0; j < NR_CLDMA; j++)
>+ INIT_LIST_HEAD(&srv->srv_q_list[j]);
>+ }
>+
>+ for (i = 0; i < NR_CLDMA; i++)
>+ for (j = 0; j < HW_QUE_NUM; j++) {
>+ if (trans->srv_cfg[i][j] < 0 ||
>+ trans->srv_cfg[i][j] >= trans->trb_srv_num)
>+ trans->srv_cfg[i][j] = 0;
>+ srv_que = devm_kzalloc(trans->mdev->dev, sizeof(*srv_que), GFP_KERNEL);
>+ if (!srv_que) {
>+ ret = -ENOMEM;
>+ goto err_free_srv_que;
>+ }
>+ srv_que->hif_id = i;
>+ srv_que->qno = j;
>+ list_add_tail(&srv_que->list,
>+ &trans->trb_srv[trans->srv_cfg[i][j]]->srv_q_list[i]);
>+ }
>+
>+ for (i = 0; i < trans->trb_srv_num; i++)
>+ trans->trb_srv[i]->trb_thread = kthread_run(mtk_ctrl_trb_thread, trans->trb_srv[i],
>+ "mtk_trb_srv%d_%s", i,
>+ trans->mdev->dev_str);
>+
>+ return 0;
>+err_free_srv_que:
>+ for (i = 0; i < trans->trb_srv_num; i++) {
>+ for (j = 0; j < NR_CLDMA; j++) {
>+ struct srv_que *next_srv_que;
>+
>+ list_for_each_entry_safe(srv_que, next_srv_que,
>+ &trans->trb_srv[i]->srv_q_list[j], list) {
>+ list_del(&srv_que->list);
>+ devm_kfree(trans->mdev->dev, srv_que);
>+ }
>+ }
>+ }
>+err_free_srv:
>+ for (i = 0; i < trans->trb_srv_num; i++) {
>+ if (!trans->trb_srv[i])
>+ break;
>+ devm_kfree(trans->mdev->dev, trans->trb_srv[i]);
>+ trans->trb_srv[i] = NULL;
>+ }
>+
>+ return ret;
>+}
>+
>+static void mtk_ctrl_trb_srv_exit(struct mtk_ctrl_trans *trans)
>+{
>+ struct srv_que *srv_que, *next_srv_que;
>+ struct trb_srv *srv;
>+ int i, j;
>+
>+ for (i = 0; i < trans->trb_srv_num; i++) {
>+ srv = trans->trb_srv[i];
>+ kthread_stop(srv->trb_thread);
>+ for (j = 0; j < NR_CLDMA; j++) {
>+ list_for_each_entry_safe(srv_que, next_srv_que,
>+ &trans->trb_srv[i]->srv_q_list[j], list) {
>+ list_del(&srv_que->list);
>+ devm_kfree(trans->mdev->dev, srv_que);
>+ }
>+ }
>+ devm_kfree(trans->mdev->dev, srv);
>+ trans->trb_srv[i] = NULL;
>+ }
>+}
>+
>+static void mtk_ctrl_remove_radix_tree(struct mtk_ctrl_trans *trans)
>+{
>+ struct queue_info **queues;
>+ int ret, idx;
>+
>+ queues = kcalloc(trans->queues_cnt, sizeof(struct queue_info *), GFP_KERNEL);
>+ if (!queues)
>+ return;
>+
>+ ret = radix_tree_gang_lookup(&trans->queue_tbl, (void **)queues,
>+ 0, trans->queues_cnt);
>+ for (idx = 0; idx < ret; idx++) {
>+ radix_tree_delete(&trans->queue_tbl, queues[idx]->rx_chl & QUEUE_CHL_MASK);
>+ kfree(queues[idx]);
>+ }
>+ kfree(queues);
>+}
>+
>+static void mtk_ctrl_queue_info_update(struct radix_tree_root *queue_tbl, u32 port_chl_mtu)
>+{
>+ struct queue_info *queue;
>+ u32 rx_chl, mtu;
>+
>+ if (!port_chl_mtu)
>+ return;
>+
>+ rx_chl = port_chl_mtu >> RX_CH_ID_SHIFT;
>+ mtu = port_chl_mtu & PORT_MTU_MASK;
>+ queue = radix_tree_lookup(queue_tbl, rx_chl);
>+ if (!queue)
>+ return;
>+
>+ queue->tx_mtu = mtu;
>+ queue->rx_mtu = mtu;
>+ queue->tx_frag_size = mtu;
>+ queue->rx_frag_size = mtu;
>+}
>+
>+static unsigned int ctrl_port_chl_mtu;
>+
>+static int mtk_pcie_hif_init(struct mtk_md_dev *mdev)
>+{
>+ struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
>+ struct queue_info *queue, *queue_info;
>+ struct mtk_ctrl_trans *trans;
>+ int i, j;
>+ int ret;
>+
>+ trans = ctrl_blk->ctrl_hw_priv;
>+ trans->ctrl_blk = ctrl_blk;
>+ queue_info = trans->queue_info;
>+
>+ INIT_RADIX_TREE(&trans->queue_tbl, GFP_KERNEL);
>+ for (i = 0; i < trans->queue_info_num; i++) {
>+ queue = kmemdup(queue_info + i, sizeof(*queue), GFP_KERNEL);
>+ if (!queue) {
>+ ret = -ENOMEM;
>+ goto err_free_radix_tree;
>+ }
>+ if (queue->txqno >= HW_QUE_NUM || queue->rxqno >= HW_QUE_NUM ||
>+ queue->hif_id >= NR_CLDMA) {
>+ dev_err((mdev)->dev, "Failed to get correct queue info %x\n",
>+ queue->rx_chl);
>+ ret = -EINVAL;
>+ goto err_free_radix_tree;
>+ }
>+ ret = radix_tree_insert(&trans->queue_tbl, queue->rx_chl & QUEUE_CHL_MASK, queue);
>+ if (ret) {
>+ dev_err((mdev)->dev, "Insert %x fail, ret: %d", queue->rx_chl, ret);
>+ kfree(queue);
>+ goto err_free_radix_tree;
>+ }
>+ trans->queues_cnt++;
>+ }
>+
>+ mtk_ctrl_queue_info_update(&trans->queue_tbl, ctrl_port_chl_mtu);
>+
>+ for (i = 0; i < NR_CLDMA; i++) {
>+ for (j = 0; j < HW_QUE_NUM; j++) {
>+ skb_queue_head_init(&trans->trans_list[i].skb_list[j]);
>+ trans->trans_list[i].tx_burst_cnt[j] = 0;
>+ }
>+ }
>+ ret = mtk_cldma_init(trans);
>+ if (ret)
>+ goto err_free_radix_tree;
>+
>+ ret = mtk_ctrl_trb_srv_init(trans);
>+ if (ret)
>+ goto err_cldma_exit;
>+
>+ atomic_set(&trans->available, 1);
>+
>+ return 0;
>+
>+err_cldma_exit:
>+ mtk_cldma_exit(trans);
>+err_free_radix_tree:
>+ mtk_ctrl_remove_radix_tree(trans);
>+
>+ return ret;
>+}
>+
>+static int mtk_pcie_hif_exit(struct mtk_md_dev *mdev)
>+{
>+ struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
>+ struct mtk_ctrl_trans *trans;
>+
>+ trans = ctrl_blk->ctrl_hw_priv;
>+
>+ atomic_set(&trans->available, 0);
>+ mtk_ctrl_trb_srv_exit(trans);
>+ mtk_ctrl_remove_radix_tree(trans);
>+ mtk_cldma_exit(trans);
>+
>+ return 0;
>+}
>+
>+static int mtk_pcie_hif_submit_skb(struct mtk_md_dev *mdev, struct sk_buff *skb, bool force_send)
>+{
>+ struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
>+ struct mtk_ctrl_trans *trans;
>+ struct queue_info *que;
>+ struct trb *trb;
>+
>+ trans = ctrl_blk->ctrl_hw_priv;
>+ trb = (struct trb *)skb->cb;
>+
>+ if (trb->cmd == TRB_CMD_STOP || trb->cmd == TRB_CMD_RECOVER) {
>+ trb->trb_complete(skb);
>+ return 0;
>+ }
>+
>+ que = radix_tree_lookup(&trans->queue_tbl, trb->channel_id & QUEUE_CHL_MASK);
>+ if (!que) {
>+ dev_warn((mdev)->dev, "lookup que fail, ch_id: %x, que: 0x%p\n",
>+ trb->channel_id, que);
>+ return -EINVAL;
>+ }
>+
>+ if (!atomic_read(&trans->available))
>+ return -EIO;
>+
>+ if (mtk_queue_list_is_full(trans, que) && !force_send)
>+ return -EAGAIN;
>+
>+ if (trb->cmd == TRB_CMD_DISABLE)
>+ skb_queue_head(&trans->trans_list[que->hif_id].skb_list[que->txqno], skb);
>+ else
>+ skb_queue_tail(&trans->trans_list[que->hif_id].skb_list[que->txqno], skb);
>+
>+ wake_up(&trans->trb_srv[trans->srv_cfg[que->hif_id][que->txqno]]->trb_waitq);
>+
>+ return 0;
>+}
>+
>+static int mtk_pcie_hif_cmd_func(struct mtk_md_dev *mdev, int cmd, void *data)
>+{
>+ struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
>+ struct mtk_ctrl_trans *trans;
>+ struct queue_info *que;
>+ int ret = 0;
>+
>+ switch (cmd) {
>+ case HIF_CTRL_CMD_CHECK_TX_FULL:
>+ trans = ctrl_blk->ctrl_hw_priv;
>+ que = radix_tree_lookup(&trans->queue_tbl,
>+ ((union ctrl_hif_cmd_data *)data)->rx_ch & QUEUE_CHL_MASK);
>+ if (!que) {
>+ dev_warn((mdev)->dev, "Failed to find que to check tx full\n");
>+ return -EINVAL;
>+ }
>+ return mtk_queue_list_is_full(trans, que);
>+ default:
>+ ret = -EINVAL;
>+ break;
>+ }
>+
>+ return ret;
just return 0 no need to zeroinit
>+}
>+
>+static struct mtk_ctrl_hif_ops pcie_ctrl_ops = {
>+ .init = mtk_pcie_hif_init,
>+ .exit = mtk_pcie_hif_exit,
>+ .submit_skb = mtk_pcie_hif_submit_skb,
>+ .send_cmd = mtk_pcie_hif_cmd_func,
>+};
>+
^ permalink raw reply
* RE: [PATCH 02/11] net: wwan: t9xx: Add control plane transaction layer
From: Jagielski, Jedrzej @ 2026-06-01 11:24 UTC (permalink / raw)
To: jackbb_wu@compal.com, Loic Poulain, Sergey Ryazanov,
Johannes Berg, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Wen-Zhi Huang, Shi-Wei Yeh,
Minano Tseng, Matthias Brugger, AngeloGioacchino Del Regno,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-mediatek@lists.infradead.org, linux-doc@vger.kernel.org
In-Reply-To: <20260529-t9xx_driver_v1-v1-2-bdbfe2c01e57@compal.com>
From: Jack Wu via B4 Relay <devnull+jackbb_wu.compal.com@kernel.org>
Sent: Friday, May 29, 2026 12:32 PM
>From: Jack Wu <jackbb_wu@compal.com>
>
>The control plane implements TX services that reside in the
>transaction layer. The services receive the packets from the
>port layer and call the corresponding DMA components to
>transmit data to the device. Meanwhile, TX services receive
>and manage the port control commands from the port layer.
>
>The control plane implements RX services that reside in the
>transaction layer. The services receive the downlink packets
>from the modem and transfer the packets to the corresponding
>port layer interfaces.
>
>Signed-off-by: Jack Wu <jackbb_wu@compal.com>
>---
> drivers/net/wwan/Kconfig | 5 ++++
> drivers/net/wwan/t9xx/Makefile | 5 ++--
> drivers/net/wwan/t9xx/mtk_ctrl_plane.c | 34 ++++++++++++++++++++++
> drivers/net/wwan/t9xx/mtk_ctrl_plane.h | 22 +++++++++++++++
> drivers/net/wwan/t9xx/mtk_dev.c | 44 +++++++++++++++++++++++++++++
> drivers/net/wwan/t9xx/mtk_dev.h | 4 +++
> drivers/net/wwan/t9xx/pcie/Makefile | 10 +++++++
> drivers/net/wwan/t9xx/pcie/mtk_pci.c | 8 ++----
> drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h | 21 ++++++++++++++
> 9 files changed, 146 insertions(+), 7 deletions(-)
>
>diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig
>index 4cee537c739f..d8be12fb988c 100644
>--- a/drivers/net/wwan/Kconfig
>+++ b/drivers/net/wwan/Kconfig
>@@ -124,6 +124,7 @@ config MTK_T7XX
> config MTK_T9XX
> tristate "MediaTek PCIe 5G WWAN modem T9xx device"
> depends on PCI
>+ select MTK_T9XX_PCI
> select NET_DEVLINK
> help
> Enables MediaTek PCIe based 5G WWAN modem (T9xx series) device.
>@@ -133,6 +134,10 @@ config MTK_T9XX
>
> If unsure, say N.
>
>+config MTK_T9XX_PCI
>+ tristate
>+ depends on PCI && MTK_T9XX
>+
> endif # WWAN
>
> endmenu
>diff --git a/drivers/net/wwan/t9xx/Makefile b/drivers/net/wwan/t9xx/Makefile
>index 6f2dd3f91454..ae9d6f2344ab 100644
>--- a/drivers/net/wwan/t9xx/Makefile
>+++ b/drivers/net/wwan/t9xx/Makefile
>@@ -4,7 +4,8 @@ ccflags-y += -I$(src)/pcie
> ccflags-y += -I$(src)
>
> obj-$(CONFIG_MTK_T9XX) += mtk_t9xx.o
>+obj-$(CONFIG_MTK_T9XX_PCI) += pcie/
>
> mtk_t9xx-y := \
>- pcie/mtk_pci.o \
>- pcie/mtk_pci_drv_m9xx.o
>+ mtk_dev.o \
>+ mtk_ctrl_plane.o
>diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
>new file mode 100644
>index 000000000000..ae5e1797b817
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
>@@ -0,0 +1,34 @@
>+// SPDX-License-Identifier: GPL-2.0-only
>+/*
>+ * Copyright (c) 2022, MediaTek Inc.
>+ * Copyright (c) 2022-2023, Intel Corporation.
>+ */
>+
>+#include <linux/device.h>
>+
>+#include "mtk_ctrl_plane.h"
>+
please add kdoc, especially there's EXPORT_SYMBOL
>+int mtk_ctrl_init(struct mtk_md_dev *mdev)
>+{
>+ struct mtk_ctrl_blk *ctrl_blk;
>+
>+ ctrl_blk = devm_kzalloc(mdev->dev, sizeof(*ctrl_blk), GFP_KERNEL);
>+ if (!ctrl_blk)
>+ return -ENOMEM;
>+
>+ ctrl_blk->mdev = mdev;
>+ mdev->ctrl_blk = ctrl_blk;
>+
>+ return 0;
>+}
>+EXPORT_SYMBOL(mtk_ctrl_init);
>+
>+int mtk_ctrl_exit(struct mtk_md_dev *mdev)
do we need int if 0 is always returned?
>+{
>+ struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
>+
>+ devm_kfree(mdev->dev, ctrl_blk);
>+
>+ return 0;
>+}
>+EXPORT_SYMBOL(mtk_ctrl_exit);
>diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
>new file mode 100644
>index 000000000000..8276be19b456
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
>@@ -0,0 +1,22 @@
>+/* SPDX-License-Identifier: GPL-2.0-only
>+ *
>+ * Copyright (c) 2022, MediaTek Inc.
shouldn't 2026 be put?
>+ */
>+
>+#ifndef __MTK_CTRL_PLANE_H__
>+#define __MTK_CTRL_PLANE_H__
>+
>+#include <linux/kref.h>
>+#include <linux/skbuff.h>
>+
>+#include "mtk_dev.h"
>+
>+struct mtk_ctrl_blk {
>+ struct mtk_md_dev *mdev;
>+ struct mtk_ctrl_trans *trans;
>+};
>+
>+int mtk_ctrl_init(struct mtk_md_dev *mdev);
>+int mtk_ctrl_exit(struct mtk_md_dev *mdev);
>+
>+#endif /* __MTK_CTRL_PLANE_H__ */
>diff --git a/drivers/net/wwan/t9xx/mtk_dev.c b/drivers/net/wwan/t9xx/mtk_dev.c
>new file mode 100644
>index 000000000000..f254ca7ed877
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/mtk_dev.c
>@@ -0,0 +1,44 @@
>+// SPDX-License-Identifier: GPL-2.0-only
>+/*
>+ * Copyright (c) 2022, MediaTek Inc.
>+ */
>+
>+#include <linux/module.h>
>+
>+#include "mtk_dev.h"
>+
>+struct mtk_md_dev *mtk_dev_alloc(struct device *pdev, const struct mtk_dev_ops *dev_ops)
>+{
>+ struct mtk_md_dev *mdev;
>+
>+ mdev = devm_kzalloc(pdev, sizeof(*mdev), GFP_KERNEL);
>+ if (!mdev)
>+ return NULL;
>+
>+ mdev->dev_ops = dev_ops;
>+ mdev->dev = pdev;
>+ return mdev;
>+}
>+EXPORT_SYMBOL(mtk_dev_alloc);
>+
>+void mtk_dev_free(struct mtk_md_dev *mdev)
>+{
>+ struct device *dev = mdev->dev;
>+
>+ devm_kfree(dev, mdev);
>+}
>+EXPORT_SYMBOL(mtk_dev_free);
>+
>+static int __init mtk_common_drv_init(void)
>+{
>+ return 0;
>+}
>+module_init(mtk_common_drv_init);
>+
>+static void __exit mtk_common_drv_exit(void)
is it used anywhere here in the patch?
>+{
>+}
>+module_exit(mtk_common_drv_exit);
>+
>+MODULE_DESCRIPTION("MediaTek T9xx PCIe WWAN driver");
>+MODULE_LICENSE("GPL");
>diff --git a/drivers/net/wwan/t9xx/mtk_dev.h b/drivers/net/wwan/t9xx/mtk_dev.h
>index 8278a0e2875e..37eec1a358fa 100644
>--- a/drivers/net/wwan/t9xx/mtk_dev.h
>+++ b/drivers/net/wwan/t9xx/mtk_dev.h
>@@ -57,6 +57,7 @@ struct mtk_md_dev {
> void *hw_priv;
> u32 hw_ver;
> char dev_str[MTK_DEV_STR_LEN];
>+ void *ctrl_blk;
> };
>
> static inline u32 mtk_dev_get_dev_state(struct mtk_md_dev *mdev)
>@@ -105,4 +106,7 @@ static inline int mtk_dev_send_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
> return mdev->dev_ops->send_dev_evt(mdev, dev_evt);
> }
>
>+struct mtk_md_dev *mtk_dev_alloc(struct device *pdev, const struct mtk_dev_ops *dev_ops);
>+void mtk_dev_free(struct mtk_md_dev *mdev);
>+
> #endif /* __MTK_DEV_H__ */
>diff --git a/drivers/net/wwan/t9xx/pcie/Makefile b/drivers/net/wwan/t9xx/pcie/Makefile
>new file mode 100644
>index 000000000000..7410d1796d27
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/pcie/Makefile
>@@ -0,0 +1,10 @@
>+# SPDX-License-Identifier: GPL-2.0-only
>+
>+ccflags-y += -I$(src)
>+ccflags-y += -I$(src)/..
>+
>+obj-$(CONFIG_MTK_T9XX_PCI) += mtk_t9xx_pcie.o
>+
>+mtk_t9xx_pcie-y := \
>+ mtk_pci_drv_m9xx.o \
>+ mtk_pci.o
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.c b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
>index adec3ccdee08..518c32d55643 100644
>--- a/drivers/net/wwan/t9xx/pcie/mtk_pci.c
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
>@@ -14,6 +14,7 @@
> #include <linux/module.h>
>
> #include "mtk_dev.h"
>+#include "mtk_trans_ctrl.h"
> #include "mtk_pci.h"
> #include "mtk_pci_reg.h"
>
>@@ -385,8 +386,7 @@ static u32 mtk_pci_ext_h2d_evt_hw_bits(u32 chs)
>
> SET_HW_BITS(hw_bits, chs, MHCCIF_RC2EP_EVT_DEVICE_RESET,
> DEV_EVT_H2D_DEVICE_RESET);
>- SET_HW_BITS(hw_bits, chs, MHCCIF_RC2EP_EVT_DRM_DISABLE_AP,
>- EXT_EVT_H2D_DRM_DISABLE_AP);
>+
> return LE32_TO_U32(cpu_to_le32(hw_bits));
> }
>
>@@ -779,13 +779,11 @@ static int mtk_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> struct mtk_md_dev *mdev;
> int ret;
>
>- mdev = devm_kzalloc(dev, sizeof(*mdev), GFP_KERNEL);
>+ mdev = mtk_dev_alloc(dev, &pci_hw_ops);
> if (!mdev) {
> ret = -ENOMEM;
> goto out;
> }
>- mdev->dev_ops = &pci_hw_ops;
>- mdev->dev = dev;
>
> priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> if (!priv) {
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
>new file mode 100644
>index 000000000000..d6de4c43b529
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
>@@ -0,0 +1,21 @@
>+/* SPDX-License-Identifier: GPL-2.0-only
>+ *
>+ * Copyright (c) 2022, MediaTek Inc.
>+ */
>+
>+#ifndef __MTK_TRANS_CTRL_H__
>+#define __MTK_TRANS_CTRL_H__
>+
>+#include <linux/kref.h>
>+#include <linux/list.h>
>+#include <linux/skbuff.h>
>+#include <linux/types.h>
>+
>+#include "mtk_dev.h"
>+
>+struct mtk_ctrl_trans {
>+ struct mtk_ctrl_blk *ctrl_blk;
>+ struct mtk_md_dev *mdev;
>+};
>+
>+#endif
>
>--
>2.34.1
^ permalink raw reply
* [PATCH v3 0/2] arm64: cpufeature: Add WORKAROUND_DISABLE_CNP capability
From: Zeng Heng @ 2026-06-01 11:19 UTC (permalink / raw)
To: vladimir.murzin, xuwei5, wangyushan12, yangyicong, maz,
yeoreum.yun, miko.lenczewski, james.clark, corbet, skhan,
kuninori.morimoto.gx, lucaswei, catalin.marinas, broonie,
lpieralisi, thuth, kevin.brodsky, tongtiangen, oupton,
ryan.roberts, mark.rutland, will, Sascha.Bischoff
Cc: linux-arm-kernel, wangkefeng.wang, linux-doc, linux-kernel
From: Zeng Heng <zengheng4@huawei.com>
v2: https://lore.kernel.org/all/20260529063132.766491-1-zengheng@huaweicloud.com/
v1: https://lore.kernel.org/all/20260526015720.206854-1-zengheng@huaweicloud.com/
Changes in v3:
- Keep CONFIG_ARM64_WORKAROUND_DISABLE_CNP config and generalise
ARM64_WORKAROUND_DISABLE_CNP capability.
Changes in v2:
- Unify CNP disable workaround into ARM64_WORKAROUND_DISABLE_CNP
Zeng Heng (2):
arm64: cpufeature: Add WORKAROUND_DISABLE_CNP capability
arm64: kernel: Disable CNP on HiSilicon HIP09
Documentation/arch/arm64/silicon-errata.rst | 2 ++
arch/arm64/Kconfig | 20 ++++++++++++++++++++
arch/arm64/include/asm/cpucaps.h | 4 ++--
arch/arm64/kernel/cpu_errata.c | 17 ++++++++++++-----
arch/arm64/kernel/cpufeature.c | 2 +-
arch/arm64/tools/cpucaps | 2 +-
6 files changed, 38 insertions(+), 9 deletions(-)
--
2.43.0
^ permalink raw reply
* [PATCH v3 2/2] arm64: kernel: Disable CNP on HiSilicon HIP09
From: Zeng Heng @ 2026-06-01 11:20 UTC (permalink / raw)
To: vladimir.murzin, xuwei5, wangyushan12, yangyicong, maz,
yeoreum.yun, miko.lenczewski, james.clark, corbet, skhan,
kuninori.morimoto.gx, lucaswei, catalin.marinas, broonie,
lpieralisi, thuth, kevin.brodsky, tongtiangen, oupton,
ryan.roberts, mark.rutland, will, Sascha.Bischoff
Cc: linux-arm-kernel, wangkefeng.wang, linux-doc, linux-kernel
In-Reply-To: <20260601112000.1145391-1-zengheng@huaweicloud.com>
From: Zeng Heng <zengheng4@huawei.com>
HiSilicon HIP09 implements TLB entry matching behavior that deviates
from the ARM architecture specification when the CNP (Common not Private)
bit is set in TTBRx_ELx.
When TTBRx.CNP=1, TLB entries may be incorrectly shared between CPU
cores, leading to TLB conflicts and stale mappings. This affects
coherency and can result in incorrect translations.
Add the hardware erratum workaround (Hisilicon erratum 162100125) to
disable CNP on affected HIP09 cores.
Co-developed-by: Tong Tiangen <tongtiangen@huawei.com>
Signed-off-by: Tong Tiangen <tongtiangen@huawei.com>
Signed-off-by: Zeng Heng <zengheng4@huawei.com>
---
Documentation/arch/arm64/silicon-errata.rst | 2 ++
arch/arm64/Kconfig | 16 ++++++++++++++++
arch/arm64/kernel/cpu_errata.c | 13 ++++++++++---
3 files changed, 28 insertions(+), 3 deletions(-)
diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst
index 211119ce7adc..cd50059edb85 100644
--- a/Documentation/arch/arm64/silicon-errata.rst
+++ b/Documentation/arch/arm64/silicon-errata.rst
@@ -284,6 +284,8 @@ stable kernels.
+----------------+-----------------+-----------------+-----------------------------+
| Hisilicon | Hip09 | #162100801 | HISILICON_ERRATUM_162100801 |
+----------------+-----------------+-----------------+-----------------------------+
+| Hisilicon | Hip09 | #162100125 | HISILICON_ERRATUM_162100125 |
++----------------+-----------------+-----------------+-----------------------------+
+----------------+-----------------+-----------------+-----------------------------+
| Qualcomm Tech. | Kryo/Falkor v1 | E1003 | QCOM_FALKOR_ERRATUM_1003 |
+----------------+-----------------+-----------------+-----------------------------+
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index f297517a83b9..75638e37883d 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -1273,6 +1273,22 @@ config HISILICON_ERRATUM_162100801
If unsure, say Y.
+config HISILICON_ERRATUM_162100125
+ bool "Hisilicon erratum 162100125"
+ default y
+ select ARM64_WORKAROUND_DISABLE_CNP
+ help
+ On HiSilicon HIP09, TLB entry matching behavior when CNP
+ (TTBRx.CNP=1) is enabled differs from the ARM architecture
+ specification.
+
+ TLB entries may be incorrectly shared between CPUs, potentially
+ causing TLB conflicts and stale mappings.
+
+ Disable CNP support for affected HiSilicon HIP09 cores.
+
+ If unsure, say Y.
+
config QCOM_FALKOR_ERRATUM_1003
bool "Falkor E1003: Incorrect translation due to ASID change"
default y
diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c
index b0db946568b7..02e0ee5c948c 100644
--- a/arch/arm64/kernel/cpu_errata.c
+++ b/arch/arm64/kernel/cpu_errata.c
@@ -608,6 +608,14 @@ static const struct midr_range erratum_ac04_cpu_23_list[] = {
};
#endif
+#ifdef CONFIG_ARM64_WORKAROUND_DISABLE_CNP
+static const struct midr_range cnp_erratum_cpus[] = {
+ MIDR_ALL_VERSIONS(MIDR_NVIDIA_CARMEL),
+ MIDR_ALL_VERSIONS(MIDR_HISI_HIP09),
+ {},
+};
+#endif
+
const struct arm64_cpu_capabilities arm64_errata[] = {
#ifdef CONFIG_ARM64_WORKAROUND_CLEAN_CACHE
{
@@ -803,10 +811,9 @@ const struct arm64_cpu_capabilities arm64_errata[] = {
#endif
#ifdef CONFIG_ARM64_WORKAROUND_DISABLE_CNP
{
- /* NVIDIA Carmel */
- .desc = "NVIDIA Carmel CNP erratum",
+ .desc = "NVIDIA Carmel CNP erratum, or Hisilicon erratum 162100125",
.capability = ARM64_WORKAROUND_DISABLE_CNP,
- ERRATA_MIDR_ALL_VERSIONS(MIDR_NVIDIA_CARMEL),
+ ERRATA_MIDR_RANGE_LIST(cnp_erratum_cpus),
},
#endif
#ifdef CONFIG_ARM64_WORKAROUND_TRBE_OVERWRITE_FILL_MODE
--
2.43.0
^ permalink raw reply related
* [PATCH v3 1/2] arm64: cpufeature: Add WORKAROUND_DISABLE_CNP capability
From: Zeng Heng @ 2026-06-01 11:19 UTC (permalink / raw)
To: vladimir.murzin, xuwei5, wangyushan12, yangyicong, maz,
yeoreum.yun, miko.lenczewski, james.clark, corbet, skhan,
kuninori.morimoto.gx, lucaswei, catalin.marinas, broonie,
lpieralisi, thuth, kevin.brodsky, tongtiangen, oupton,
ryan.roberts, mark.rutland, will, Sascha.Bischoff
Cc: linux-arm-kernel, wangkefeng.wang, linux-doc, linux-kernel
In-Reply-To: <20260601112000.1145391-1-zengheng@huaweicloud.com>
From: Zeng Heng <zengheng4@huawei.com>
The NVIDIA Carmel CNP erratum is not the only case requiring CNP to be
disabled. Abstract this into a common WORKAROUND_DISABLE_CNP capability
to facilitate adding errata for future chips and reduce duplicate
checks in has_useable_cnp().
This serves as a prerequisite for the subsequent Hisilicon erratum
162100125.
Suggested-by: Vladimir Murzin <vladimir.murzin@arm.com>
Signed-off-by: Zeng Heng <zengheng4@huawei.com>
---
arch/arm64/Kconfig | 4 ++++
arch/arm64/include/asm/cpucaps.h | 4 ++--
arch/arm64/kernel/cpu_errata.c | 4 ++--
arch/arm64/kernel/cpufeature.c | 2 +-
arch/arm64/tools/cpucaps | 2 +-
5 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index fe60738e5943..f297517a83b9 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -1315,9 +1315,13 @@ config QCOM_FALKOR_ERRATUM_E1041
If unsure, say Y.
+config ARM64_WORKAROUND_DISABLE_CNP
+ bool
+
config NVIDIA_CARMEL_CNP_ERRATUM
bool "NVIDIA Carmel CNP: CNP on Carmel semantically different than ARM cores"
default y
+ select ARM64_WORKAROUND_DISABLE_CNP
help
If CNP is enabled on Carmel cores, non-sharable TLBIs on a core will not
invalidate shared TLB entries installed by a different core, as it would
diff --git a/arch/arm64/include/asm/cpucaps.h b/arch/arm64/include/asm/cpucaps.h
index d0d3cdd5763c..25c61cda901c 100644
--- a/arch/arm64/include/asm/cpucaps.h
+++ b/arch/arm64/include/asm/cpucaps.h
@@ -58,8 +58,8 @@ cpucap_is_possible(const unsigned int cap)
return IS_ENABLED(CONFIG_ARM64_ERRATUM_2658417);
case ARM64_WORKAROUND_CAVIUM_23154:
return IS_ENABLED(CONFIG_CAVIUM_ERRATUM_23154);
- case ARM64_WORKAROUND_NVIDIA_CARMEL_CNP:
- return IS_ENABLED(CONFIG_NVIDIA_CARMEL_CNP_ERRATUM);
+ case ARM64_WORKAROUND_DISABLE_CNP:
+ return IS_ENABLED(CONFIG_ARM64_WORKAROUND_DISABLE_CNP);
case ARM64_WORKAROUND_REPEAT_TLBI:
return IS_ENABLED(CONFIG_ARM64_WORKAROUND_REPEAT_TLBI);
case ARM64_WORKAROUND_SPECULATIVE_SSBS:
diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c
index 5377e4c2eba2..b0db946568b7 100644
--- a/arch/arm64/kernel/cpu_errata.c
+++ b/arch/arm64/kernel/cpu_errata.c
@@ -801,11 +801,11 @@ const struct arm64_cpu_capabilities arm64_errata[] = {
1, 0),
},
#endif
-#ifdef CONFIG_NVIDIA_CARMEL_CNP_ERRATUM
+#ifdef CONFIG_ARM64_WORKAROUND_DISABLE_CNP
{
/* NVIDIA Carmel */
.desc = "NVIDIA Carmel CNP erratum",
- .capability = ARM64_WORKAROUND_NVIDIA_CARMEL_CNP,
+ .capability = ARM64_WORKAROUND_DISABLE_CNP,
ERRATA_MIDR_ALL_VERSIONS(MIDR_NVIDIA_CARMEL),
},
#endif
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 6d53bb15cf7b..20c5f24f74a9 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -1785,7 +1785,7 @@ has_useable_cnp(const struct arm64_cpu_capabilities *entry, int scope)
if (is_kdump_kernel())
return false;
- if (cpus_have_cap(ARM64_WORKAROUND_NVIDIA_CARMEL_CNP))
+ if (cpus_have_cap(ARM64_WORKAROUND_DISABLE_CNP))
return false;
return has_cpuid_feature(entry, scope);
diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps
index 811c2479e82d..9b85a84f6fd4 100644
--- a/arch/arm64/tools/cpucaps
+++ b/arch/arm64/tools/cpucaps
@@ -120,7 +120,7 @@ WORKAROUND_CAVIUM_TX2_219_PRFM
WORKAROUND_CAVIUM_TX2_219_TVM
WORKAROUND_CLEAN_CACHE
WORKAROUND_DEVICE_LOAD_ACQUIRE
-WORKAROUND_NVIDIA_CARMEL_CNP
+WORKAROUND_DISABLE_CNP
WORKAROUND_PMUV3_IMPDEF_TRAPS
WORKAROUND_QCOM_FALKOR_E1003
WORKAROUND_QCOM_ORYON_CNTVOFF
--
2.43.0
^ permalink raw reply related
* RE: [PATCH 01/11] net: wwan: t9xx: Add PCIe core
From: Jagielski, Jedrzej @ 2026-06-01 11:18 UTC (permalink / raw)
To: jackbb_wu@compal.com, Loic Poulain, Sergey Ryazanov,
Johannes Berg, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Wen-Zhi Huang, Shi-Wei Yeh,
Minano Tseng, Matthias Brugger, AngeloGioacchino Del Regno,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-mediatek@lists.infradead.org, linux-doc@vger.kernel.org
In-Reply-To: <20260529-t9xx_driver_v1-v1-1-bdbfe2c01e57@compal.com>
From: Jack Wu via B4 Relay <devnull+jackbb_wu.compal.com@kernel.org>
Sent: Friday, May 29, 2026 12:32 PM
>From: Jack Wu <jackbb_wu@compal.com>
>
>Registers the T900 device driver with the kernel. Set up all
>the fundamental configurations for the device: PCIe layer,
>Modem Host Cross Core Interface (MHCCIF), Reset Generation
>Unit (RGU), modem common control operations and build
>infrastructure.
>
>* PCIe layer code implements driver probe and removal, MSI-X
> interrupt initialization and de-initialization, and the way
> of resetting the device.
>* MHCCIF provides interrupt channels to communicate events
> such as handshake, PM and port enumeration.
>* RGU provides interrupt channels to generate notifications
> from the device so that the T900 driver could get the
> device reset.
>* Modem common control operations provide the basic read/write
> functions of the device's hardware registers,
> mask/unmask/get/clear functions of the device's interrupt
> registers and inquiry functions of the device's status.
>
>Signed-off-by: Jack Wu <jackbb_wu@compal.com>
>---
> drivers/net/wwan/Kconfig | 12 +
> drivers/net/wwan/Makefile | 1 +
> drivers/net/wwan/t9xx/Makefile | 10 +
> drivers/net/wwan/t9xx/mtk_dev.h | 108 +++
> drivers/net/wwan/t9xx/pcie/mtk_pci.c | 926 ++++++++++++++++++++++++++
> drivers/net/wwan/t9xx/pcie/mtk_pci.h | 219 ++++++
> drivers/net/wwan/t9xx/pcie/mtk_pci_drv_m9xx.c | 69 ++
> drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h | 71 ++
> 8 files changed, 1416 insertions(+)
>
>diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig
>index 88df55d78d90..4cee537c739f 100644
>--- a/drivers/net/wwan/Kconfig
>+++ b/drivers/net/wwan/Kconfig
>@@ -121,6 +121,18 @@ config MTK_T7XX
>
> If unsure, say N.
>
>+config MTK_T9XX
>+ tristate "MediaTek PCIe 5G WWAN modem T9xx device"
>+ depends on PCI
>+ select NET_DEVLINK
>+ help
>+ Enables MediaTek PCIe based 5G WWAN modem (T9xx series) device.
>+
>+ To compile this driver as a module, choose M here: the module will be
>+ called mtk_t9xx.
>+
>+ If unsure, say N.
>+
> endif # WWAN
>
> endmenu
>diff --git a/drivers/net/wwan/Makefile b/drivers/net/wwan/Makefile
>index 3960c0ae2445..7361eef4c472 100644
>--- a/drivers/net/wwan/Makefile
>+++ b/drivers/net/wwan/Makefile
>@@ -14,3 +14,4 @@ obj-$(CONFIG_QCOM_BAM_DMUX) += qcom_bam_dmux.o
> obj-$(CONFIG_RPMSG_WWAN_CTRL) += rpmsg_wwan_ctrl.o
> obj-$(CONFIG_IOSM) += iosm/
> obj-$(CONFIG_MTK_T7XX) += t7xx/
>+obj-$(CONFIG_MTK_T9XX) += t9xx/
>diff --git a/drivers/net/wwan/t9xx/Makefile b/drivers/net/wwan/t9xx/Makefile
>new file mode 100644
>index 000000000000..6f2dd3f91454
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/Makefile
>@@ -0,0 +1,10 @@
>+# SPDX-License-Identifier: GPL-2.0-only
>+
>+ccflags-y += -I$(src)/pcie
>+ccflags-y += -I$(src)
>+
>+obj-$(CONFIG_MTK_T9XX) += mtk_t9xx.o
>+
>+mtk_t9xx-y := \
>+ pcie/mtk_pci.o \
>+ pcie/mtk_pci_drv_m9xx.o
>diff --git a/drivers/net/wwan/t9xx/mtk_dev.h b/drivers/net/wwan/t9xx/mtk_dev.h
>new file mode 100644
>index 000000000000..8278a0e2875e
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/mtk_dev.h
>@@ -0,0 +1,108 @@
>+/* SPDX-License-Identifier: GPL-2.0-only
>+ *
>+ * Copyright (c) 2022, MediaTek Inc.
>+ */
>+
>+#ifndef __MTK_DEV_H__
>+#define __MTK_DEV_H__
>+
>+#include <linux/dma-mapping.h>
>+#include <linux/dmapool.h>
>+#include <linux/kernel.h>
>+#include <linux/module.h>
>+#include <linux/sched.h>
>+#include <linux/slab.h>
>+#include <linux/spinlock.h>
>+
>+#define MTK_DEV_STR_LEN 16
>+
>+enum mtk_user_id {
>+ MTK_USER_MIN,
>+ MTK_USER_CTRL,
>+ MTK_USER_DATA,
>+ MTK_USER_MAX
>+};
>+
>+enum mtk_dev_evt_h2d {
>+ DEV_EVT_H2D_DEVICE_RESET = BIT(2),
>+ DEV_EVT_H2D_MAX = BIT(5)
>+};
>+
>+enum mtk_dev_evt_d2h {
>+ DEV_EVT_D2H_BOOT_FLOW_SYNC = BIT(4),
>+ DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP = BIT(5),
>+ DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD = BIT(6),
>+ DEV_EVT_D2H_MAX = BIT(11)
>+};
>+
>+struct mtk_md_dev;
>+
>+struct mtk_dev_ops {
>+ u32 (*get_dev_state)(struct mtk_md_dev *mdev);
>+ void (*ack_dev_state)(struct mtk_md_dev *mdev, u32 state);
>+ u32 (*get_dev_cfg)(struct mtk_md_dev *mdev);
>+ int (*register_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt,
>+ int (*evt_cb)(u32 status, void *data), void *data);
>+ void (*unregister_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
>+ void (*mask_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
>+ void (*unmask_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
>+ void (*clear_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
>+ int (*send_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
>+};
>+
>+/* mtk_md_dev defines the structure of MTK modem device */
>+struct mtk_md_dev {
>+ struct device *dev;
>+ const struct mtk_dev_ops *dev_ops;
>+ void *hw_priv;
>+ u32 hw_ver;
>+ char dev_str[MTK_DEV_STR_LEN];
>+};
>+
>+static inline u32 mtk_dev_get_dev_state(struct mtk_md_dev *mdev)
>+{
>+ return mdev->dev_ops->get_dev_state(mdev);
>+}
>+
>+static inline void mtk_dev_ack_dev_state(struct mtk_md_dev *mdev, u32 state)
>+{
>+ return mdev->dev_ops->ack_dev_state(mdev, state);
>+}
>+
>+static inline u32 mtk_dev_get_dev_cfg(struct mtk_md_dev *mdev)
>+{
>+ return mdev->dev_ops->get_dev_cfg(mdev);
>+}
>+
>+static inline int mtk_dev_register_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt,
>+ int (*evt_cb)(u32 status, void *data), void *data)
>+{
>+ return mdev->dev_ops->register_dev_evt(mdev, dev_evt, evt_cb, data);
>+}
>+
>+static inline void mtk_dev_unregister_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
>+{
>+ mdev->dev_ops->unregister_dev_evt(mdev, dev_evt);
>+}
>+
>+static inline void mtk_dev_mask_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
>+{
>+ mdev->dev_ops->mask_dev_evt(mdev, dev_evt);
>+}
>+
>+static inline void mtk_dev_unmask_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
>+{
>+ mdev->dev_ops->unmask_dev_evt(mdev, dev_evt);
>+}
>+
>+static inline void mtk_dev_clear_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
>+{
>+ mdev->dev_ops->clear_dev_evt(mdev, dev_evt);
>+}
>+
>+static inline int mtk_dev_send_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
>+{
>+ return mdev->dev_ops->send_dev_evt(mdev, dev_evt);
>+}
>+
>+#endif /* __MTK_DEV_H__ */
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.c b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
>new file mode 100644
>index 000000000000..adec3ccdee08
>--- /dev/null
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
>@@ -0,0 +1,926 @@
>+// SPDX-License-Identifier: GPL-2.0-only
>+/*
>+ * Copyright (c) 2022, MediaTek Inc.
>+ */
>+
>+#include <linux/acpi.h>
>+#include <linux/aer.h>
>+#include <linux/bitfield.h>
>+#include <linux/debugfs.h>
>+#include <linux/delay.h>
>+#include <linux/device.h>
>+#include <linux/dma-mapping.h>
>+#include <linux/kernel.h>
>+#include <linux/module.h>
>+
>+#include "mtk_dev.h"
>+#include "mtk_pci.h"
>+#include "mtk_pci_reg.h"
>+
>+#define BAR_NUM 6
please add driver prefix
>+#define MTK_PCI_TRANSPARENT_ATR_SIZE (0x3F)
>+#define MTK_PCI_MINIMUM_ATR_SIZE (0x1000)
>+#define LE32_TO_U32(x) ((__force u32)(__le32)(x))
>+#define SET_HW_BITS(dest, chs, mhccif, dev) \
>+ ({ \
>+ if ((chs) & (dev))
what if any of these is equal to 0?
just skip do not log anything?
>+ (dest) |= FIELD_PREP(mhccif, 1); \
>+ })
>+
>+extern const struct mtk_pci_dev_cfg mtk_dev_cfg_0900;
>+
>+struct mtk_mhccif_cb {
>+ struct list_head entry;
>+ int (*evt_cb)(u32 status, void *data);
>+ void *data;
>+ u32 chs;
>+};
>+
>+u32 mtk_pci_mac_read32(struct mtk_pci_priv *priv, u64 addr)
>+{
>+ return ioread32(priv->mac_reg_base + addr);
>+}
>+
>+void mtk_pci_mac_write32(struct mtk_pci_priv *priv, u64 addr, u32 val)
>+{
>+ iowrite32(val, priv->mac_reg_base + addr);
>+}
>+
>+u32 mtk_pci_read32(struct mtk_md_dev *mdev, u64 addr)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+
>+ return ioread32(priv->ext_reg_base + addr);
>+}
>+
>+void mtk_pci_write32(struct mtk_md_dev *mdev, u64 addr, u32 val)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+
>+ iowrite32(val, priv->ext_reg_base + addr);
>+}
>+
would be lovely to have kdoc of the non-static functions from the series
>+int mtk_pci_setup_atr(struct mtk_md_dev *mdev, struct mtk_atr_cfg *cfg)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ u32 addr, val, size_h, size_l;
>+ int atr_size, pos, offset;
>+
>+ if (cfg->transparent) {
>+ atr_size = MTK_PCI_TRANSPARENT_ATR_SIZE; /* No address conversion is performed */
>+ } else {
>+ if (cfg->size < MTK_PCI_MINIMUM_ATR_SIZE)
>+ cfg->size = MTK_PCI_MINIMUM_ATR_SIZE;
>+
>+ if (cfg->src_addr & (cfg->size - 1)) {
>+ dev_err((mdev)->dev, "Invalid atr src addr is not aligned to size\n");
>+ return -EFAULT;
>+ }
>+ if (cfg->trsl_addr & (cfg->size - 1)) {
>+ dev_err((mdev)->dev,
>+ "Invalid atr trsl addr is not aligned to size, %llx, %llx\n",
>+ cfg->trsl_addr, cfg->size - 1);
>+ return -EFAULT;
>+ }
>+ size_l = FIELD_GET(GENMASK_ULL(31, 0), cfg->size);
>+ size_h = FIELD_GET(GENMASK_ULL(63, 32), cfg->size);
>+ pos = ffs(size_l);
>+ if (pos) {
>+ atr_size = pos - 2;
>+ } else {
>+ pos = ffs(size_h);
>+ atr_size = pos + 30;
i believe better would be to have some defines instead of magic
>+ }
please put some breaks to have the code logically separated
>+ }
>+
>+ /* Calculate table offset */
>+ offset = ATR_PORT_OFFSET * cfg->port + ATR_TABLE_OFFSET * cfg->table;
>+ /* SRC_ADDR_H */
>+ addr = REG_ATR_PCIE_WIN0_T0_SRC_ADDR_MSB + offset;
>+ val = (u32)(cfg->src_addr >> 32);
>+ mtk_pci_mac_write32(priv, addr, val);
>+ /* SRC_ADDR_L */
>+ addr = REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset;
>+ val = (u32)(cfg->src_addr & 0xFFFFF000) | (atr_size << 1) | 0x1;
>+ mtk_pci_mac_write32(priv, addr, val);
>+
>+ /* TRSL_ADDR_H */
>+ addr = REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_MSB + offset;
>+ val = (u32)(cfg->trsl_addr >> 32);
>+ mtk_pci_mac_write32(priv, addr, val);
>+ /* TRSL_ADDR_L */
>+ addr = REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_LSB + offset;
>+ val = (u32)(cfg->trsl_addr & 0xFFFFF000);
>+ mtk_pci_mac_write32(priv, addr, val);
comments seem to be redundant imo; clearer would be to have just newline
instead
>+
>+ /* TRSL_PARAM */
>+ addr = REG_ATR_PCIE_WIN0_T0_TRSL_PARAM + offset;
>+ val = (cfg->trsl_param << 16) | cfg->trsl_id;
>+ mtk_pci_mac_write32(priv, addr, val);
again a lot of magic here
>+
>+ return 0;
>+}
>+
>+void mtk_pci_atr_disable(struct mtk_pci_priv *priv)
>+{
>+ int port, tbl, offset;
>+ u32 val;
>+
>+ /* Disable all ATR table for all ports */
>+ for (port = ATR_SRC_PCI_WIN0; port <= ATR_SRC_AXIS_3; port++)
>+ for (tbl = 0; tbl < ATR_TABLE_NUM_PER_ATR; tbl++) {
>+ /* Calculate table offset */
>+ offset = ATR_PORT_OFFSET * port + ATR_TABLE_OFFSET * tbl;
>+ val = mtk_pci_mac_read32(priv, REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset);
>+ val = val & (~BIT(0));
>+ /* Disable table by SRC_ADDR_L */
>+ mtk_pci_mac_write32(priv, REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset, val);
>+ }
>+}
>+
>+static void mtk_pci_set_msix_merged(struct mtk_pci_priv *priv, int irq_cnt)
>+{
>+ mtk_pci_mac_write32(priv, REG_PCIE_CFG_MSIX, ffs(irq_cnt) * 2 - 1);
>+}
>+
>+u32 mtk_pci_get_dev_state(struct mtk_md_dev *mdev)
>+{
>+ return mtk_pci_mac_read32(mdev->hw_priv, REG_PCIE_DEBUG_DUMMY_7);
>+}
>+
>+void mtk_pci_ack_dev_state(struct mtk_md_dev *mdev, u32 state)
>+{
>+ mtk_pci_mac_write32(mdev->hw_priv, REG_PCIE_DEBUG_DUMMY_7, state);
>+}
>+
>+int mtk_pci_get_irq_id(struct mtk_md_dev *mdev, enum mtk_irq_src irq_src)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ const int *irq_tbl = priv->cfg->irq_tbl;
>+ int irq_id = -EINVAL;
>+
>+ if (irq_src > MTK_IRQ_SRC_MIN && irq_src < MTK_IRQ_SRC_MAX) {
>+ irq_id = irq_tbl[irq_src];
>+ if (unlikely(irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX))
>+ irq_id = -EINVAL;
>+ }
>+
>+ return irq_id;
>+}
>+
>+int mtk_pci_get_virq_id(struct mtk_md_dev *mdev, int irq_id)
>+{
>+ struct pci_dev *pdev = to_pci_dev(mdev->dev);
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ int nr = 0;
what's the point of zeroiniting if the value is assigned at
the next line?
>+
>+ nr = irq_id % priv->irq_cnt;
are we sure irq_cnt won't be equal to 0 in any scenario?
>+
>+ return pci_irq_vector(pdev, nr);
>+}
>+
>+int mtk_pci_register_irq(struct mtk_md_dev *mdev, int irq_id,
>+ int (*irq_cb)(int irq_id, void *data), void *data)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+
>+ if (unlikely((irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX) || !irq_cb))
>+ return -EINVAL;
>+
>+ if (priv->irq_cb_list[irq_id]) {
>+ dev_err((mdev)->dev,
>+ "Unable to register irq, irq_id=%d, it's already been register by %ps.\n",
>+ irq_id, priv->irq_cb_list[irq_id]);
>+ return -EFAULT;
>+ }
>+ priv->irq_cb_list[irq_id] = irq_cb;
>+ priv->irq_cb_data[irq_id] = data;
>+
>+ return 0;
>+}
>+
>+int mtk_pci_unregister_irq(struct mtk_md_dev *mdev, int irq_id)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+
>+ if (unlikely(irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX))
>+ return -EINVAL;
is it anyhow beneficial to put unlikely here and in case of other
appearances within the series?
>+
>+ if (!priv->irq_cb_list[irq_id]) {
>+ dev_err((mdev)->dev, "irq_id=%d has not been registered\n", irq_id);
>+ return -EFAULT;
>+ }
>+ priv->irq_cb_list[irq_id] = NULL;
>+ priv->irq_cb_data[irq_id] = NULL;
>+
>+ return 0;
>+}
>+
>+int mtk_pci_mask_irq(struct mtk_md_dev *mdev, int irq_id)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+
>+ if (unlikely((irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX) || priv->irq_type != PCI_IRQ_MSIX)) {
same here
>+ dev_err(mdev->dev, "Failed to mask irq: input irq_id=%d\n", irq_id);
>+ return -EINVAL;
>+ }
>+
>+ mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, BIT(irq_id));
>+
>+ return 0;
>+}
>+
>+int mtk_pci_unmask_irq(struct mtk_md_dev *mdev, int irq_id)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+
>+ if (unlikely((irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX) || priv->irq_type != PCI_IRQ_MSIX)) {
>+ dev_err(mdev->dev, "Failed to unmask irq: input irq_id=%d\n", irq_id);
>+ return -EINVAL;
>+ }
>+
>+ mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_SET_GRP0_0, BIT(irq_id));
>+
>+ return 0;
>+}
>+
>+int mtk_pci_clear_irq(struct mtk_md_dev *mdev, int irq_id)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+
>+ if (unlikely((irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX) || priv->irq_type != PCI_IRQ_MSIX)) {
>+ dev_err(mdev->dev, "Failed to clear irq: input irq_id=%d\n", irq_id);
>+ return -EINVAL;
>+ }
>+
>+ mtk_pci_mac_write32(priv, REG_MSIX_ISTATUS_HOST_GRP0_0, BIT(irq_id));
>+
>+ return 0;
>+}
>+
>+static u32 mtk_pci_ext_d2h_evt_hw_bits(u32 chs)
>+{
>+ u32 hw_bits = 0;
>+
>+ SET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC,
>+ DEV_EVT_D2H_BOOT_FLOW_SYNC);
>+ SET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP,
>+ DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP);
>+ SET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD,
>+ DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD);
>+
>+ return LE32_TO_U32(cpu_to_le32(hw_bits));
>+}
>+
>+static u32 mtk_pci_ext_d2h_evt_chs(u32 hw_bits)
>+{
>+ u32 chs = 0;
>+
>+ if (!hw_bits)
>+ return chs;
>+
>+ chs = FIELD_PREP(DEV_EVT_D2H_BOOT_FLOW_SYNC,
>+ FIELD_GET(MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC, hw_bits)) |
>+ FIELD_PREP(DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP,
>+ FIELD_GET(MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP, hw_bits)) |
>+ FIELD_PREP(DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD,
>+ FIELD_GET(MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD, hw_bits));
>+
>+ return chs;
>+}
>+
>+int mtk_pci_register_ext_evt(struct mtk_md_dev *mdev, u32 chs,
>+ int (*evt_cb)(u32 status, void *data), void *data)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ struct mtk_mhccif_cb *cb;
>+ int ret = 0;
>+
>+ if (!chs || !evt_cb)
>+ return -EINVAL;
>+
>+ spin_lock_bh(&priv->mhccif_lock);
>+ list_for_each_entry(cb, &priv->mhccif_cb_list, entry) {
>+ if (cb->chs & chs) {
>+ ret = -EFAULT;
>+ dev_err((mdev)->dev,
>+ "Unable to register evt, intersection: chs=0x%08x&0x%08x registered_cb=%ps\n",
>+ chs, cb->chs, cb->evt_cb);
>+ goto err_spin_unlock;
>+ }
>+ }
>+ cb = devm_kzalloc(mdev->dev, sizeof(*cb), GFP_ATOMIC);
>+ if (!cb) {
>+ ret = -ENOMEM;
>+ goto err_spin_unlock;
>+ }
>+ cb->evt_cb = evt_cb;
>+ cb->data = data;
>+ cb->chs = chs;
>+ list_add_tail(&cb->entry, &priv->mhccif_cb_list);
>+err_spin_unlock:
>+ spin_unlock_bh(&priv->mhccif_lock);
>+
>+ return ret;
>+}
>+
>+void mtk_pci_unregister_ext_evt(struct mtk_md_dev *mdev, u32 chs)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ struct mtk_mhccif_cb *cb, *next;
>+
>+ if (!chs)
>+ return;
>+
>+ spin_lock_bh(&priv->mhccif_lock);
>+ list_for_each_entry_safe(cb, next, &priv->mhccif_cb_list, entry) {
>+ if (cb->chs == chs) {
>+ list_del(&cb->entry);
>+ devm_kfree(mdev->dev, cb);
>+ goto out;
>+ }
>+ }
>+ dev_warn((mdev)->dev,
>+ "Unable to unregister evt, no chs=0x%08x has been registered.\n", chs);
>+out:
>+ spin_unlock_bh(&priv->mhccif_lock);
>+}
>+
>+void mtk_pci_mask_ext_evt(struct mtk_md_dev *mdev, u32 chs)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ u32 hw_bits;
>+
>+ hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);
one of these is inited at declaration, 2nd one isnt
please stay consistant, @hw_bits can be inited as well
>+
>+ mtk_pci_write32(mdev, priv->cfg->mhccif_rc_base_addr +
>+ MHCCIF_EP2RC_SW_INT_EAP_MASK_SET, hw_bits);
>+}
>+
>+void mtk_pci_unmask_ext_evt(struct mtk_md_dev *mdev, u32 chs)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ u32 hw_bits;
>+
>+ hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);
>+
>+ mtk_pci_write32(mdev, priv->cfg->mhccif_rc_base_addr +
>+ MHCCIF_EP2RC_SW_INT_EAP_MASK_CLR, hw_bits);
>+}
>+
>+void mtk_pci_clear_ext_evt(struct mtk_md_dev *mdev, u32 chs)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ u32 hw_bits;
>+
>+ hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);
>+
>+ mtk_pci_write32(mdev, priv->cfg->mhccif_rc_base_addr +
>+ MHCCIF_EP2RC_SW_INT_ACK, hw_bits);
>+}
>+
>+static u32 mtk_pci_ext_h2d_evt_hw_bits(u32 chs)
>+{
>+ u32 hw_bits = 0;
>+
>+ SET_HW_BITS(hw_bits, chs, MHCCIF_RC2EP_EVT_DEVICE_RESET,
>+ DEV_EVT_H2D_DEVICE_RESET);
>+ SET_HW_BITS(hw_bits, chs, MHCCIF_RC2EP_EVT_DRM_DISABLE_AP,
>+ EXT_EVT_H2D_DRM_DISABLE_AP);
>+ return LE32_TO_U32(cpu_to_le32(hw_bits));
>+}
>+
missing kdoc here and there
>+int mtk_pci_send_ext_evt(struct mtk_md_dev *mdev, u32 ch)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ u32 rc_base;
>+ u32 hw_bits;
please squash variables of the same type into single line
>+
>+ rc_base = priv->cfg->mhccif_rc_base_addr;
>+
>+ /* Only allow one ch to be triggered at a time */
>+ if (!is_power_of_2(ch)) {
>+ dev_err((mdev)->dev, "Unsupported ext evt ch=0x%08x\n", ch);
>+ return -EINVAL;
>+ }
>+
>+ hw_bits = mtk_pci_ext_h2d_evt_hw_bits(ch);
>+ mtk_pci_write32(mdev, rc_base + MHCCIF_RC2EP_SW_BSY, hw_bits);
>+ mtk_pci_write32(mdev, rc_base + MHCCIF_RC2EP_SW_TCHNUM, ffs(hw_bits) - 1);
>+ return 0;
>+}
>+
>+static u32 mtk_pci_get_ext_evt_hw_status(struct mtk_md_dev *mdev)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+
>+ return mtk_pci_read32(mdev, priv->cfg->mhccif_rc_base_addr + MHCCIF_EP2RC_SW_INT_STS);
>+}
>+
>+int mtk_pci_fldr(struct mtk_md_dev *mdev)
>+{
>+#ifdef CONFIG_ACPI
>+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
>+ acpi_status acpi_ret;
>+ acpi_handle handle;
>+
>+ if (acpi_disabled) {
>+ dev_err((mdev)->dev, "Unsupported, acpi function isn't enable\n");
>+ return -ENODEV;
>+ }
>+ handle = ACPI_HANDLE(mdev->dev);
>+ if (!handle) {
>+ dev_err((mdev)->dev, "Unsupported, acpi handle isn't found\n");
>+ return -ENODEV;
>+ }
>+ if (!acpi_has_method(handle, "_RST")) {
>+ dev_err((mdev)->dev, "Unsupported, _RST method isn't found\n");
>+ return -ENODEV;
>+ }
>+ acpi_ret = acpi_evaluate_object(handle, "_RST", NULL, &buffer);
>+ if (ACPI_FAILURE(acpi_ret)) {
>+ dev_err((mdev)->dev, "Failed to execute _RST method: %s\n",
>+ acpi_format_exception(acpi_ret));
>+ return -EFAULT;
>+ }
>+ acpi_os_free(buffer.pointer);
>+
>+ return 0;
>+#else
#else /* !CONFIG_ACPI */
>+ dev_err((mdev)->dev, "Unsupported, CONFIG ACPI hasn't been set to 'y'\n");
>+
>+ return -ENODEV;
>+#endif
#endif /* CONFIG_ACPI */
>+}
>+
>+int mtk_pci_pldr(struct mtk_md_dev *mdev)
>+{
>+#ifdef CONFIG_ACPI
>+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
>+ struct pci_dev *bridge;
>+ acpi_status acpi_ret;
>+ acpi_handle handle;
>+
>+ if (acpi_disabled) {
>+ dev_err((mdev)->dev, "Unsupported, acpi function isn't enable\n");
>+ return -ENODEV;
>+ }
>+
>+ bridge = pci_upstream_bridge(to_pci_dev(mdev->dev));
>+ if (!bridge) {
>+ dev_err((mdev)->dev, "Unable to find bridge\n");
>+ return -ENODEV;
>+ }
>+
>+ handle = ACPI_HANDLE(&bridge->dev);
>+ if (!handle) {
>+ dev_err((mdev)->dev, "Unsupported, acpi handle isn't found\n");
>+ return -ENODEV;
>+ }
>+ if (!acpi_has_method(handle, "PXP._OFF") ||
>+ !acpi_has_method(handle, "PXP._ON")) {
>+ dev_err((mdev)->dev, "Unsupported, pldr method isn't supported\n");
>+ return -ENODEV;
>+ }
>+ acpi_ret = acpi_evaluate_object(handle, "PXP._OFF", NULL, &buffer);
>+ if (ACPI_FAILURE(acpi_ret)) {
>+ dev_err((mdev)->dev, "Failed to execute _OFF method: %s\n",
>+ acpi_format_exception(acpi_ret));
>+ return -EFAULT;
>+ }
>+ msleep(500);
please dont use magic number
also where this value has been derived from?
>+ acpi_ret = acpi_evaluate_object(handle, "PXP._ON", NULL, &buffer);
>+ if (ACPI_FAILURE(acpi_ret)) {
>+ dev_err((mdev)->dev, "Failed to execute _ON method: %s\n",
>+ acpi_format_exception(acpi_ret));
>+ return -EFAULT;
>+ }
>+ acpi_os_free(buffer.pointer);
pleae add some newlines
>+
>+ return 0;
>+#else
>+ dev_err((mdev)->dev, "Unsupported, CONFIG ACPI hasn't been set to 'y'\n");
>+
>+ return -ENODEV;
>+#endif
>+}
>+
>+u32 mtk_pci_get_dev_cfg(struct mtk_md_dev *mdev)
>+{
>+ u32 val;
>+
>+ val = mtk_pci_mac_read32(mdev->hw_priv, REG_PCIE_DEBUG_DUMMY_4);
>+ return (val >> MTK_CFG_INFO_BIT_SHIFT);
>+}
>+
>+static int mtk_pci_dev_reset(struct mtk_md_dev *mdev, enum mtk_reset_type type)
>+{
>+ switch (type) {
>+ case RESET_MHCCIF:
>+ return mtk_pci_send_ext_evt(mdev, DEV_EVT_H2D_DEVICE_RESET);
>+ case RESET_FLDR:
>+ return mtk_pci_fldr(mdev);
>+ case RESET_PLDR:
>+ return mtk_pci_pldr(mdev);
>+ default:
>+ break;
>+ }
>+
>+ return -EINVAL;
please put return into default label
>+}
>+
>+int mtk_pci_reset(struct mtk_md_dev *mdev, enum mtk_reset_type type)
>+{
>+ return mtk_pci_dev_reset(mdev, type);
>+}
>+
>+bool mtk_pci_link_check(struct mtk_md_dev *mdev)
>+{
>+ return pci_device_is_present(to_pci_dev(mdev->dev));
>+}
>+
>+static void mtk_mhccif_isr_work(struct work_struct *work)
>+{
>+ struct mtk_pci_priv *priv = container_of(work, struct mtk_pci_priv, mhccif_work);
isn't this line > 80 chars?
>+ struct mtk_md_dev *mdev = priv->irq_desc->mdev;
>+ struct mtk_mhccif_cb *cb;
>+ u32 stat, mask, chs;
>+
>+ stat = mtk_pci_get_ext_evt_hw_status(mdev);
>+ mask = mtk_pci_read32(mdev, priv->cfg->mhccif_rc_base_addr
>+ + MHCCIF_EP2RC_SW_INT_EAP_MASK);
>+ if (unlikely(stat == U32_MAX && !(mtk_pci_link_check(mdev)))) {
>+ /* When link failed, we don't need to unmask/clear. */
>+ dev_err((mdev)->dev, "Failed to check link in MHCCIF handler.\n");
>+ return;
>+ }
>+
>+ stat &= ~mask;
>+ chs = mtk_pci_ext_d2h_evt_chs(stat);
>+ spin_lock_bh(&priv->mhccif_lock);
>+ list_for_each_entry(cb, &priv->mhccif_cb_list, entry) {
>+ if (cb->chs & chs)
>+ cb->evt_cb(cb->chs & chs, cb->data);
>+ }
>+ spin_unlock_bh(&priv->mhccif_lock);
>+
>+ mtk_pci_clear_irq(mdev, priv->mhccif_irq_id);
>+ mtk_pci_unmask_irq(mdev, priv->mhccif_irq_id);
>+}
>+
>+static const struct pci_device_id t9xx_pci_table[] = {
>+ MTK_PCI_DEV_CFG(0x0900, mtk_dev_cfg_0900),
>+ CEI_PCI_DEV_CFG(0x01CA, mtk_dev_cfg_0900),
>+ {/* end: all zeroes */}
>+};
>+
>+MODULE_DEVICE_TABLE(pci, t9xx_pci_table);
>+
>+static int mtk_pci_bar_init(struct mtk_md_dev *mdev)
>+{
>+ struct pci_dev *pdev = to_pci_dev(mdev->dev);
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ u32 bar[BAR_NUM];
>+ int i, ret;
>+
>+ for (i = 0; i < BAR_NUM; i++)
>+ pci_read_config_dword(to_pci_dev(mdev->dev),
>+ PCI_BASE_ADDRESS_0 + (i << 2), bar + i);
>+
>+ ret = pcim_iomap_regions(pdev, MTK_REQUESTED_BARS, mdev->dev_str);
>+ if (ret) {
>+ dev_err((mdev)->dev, "Failed to init MMIO. ret=%d\n", ret);
>+ return ret;
>+ }
>+
>+ /* get ioremapped memory */
>+ priv->mac_reg_base = pcim_iomap_table(pdev)[MTK_BAR_0_1_IDX];
>+ priv->bar23_addr = pcim_iomap_table(pdev)[MTK_BAR_2_3_IDX];
>+ if (!priv->mac_reg_base || !priv->bar23_addr) {
>+ dev_err((mdev)->dev, "Failed to init BAR.\n");
>+ return -EINVAL;
>+ }
>+ /* We use MD view base address "0" to observe registers */
>+ priv->ext_reg_base = priv->bar23_addr - ATR_PCIE_REG_TRSL_ADDR;
>+
>+ return 0;
>+}
>+
>+static void mtk_pci_bar_exit(struct mtk_md_dev *mdev)
>+{
>+ pcim_iounmap_region(to_pci_dev(mdev->dev), MTK_REQUESTED_BARS);
>+}
>+
>+static int mtk_mhccif_irq_cb(int irq_id, void *data)
>+{
>+ struct mtk_md_dev *mdev = data;
>+ struct mtk_pci_priv *priv;
>+
>+ priv = mdev->hw_priv;
>+ queue_work(system_highpri_wq, &priv->mhccif_work);
>+
>+ return 0;
>+}
>+
>+static int mtk_mhccif_init(struct mtk_md_dev *mdev)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ int ret;
>+
>+ INIT_LIST_HEAD(&priv->mhccif_cb_list);
>+ spin_lock_init(&priv->mhccif_lock);
>+ INIT_WORK(&priv->mhccif_work, mtk_mhccif_isr_work);
>+
>+ ret = mtk_pci_get_irq_id(mdev, MTK_IRQ_SRC_MHCCIF);
>+ if (ret < 0) {
>+ dev_err((mdev)->dev, "Failed to get mhccif_irq_id. ret=%d\n", ret);
>+ goto err;
why cannot just return ret?
>+ }
>+ priv->mhccif_irq_id = ret;
>+
>+ ret = mtk_pci_register_irq(mdev, priv->mhccif_irq_id, mtk_mhccif_irq_cb, mdev);
>+ if (ret) {
>+ dev_err((mdev)->dev, "Failed to register mhccif_irq callback\n");
>+ goto err;
it's redundant
>+ }
>+
>+err:
>+ return ret;
>+}
>+
>+static void mtk_mhccif_exit(struct mtk_md_dev *mdev)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+
>+ mtk_pci_unregister_irq(mdev, priv->mhccif_irq_id);
>+ cancel_work_sync(&priv->mhccif_work);
>+}
>+
>+static irqreturn_t mtk_pci_irq_handler(struct mtk_md_dev *mdev, u32 irq_state)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ int irq_id;
>+
>+ /* Check whether each set bit has a callback, if has, call it */
>+ do {
>+ irq_id = fls(irq_state) - 1;
are we sure irq_state cannot be 0?
>+ irq_state &= ~BIT(irq_id);
>+ if (likely(priv->irq_cb_list[irq_id]))
>+ priv->irq_cb_list[irq_id](irq_id, priv->irq_cb_data[irq_id]);
>+ else
>+ dev_err((mdev)->dev, "Unhandled irq_id=%d, no callback for it.\n", irq_id);
>+ } while (irq_state);
>+
>+ return IRQ_HANDLED;
>+}
>+
>+static irqreturn_t mtk_pci_irq_msix(int irq, void *data)
>+{
>+ struct mtk_pci_irq_desc *irq_desc = data;
>+ struct mtk_md_dev *mdev = irq_desc->mdev;
>+ struct mtk_pci_priv *priv;
>+ u32 irq_state, irq_enable;
>+
>+ priv = mdev->hw_priv;
>+ irq_state = mtk_pci_mac_read32(priv, REG_MSIX_ISTATUS_HOST_GRP0_0);
>+ irq_enable = mtk_pci_mac_read32(priv, REG_IMASK_HOST_MSIX_GRP0_0);
>+ irq_state &= irq_enable;
>+
>+ if (unlikely(!irq_state) ||
>+ unlikely(!((irq_state & GENMASK(priv->irq_cnt - 1, 0)) & irq_desc->msix_bits)))
>+ return IRQ_NONE;
>+
>+ /* Mask the bit and user needs to unmask by itself */
>+ mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, irq_state & ~BIT(30));
>+
>+ return mtk_pci_irq_handler(mdev, irq_state);
>+}
>+
>+static int mtk_pci_request_irq_msix(struct mtk_md_dev *mdev, int irq_cnt_allocated)
>+{
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ struct mtk_pci_irq_desc *irq_desc;
>+ struct pci_dev *pdev;
>+ int irq_cnt;
>+ int ret, i;
>+
>+ /* calculate the nearest 2's power number */
>+ irq_cnt = BIT(fls(irq_cnt_allocated) - 1);
>+ pdev = to_pci_dev(mdev->dev);
>+ irq_desc = priv->irq_desc;
>+ for (i = 0; i < irq_cnt; i++) {
>+ irq_desc[i].mdev = mdev;
>+ irq_desc[i].msix_bits = BIT(i);
>+ snprintf(irq_desc[i].name, MTK_IRQ_NAME_LEN, "msix%d-%s", i, mdev->dev_str);
>+ ret = pci_request_irq(pdev, i, mtk_pci_irq_msix, NULL,
>+ &irq_desc[i], irq_desc[i].name);
>+ if (ret) {
>+ dev_err((mdev)->dev, "Failed to request %s: ret=%d\n",
>+ irq_desc[i].name, ret);
>+ for (i--; i >= 0; i--)
>+ pci_free_irq(pdev, i, &irq_desc[i]);
>+ return ret;
>+ }
>+ }
>+ priv->irq_cnt = irq_cnt;
>+ priv->irq_type = PCI_IRQ_MSIX;
>+
>+ if (irq_cnt != MTK_IRQ_CNT_MAX)
>+ mtk_pci_set_msix_merged(priv, irq_cnt);
>+
>+ return 0;
>+}
>+
>+static int mtk_pci_request_irq(struct mtk_md_dev *mdev)
>+{
>+ struct pci_dev *pdev = to_pci_dev(mdev->dev);
>+ int irq_cnt;
>+
>+ irq_cnt = pci_alloc_irq_vectors(pdev, MTK_IRQ_CNT_MIN, MTK_IRQ_CNT_MAX, PCI_IRQ_MSIX);
>+
>+ if (irq_cnt < MTK_IRQ_CNT_MIN) {
>+ dev_err(mdev->dev,
>+ "Unable to alloc pci irq vectors. ret=%d maxirqcnt=%d irqtype=0x%x\n",
>+ irq_cnt, MTK_IRQ_CNT_MAX, PCI_IRQ_MSIX);
>+ return -EFAULT;
>+ }
>+
>+ return mtk_pci_request_irq_msix(mdev, irq_cnt);
>+}
>+
>+static void mtk_pci_free_irq(struct mtk_md_dev *mdev)
>+{
>+ struct pci_dev *pdev = to_pci_dev(mdev->dev);
>+ struct mtk_pci_priv *priv = mdev->hw_priv;
>+ int i;
>+
>+ for (i = 0; i < priv->irq_cnt; i++)
>+ pci_free_irq(pdev, i, &priv->irq_desc[i]);
>+
>+ pci_free_irq_vectors(pdev);
>+}
>+
>+static const struct mtk_dev_ops pci_hw_ops = {
>+ .get_dev_state = mtk_pci_get_dev_state,
>+ .ack_dev_state = mtk_pci_ack_dev_state,
>+ .get_dev_cfg = mtk_pci_get_dev_cfg,
>+ .register_dev_evt = mtk_pci_register_ext_evt,
>+ .unregister_dev_evt = mtk_pci_unregister_ext_evt,
>+ .mask_dev_evt = mtk_pci_mask_ext_evt,
>+ .unmask_dev_evt = mtk_pci_unmask_ext_evt,
>+ .clear_dev_evt = mtk_pci_clear_ext_evt,
>+ .send_dev_evt = mtk_pci_send_ext_evt,
>+};
>+
>+static int mtk_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
>+{
>+ struct device *dev = &pdev->dev;
>+ struct mtk_pci_priv *priv;
>+ struct mtk_md_dev *mdev;
>+ int ret;
>+
>+ mdev = devm_kzalloc(dev, sizeof(*mdev), GFP_KERNEL);
>+ if (!mdev) {
>+ ret = -ENOMEM;
>+ goto out;
as for the rest of the labels please name what is done
eg log_err
>+ }
>+ mdev->dev_ops = &pci_hw_ops;
>+ mdev->dev = dev;
>+
>+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>+ if (!priv) {
>+ ret = -ENOMEM;
>+ goto free_cntx_data;
>+ }
>+
>+ pci_set_drvdata(pdev, mdev);
>+ priv->cfg = (void *)id->driver_data;
>+ priv->mdev = mdev;
>+ mdev->hw_ver = pdev->device;
>+ mdev->hw_priv = priv;
>+ mdev->dev = dev;
>+ snprintf(mdev->dev_str, MTK_DEV_STR_LEN, "%02x%02x%d",
>+ pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn));
>+ if (pdev->state_saved)
>+ pci_restore_state(pdev);
>+
>+ ret = pcim_enable_device(pdev);
>+ if (ret) {
>+ dev_err((mdev)->dev, "Failed to enable pci device.\n");
>+ goto free_priv_data;
>+ }
>+
>+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
>+ if (ret) {
>+ dev_err((mdev)->dev, "Failed to set DMA Mask and Coherent. (ret=%d)\n", ret);
>+ goto disable_device;
>+ }
>+
>+ ret = mtk_pci_bar_init(mdev);
>+ if (ret)
>+ goto disable_device;
>+
>+ ret = priv->cfg->atr_init(mdev);
>+ if (ret)
>+ goto free_bar;
>+
>+ ret = mtk_mhccif_init(mdev);
>+ if (ret)
>+ goto free_bar;
>+
>+ /* mask all irqs */
>+ if (priv->cfg->flag & MTK_CFG_IRQ_DFLT_MASK)
>+ mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, U32_MAX);
>+
>+ ret = mtk_pci_request_irq(mdev);
>+ if (ret)
>+ goto free_mhccif;
>+
>+ pci_set_master(pdev);
>+ mtk_pci_unmask_irq(mdev, priv->mhccif_irq_id);
>+
>+ if (mtk_pci_link_check(mdev)) {
>+ pci_save_state(pdev);
>+ } else {
>+ ret = -EFAULT;
>+ goto clear_master;
#define EFAULT 14 /* Bad address */
does it suit here?
>+ }
>+
>+ priv->saved_state = pci_store_saved_state(pdev);
>+ if (!priv->saved_state) {
>+ ret = -EFAULT;
>+ goto clear_master;
>+ }
>+
>+ return 0;
>+
>+clear_master:
>+ pci_clear_master(pdev);
>+ mtk_pci_free_irq(mdev);
>+free_mhccif:
>+ mtk_mhccif_exit(mdev);
>+free_bar:
>+ mtk_pci_bar_exit(mdev);
>+disable_device:
>+ pci_disable_device(pdev);
>+free_priv_data:
>+ devm_kfree(dev, priv);
>+free_cntx_data:
>+ devm_kfree(dev, mdev);
>+out:
>+ dev_err(dev, "Failed to probe device, ret=%d\n", ret);
>+
>+ return ret;
>+}
>+
please also take a look on sashiko notes, there is some number of them
^ permalink raw reply
* Re: [PATCH mm-unstable v18 06/14] mm/khugepaged: generalize collapse_huge_page for mTHP collapse
From: David Hildenbrand (Arm) @ 2026-06-01 11:13 UTC (permalink / raw)
To: Lance Yang
Cc: npache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel,
aarcange, akpm, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, liam, ljs, mathieu.desnoyers, matthew.brost,
mhiramat, mhocko, peterx, pfalcato, rakie.kim, raquini, rdunlap,
richard.weiyang, rientjes, rostedt, rppt, ryan.roberts, shivankg,
sunnanyong, surenb, thomas.hellstrom, tiwai, usamaarif642, vbabka,
vishal.moola, wangkefeng.wang, will, willy, yang, ying.huang, ziy,
zokeefe, usama.arif
In-Reply-To: <baa0a462-46e0-44ab-b583-c722ad253afe@linux.dev>
On 6/1/26 12:47, Lance Yang wrote:
>
>
> On 2026/6/1 18:23, David Hildenbrand (Arm) wrote:
>> On 6/1/26 11:08, Lance Yang wrote:
>>>
>>>
>>>
>>> One small thing, I think we should probably keep the smp_wmb(), and just
>>> move it before the earlier pmd_populate().
>>>
>>> IIUC, the ordering we want is still:
>>>
>>> clear old PTEs
>>> smp_wmb()
>>> pmd_populate()
>>>
>>> so another CPU cannot walk through the re-installed PMD and still observe
>>> the old PTEs, right?
>>
>> There is a smp_wmb() in __folio_mark_uptodate(), that should be sufficient?
>
> Ah, cool! __folio_mark_uptodate() already does the job :P
>
> So yeah, no extra smp_wmb() needed here!
Yeah. BTW, I think we'd need a spin_lock_nested(), so @Nico, treat my code as a
draft.
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH v3 1/4] mm/zswap: Make shrink_worker writeback cursor per-memcg
From: Hao Jia @ 2026-06-01 11:07 UTC (permalink / raw)
To: Yosry Ahmed
Cc: akpm, tj, hannes, shakeel.butt, mhocko, mkoutny, nphamcs,
chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <aho7nepN5jZtKmef@google.com>
On 2026/5/30 09:24, Yosry Ahmed wrote:
> On Tue, May 26, 2026 at 07:45:58PM +0800, Hao Jia wrote:
>> From: Hao Jia <jiahao1@lixiang.com>
>>
>> The zswap background writeback worker shrink_worker() uses a global
>> cursor zswap_next_shrink, protected by zswap_shrink_lock, to round-robin
>> across the online memcgs under root_mem_cgroup.
>>
>> Proactive writeback also wants a similar per-memcg cursor that is
>> scoped to the specified memcg, so that repeated invocations against
>> the same memcg make forward progress across its descendant memcgs
>> instead of restarting from the first child memcg each time.
>
> Is this a problem in practice?
>
> Is the concern the overhead of scanning memcgs repeatedly, or lack of
> fairness? I wonder if we should just do writeback in batches from all
> memcgs, similar to how reclaim does it, then evaluate at the end if we
> need to start over?
>
Not using a per-cgroup cursor will cause issues for "repeated
small-budget calls" cases. For example, repeatedly triggering a 2MB
writeback might result in only writing back pages from the first few
child memcgs every time. In the worst-case scenario (where the writeback
amount is less than WB_BATCH), it might only ever write back from the
first child memcg.
Similar to how memory reclaim uses mem_cgroup_iter() (via struct
mem_cgroup_reclaim_iter) and the old shrink_worker() used
zswap_next_shrink, we need a shared cursor here.
>>
>> Naturally, group the cursor and its protecting spinlock into a
>> zswap_wb_iter struct, and make it a member of struct mem_cgroup to
>> realize per-memcg cursor management. Accordingly, shrink_worker() now
>> uses the lock and cursor in root_mem_cgroup->zswap_wb_iter.
>
> If we really need to have per-memcg cursors (I am not a big fan), I
> think we can minimize the overhead by making the cursor updates use
> atomic cmpxchg instead of having a per-memcg lock.
>
Because mem_cgroup_iter() always calls css_put(&prev->css), we cannot
simply update zswap_wb_iter.pos via cmpxchg() after calling it. Doing so
could lead to a double css_put() issue on prev->css.
Therefore, if we switch to the cmpxchg() approach, we wouldn't be able
to reuse the existing mem_cgroup_iter() logic. We would have to write a
new function similar to cgroup_iter(), and its implementation might end
up looking a bit obscure/complex.
Currently, this lock is only used in shrink_memcg(), proactive
writeback, and mem_cgroup_css_offline(). Note that shrink_memcg() only
acquires the lock of the root cgroup, and mem_cgroup_css_offline() is
unlikely to be a hot path.
So, should we keep the spin_lock or go with the cmpxchg() approach?
Yosry and Nhat, what are your thoughts on this?
>>
>> Because the cursor is now per-memcg, the offline cleanup must visit
>> every ancestor that could be holding a reference to the dying memcg.
>> Factor out __zswap_memcg_offline_cleanup() and walk from dead_memcg up
>> to the root.
>
> Another reason why I don't like per-memcg cursors. There is too much
> complexity and I wonder if it's warranted. If we stick with per-memcg
> cursors please do the refactoring in separate patches to make the
> patches easier to review.
Sorry about that. I will try to keep each patch as simple as possible in
the next version.
Thanks,
Hao
^ permalink raw reply
* Re: [PATCH mm-unstable v18 06/14] mm/khugepaged: generalize collapse_huge_page for mTHP collapse
From: Lance Yang @ 2026-06-01 10:47 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: npache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel,
aarcange, akpm, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, liam, ljs, mathieu.desnoyers, matthew.brost,
mhiramat, mhocko, peterx, pfalcato, rakie.kim, raquini, rdunlap,
richard.weiyang, rientjes, rostedt, rppt, ryan.roberts, shivankg,
sunnanyong, surenb, thomas.hellstrom, tiwai, usamaarif642, vbabka,
vishal.moola, wangkefeng.wang, will, willy, yang, ying.huang, ziy,
zokeefe, usama.arif
In-Reply-To: <6b11bf0a-769c-4ef2-ac6f-2af38200a6bc@kernel.org>
On 2026/6/1 18:23, David Hildenbrand (Arm) wrote:
> On 6/1/26 11:08, Lance Yang wrote:
>>
>>
>> On 2026/6/1 14:54, David Hildenbrand (Arm) wrote:
>>> On 6/1/26 05:28, Lance Yang wrote:
>>>>
>>>>
>>>> Ah, fair point.
>>>>
>>>> I was mostly worried about arch hooks that walk vma->vm_mm again, rather
>>>> than only using the pte pointer passed in. For example, mips does:
>>>
>>> Right, a re-walk would be the real problem.
>>>
>>>>
>>>> update_mmu_cache_range()
>>>> -> __update_tlb()
>>>> -> pgd_offset(vma->vm_mm, address)
>>>> -> pte_offset_map(...)
>>>>
>>>> and __update_tlb() has this assumption:
>>>>
>>>> /*
>>>> * update_mmu_cache() is called between pte_offset_map_lock()
>>>> * and pte_unmap_unlock(), so we can assume that ptep is not
>>>> * NULL here: and what should be done below if it were NULL?
>>>> */
>>>>
>>>> So if khugepaged happens to run with current->active_mm == vma->vm_mm
>>>> here, could __update_tlb() hit the none PMD, get NULL from
>>>> pte_offset_map(), and then dereference it?
>>>
>>> Likely yes -- that MIPS code is horrible. And the comment in MIPS code
>>> even spells that out. :(
>>>
>>> Do you know about other code like that, or is MIPS the only one doing a
>>> re-walk and crossing fingers?
>>>
>>>>
>>>> Just wanted to raise it since some arch code may still have assumptions
>>>> like this, and the always-enable-mTHP work is getting closer ...
>>>
>>> Right. I assume set_pte_at() couldn't trigger something similar (re-walk) in
>>> arch code,
>>> because we simply provide the ptep. update_mmu_cache_range() only consumes the
>>> pte.
>>>
>>>>
>>>> Probably very very very hard to hit, though :)
>>>
>>> Delaying update_mmu_cache_range() is nasty, as we'd have to make sure that
>>> nobody can interfere in the meantime ... and the PMD lock will not be sufficient.
>>>
>>> Maybe we could reinstall the page table with the cleared (none) entries while
>>> still holding the PTL?
>>>
>>> Thinking out loud:
>>>
>>> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
>>> index 5ba298d420b7..e39b750b1e6f 100644
>>> --- a/mm/khugepaged.c
>>> +++ b/mm/khugepaged.c
>>> @@ -1413,13 +1413,17 @@ static enum scan_result collapse_huge_page(struct
>>> mm_struct *mm, unsigned long s
>>> map_anon_folio_pmd_nopf(folio, pmd, vma, pmd_addr);
>>> } else {
>>> /*
>>> - * set_ptes is called in map_anon_folio_pte_nopf with the
>>> - * pmd_ptl lock still held; this is safe as the PMD is expected
>>> - * to be none. The pmd entry is then repopulated below.
>>> + * Re-insert the page table with the cleared entries, but
>>> + * hold the PTL, such that no one can mess with the re-installed
>>> + * page table until we updated the temporarily-cleared entries
>>> + * through map_anon_folio_pte_nopf().
>>> */
>>> - map_anon_folio_pte_nopf(folio, pte, vma, start_addr, /
>>> *uffd_wp=*/ false);
>>> - smp_wmb(); /* make PTEs visible before PMD. See pmd_install() */
>>
>> One small thing, I think we should probably keep the smp_wmb(), and just
>> move it before the earlier pmd_populate().
>>
>> IIUC, the ordering we want is still:
>>
>> clear old PTEs
>> smp_wmb()
>> pmd_populate()
>>
>> so another CPU cannot walk through the re-installed PMD and still observe
>> the old PTEs, right?
>
> There is a smp_wmb() in __folio_mark_uptodate(), that should be sufficient?
Ah, cool! __folio_mark_uptodate() already does the job :P
So yeah, no extra smp_wmb() needed here!
Cheers, Lance
^ permalink raw reply
* [PATCH v6] kernel: param: initialize module_kset in a pure_initcall
From: Shashank Balaji @ 2026-06-01 10:19 UTC (permalink / raw)
To: Gary Guo, Danilo Krummrich, Petr Pavlu
Cc: Shashank Balaji, Rahul Bukte, linux-kernel, coresight,
linux-arm-kernel, driver-core, rust-for-linux, linux-doc,
Daniel Palmer, Tim Bird, linux-modules, linux-tegra, Sumit Gupta,
Suzuki K Poulose, James Clark, Alexander Shishkin,
Greg Kroah-Hartman, Rafael J. Wysocki, Miguel Ojeda, Boqun Feng,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Jonathan Corbet, Shuah Khan, Luis Chamberlain,
Daniel Gomez, Sami Tolvanen, Aaron Tomlin, Mike Leach, Leo Yan,
Thierry Reding, Jonathan Hunter
In-Reply-To: <ahEd4iC-2hqUbMy3@JPC00244420>
Commit "driver core: platform: set mod_name in driver registration" will set
struct device_driver's mod_name member for platform driver registration. For a
driver to be registered with its mod_name set, module_kset needs to be
initialized, which currently happens in a subsys_initcall in param_sysfs_init().
The tegra cbb drivers register themselves before module_kset init, in a
core_initcall. This works currently because lookup_or_create_module_kobject(),
which dereferences module_kset via kset_find_obj(), is not called if mod_name
is not set, which is the case now.
So in preparation for the commit "driver core: platform: set mod_name in driver registration",
move module_kset init to pure_initcall level, ensuring it happens before tegra
cbb driver registration.
Suggested-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Gary Guo <gary@garyguo.net>
Co-developed-by: Rahul Bukte <rahul.bukte@sony.com>
Signed-off-by: Rahul Bukte <rahul.bukte@sony.com>
Signed-off-by: Shashank Balaji <shashank.mahadasyam@sony.com>
---
I'm sending v6 of just this patch to add the comment suggested by Petr and pick
up Gary's Reviewed-by. The rest of the patches are the same as v5.
Danilo, I'm assuming this series goes through driver-core. Could you please pick
up this version of this patch and the v5 of the others?
---
kernel/params.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/kernel/params.c b/kernel/params.c
index 74d620bc2521..a668863a4bb6 100644
--- a/kernel/params.c
+++ b/kernel/params.c
@@ -942,9 +942,9 @@ const struct kobj_type module_ktype = {
/*
* param_sysfs_init - create "module" kset
*
- * This must be done before the initramfs is unpacked and
- * request_module() thus becomes possible, because otherwise the
- * module load would fail in mod_sysfs_init.
+ * This must be done before any driver registration so that when a driver comes
+ * from a built-in module, the driver core can add the module under /sys/module
+ * and create the associated driver symlinks.
*/
static int __init param_sysfs_init(void)
{
@@ -957,7 +957,7 @@ static int __init param_sysfs_init(void)
return 0;
}
-subsys_initcall(param_sysfs_init);
+pure_initcall(param_sysfs_init);
/*
* param_sysfs_builtin_init - add sysfs version and parameter
--
2.43.0
^ permalink raw reply related
* Re: [PATCH mm-unstable v18 06/14] mm/khugepaged: generalize collapse_huge_page for mTHP collapse
From: David Hildenbrand (Arm) @ 2026-06-01 10:23 UTC (permalink / raw)
To: Lance Yang
Cc: npache, linux-doc, linux-kernel, linux-mm, linux-trace-kernel,
aarcange, akpm, anshuman.khandual, apopple, baohua, baolin.wang,
byungchul, catalin.marinas, cl, corbet, dave.hansen, dev.jain,
gourry, hannes, hughd, jack, jackmanb, jannh, jglisse,
joshua.hahnjy, kas, liam, ljs, mathieu.desnoyers, matthew.brost,
mhiramat, mhocko, peterx, pfalcato, rakie.kim, raquini, rdunlap,
richard.weiyang, rientjes, rostedt, rppt, ryan.roberts, shivankg,
sunnanyong, surenb, thomas.hellstrom, tiwai, usamaarif642, vbabka,
vishal.moola, wangkefeng.wang, will, willy, yang, ying.huang, ziy,
zokeefe, usama.arif
In-Reply-To: <616de1a8-1cfd-40b8-b04f-7b324be40bfd@linux.dev>
On 6/1/26 11:08, Lance Yang wrote:
>
>
> On 2026/6/1 14:54, David Hildenbrand (Arm) wrote:
>> On 6/1/26 05:28, Lance Yang wrote:
>>>
>>>
>>> Ah, fair point.
>>>
>>> I was mostly worried about arch hooks that walk vma->vm_mm again, rather
>>> than only using the pte pointer passed in. For example, mips does:
>>
>> Right, a re-walk would be the real problem.
>>
>>>
>>> update_mmu_cache_range()
>>> -> __update_tlb()
>>> -> pgd_offset(vma->vm_mm, address)
>>> -> pte_offset_map(...)
>>>
>>> and __update_tlb() has this assumption:
>>>
>>> /*
>>> * update_mmu_cache() is called between pte_offset_map_lock()
>>> * and pte_unmap_unlock(), so we can assume that ptep is not
>>> * NULL here: and what should be done below if it were NULL?
>>> */
>>>
>>> So if khugepaged happens to run with current->active_mm == vma->vm_mm
>>> here, could __update_tlb() hit the none PMD, get NULL from
>>> pte_offset_map(), and then dereference it?
>>
>> Likely yes -- that MIPS code is horrible. And the comment in MIPS code
>> even spells that out. :(
>>
>> Do you know about other code like that, or is MIPS the only one doing a
>> re-walk and crossing fingers?
>>
>>>
>>> Just wanted to raise it since some arch code may still have assumptions
>>> like this, and the always-enable-mTHP work is getting closer ...
>>
>> Right. I assume set_pte_at() couldn't trigger something similar (re-walk) in
>> arch code,
>> because we simply provide the ptep. update_mmu_cache_range() only consumes the
>> pte.
>>
>>>
>>> Probably very very very hard to hit, though :)
>>
>> Delaying update_mmu_cache_range() is nasty, as we'd have to make sure that
>> nobody can interfere in the meantime ... and the PMD lock will not be sufficient.
>>
>> Maybe we could reinstall the page table with the cleared (none) entries while
>> still holding the PTL?
>>
>> Thinking out loud:
>>
>> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
>> index 5ba298d420b7..e39b750b1e6f 100644
>> --- a/mm/khugepaged.c
>> +++ b/mm/khugepaged.c
>> @@ -1413,13 +1413,17 @@ static enum scan_result collapse_huge_page(struct
>> mm_struct *mm, unsigned long s
>> map_anon_folio_pmd_nopf(folio, pmd, vma, pmd_addr);
>> } else {
>> /*
>> - * set_ptes is called in map_anon_folio_pte_nopf with the
>> - * pmd_ptl lock still held; this is safe as the PMD is expected
>> - * to be none. The pmd entry is then repopulated below.
>> + * Re-insert the page table with the cleared entries, but
>> + * hold the PTL, such that no one can mess with the re-installed
>> + * page table until we updated the temporarily-cleared entries
>> + * through map_anon_folio_pte_nopf().
>> */
>> - map_anon_folio_pte_nopf(folio, pte, vma, start_addr, /
>> *uffd_wp=*/ false);
>> - smp_wmb(); /* make PTEs visible before PMD. See pmd_install() */
>
> One small thing, I think we should probably keep the smp_wmb(), and just
> move it before the earlier pmd_populate().
>
> IIUC, the ordering we want is still:
>
> clear old PTEs
> smp_wmb()
> pmd_populate()
>
> so another CPU cannot walk through the re-installed PMD and still observe
> the old PTEs, right?
There is a smp_wmb() in __folio_mark_uptodate(), that should be sufficient?
--
Cheers,
David
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox