* Re: [PATCH mm-unstable v19 05/14] mm/khugepaged: require collapse_huge_page to enter/exit with the lock dropped
From: Zi Yan @ 2026-06-05 20:07 UTC (permalink / raw)
To: Nico Pache
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,
lance.yang, 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,
zokeefe
In-Reply-To: <20260605161422.213817-6-npache@redhat.com>
On 5 Jun 2026, at 12:14, Nico Pache wrote:
> Currently the collapse_huge_page function requires the mmap_read_lock to
> enter with it held, and exit with it dropped. This function moves the
> unlock into its parent caller, and changes this semantic to requiring it
> to enter/exit with it always unlocked.
>
> In future patches, we need this expectation, as for in mTHP collapse, we
> may have already dropped the lock, and do not want to conditionally
> check for this by passing through the lock_dropped variable.
>
> No functional change is expected as one of the first things the
> collapse_huge_page function does is drop this lock before allocating the
> hugepage.
>
> Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> Signed-off-by: Nico Pache <npache@redhat.com>
> ---
> mm/khugepaged.c | 16 ++++++++--------
> 1 file changed, 8 insertions(+), 8 deletions(-)
>
LGTM.
Reviewed-by: Zi Yan <ziy@nvidia.com>
Best Regards,
Yan, Zi
^ permalink raw reply
* [PATCH v3 0/6] alloc_tag: introduce IOCTL-based filtering for MAP
From: Abhishek Bapat @ 2026-06-05 23:36 UTC (permalink / raw)
To: Suren Baghdasaryan, Andrew Morton, Kent Overstreet, Hao Ge
Cc: Shuah Khan, Jonathan Corbet, linux-doc, linux-kernel, linux-mm,
Sourav Panda, Abhishek Bapat
Currently, memory allocation profiling data is primarily exposed through
/proc/allocinfo. While useful for manual inspection, this text-based
interface poses challenges for production monitoring and large-scale
analysis:
1. Userspace must parse large amounts of text to extract specific
fields.
2. To find specific tags, userspace must read the entire dataset,
requiring many context switches and high data copying.
3. The kernel currently aggregates per-CPU counters for every allocation
size, even those the user intends to filter out immediately.
This series introduces a new IOCTL-based binary interface for allocinfo
that supports kernel-side filtering. By allowing the user to specify a
filter mask, we significantly reduce the work performed in-kernel and
the amount of data transferred to userspace.
Performance measurements were conducted on an Intel Xeon Platinum 8481C
(224 CPUs) with caches dropped before each run.
The IOCTL mechanism shows a ~20x performance improvement for
filtered queries. The kernel avoids the expensive per-CPU counter
aggregation (alloc_tag_read) for any tags that fail the initial string
or location filters.
Scenario 1: Specific File Filtering (arch/x86/events/rapl.c)
1. Traditional (cat /proc/allocinfo | grep): 22ms (sys)
2. IOCTL Interface: 1ms (sys)
Scenario 2: Compound Filtering (Filename + Size)
1. Traditional: (cat ... | grep | awk): 21ms (sys)
2. IOCTL Interface: 1ms (sys)
Scenario 3: Size-Based Filtering (min_size = 1MB)
1. Traditional: (cat ... | awk): 21ms (sys)
2. IOCTL Interface: 14ms (sys)
v2 changes:
- Patch 1/6: Introduced locking for m->private. Also included the new uapi
header file in MAINTAINERS list.
- Patch 2/6: Handled the case where ALLOCINFO_FILTER_MASK_MODNAME is
passed but ct->modname is NULL.
- Patch 3/6: Moved min_size and max_size outside of struct allocinfo_tag
into struct allocinfo_filter. Added validation that min_size <=
max_size. Prefetched alloc_tag_counters if size based filter masks are
provided to avoid assimilating per-cpu counters twice.
- Patch 5/6: Removed the hardcoded logic to skip the header, instead the
test will skip lines that don't match the format. Also included the
newly added alloc_tag selftests directory in MAINTAINERS list.
v3 changes:
- Patch 1/6: Modified Documentation to indicate that map supports
ioctl(). Modified struct allocinfo_count to use
__attribute__((aligned(8))) instead of manual padding. Removed
redundance type-casting. Added comments for static functions in
lib/alloc_tag.c. Introduced a new seq counter for content_id that gets
bumped every time module is loaded / unloaded. Introduced logic to
validate user specified position is not greater than number of
allocation tags and return early if it is. Changed strscpy to
strscpy_pad to not echo arbitrary user data back to the user.
- Patch 2/6: Handled the case where user wants to specifically filter
for built-in modules. Included some comments for static functions.
- Patch 3/6: Modified logic to only fetch per-CPU counters for codetags
that satisfy other filters. Included some comments for static
functions.
Abhishek Bapat (5):
alloc_tag: add ioctl filters to /proc/allocinfo
alloc_tag: add size-based filtering to ioctl
alloc_tag: add accuracy based filtering to ioctl
kselftest: alloc_tag: add kselftest for ioctl interface
kselftest: alloc_tag: extend the allocinfo ioctl kselftest
Suren Baghdasaryan (1):
alloc_tag: add ioctl to /proc/allocinfo
Documentation/mm/allocation-profiling.rst | 5 +
.../userspace-api/ioctl/ioctl-number.rst | 2 +
MAINTAINERS | 2 +
include/linux/codetag.h | 2 +
include/uapi/linux/alloc_tag.h | 87 +++
lib/alloc_tag.c | 342 +++++++++++-
lib/codetag.c | 18 +
tools/testing/selftests/alloc_tag/Makefile | 9 +
.../alloc_tag/allocinfo_ioctl_test.c | 505 ++++++++++++++++++
9 files changed, 970 insertions(+), 2 deletions(-)
create mode 100644 include/uapi/linux/alloc_tag.h
create mode 100644 tools/testing/selftests/alloc_tag/Makefile
create mode 100644 tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply
* [PATCH v3 1/6] alloc_tag: add ioctl to /proc/allocinfo
From: Abhishek Bapat @ 2026-06-05 23:36 UTC (permalink / raw)
To: Suren Baghdasaryan, Andrew Morton, Kent Overstreet, Hao Ge
Cc: Shuah Khan, Jonathan Corbet, linux-doc, linux-kernel, linux-mm,
Sourav Panda, Abhishek Bapat
In-Reply-To: <cover.1780701922.git.abhishekbapat@google.com>
From: Suren Baghdasaryan <surenb@google.com>
Add the following ioctl commands for /proc/allocinfo file:
ALLOCINFO_IOC_CONTENT_ID - gets content identifier which can be used
to check whether the file content has changed specifically due to module
load/unload. Every time a module is loaded / unloaded, the returned
value will be different. By comparing the identifier value at the
beginning and at the end of the content retrieval operation, users can
validate retrieved information for consistency.
ALLOCINFO_IOC_GET_AT - gets the record at the specified position. This
is the position of a record in /proc/allocinfo.
ALLOCINFO_IOC_GET_NEXT - gets the record next to the last retrieved
one. If no records were previously retrieved, returns the first
record.
Signed-off-by: Suren Baghdasaryan <surenb@google.com>
Signed-off-by: Abhishek Bapat <abhishekbapat@google.com>
---
Documentation/mm/allocation-profiling.rst | 5 +
.../userspace-api/ioctl/ioctl-number.rst | 2 +
MAINTAINERS | 1 +
include/linux/codetag.h | 2 +
include/uapi/linux/alloc_tag.h | 54 ++++
lib/alloc_tag.c | 232 +++++++++++++++++-
lib/codetag.c | 18 ++
7 files changed, 312 insertions(+), 2 deletions(-)
create mode 100644 include/uapi/linux/alloc_tag.h
diff --git a/Documentation/mm/allocation-profiling.rst b/Documentation/mm/allocation-profiling.rst
index 5389d241176a..c3a28467955f 100644
--- a/Documentation/mm/allocation-profiling.rst
+++ b/Documentation/mm/allocation-profiling.rst
@@ -46,6 +46,11 @@ sysctl:
Runtime info:
/proc/allocinfo
+ Profiling data can be retrieved either by reading `/proc/allocinfo` directly as
+ text or programmatically via `ioctl()` calls defined in `<uapi/linux/alloc_tag.h>`.
+ The ioctl interface supports structured binary data extraction as well as filtering
+ by module name, function, file, line number, accuracy, or allocation size limits.
+
Example output::
root@moria-kvm:~# sort -g /proc/allocinfo|tail|numfmt --to=iec
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 331223761fff..84f6808a8578 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -349,6 +349,8 @@ Code Seq# Include File Comments
<mailto:luzmaximilian@gmail.com>
0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver
<mailto:luzmaximilian@gmail.com>
+0xA6 00-0F uapi/linux/alloc_tag.h Memory allocation profiling
+ <mailto:surenb@google.com>
0xAA 00-3F linux/uapi/linux/userfaultfd.h
0xAB 00-1F linux/nbd.h
0xAC 00-1F linux/raw.h
diff --git a/MAINTAINERS b/MAINTAINERS
index a31f6f207afd..77f3fc487691 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16711,6 +16711,7 @@ S: Maintained
F: Documentation/mm/allocation-profiling.rst
F: include/linux/alloc_tag.h
F: include/linux/pgalloc_tag.h
+F: include/uapi/linux/alloc_tag.h
F: lib/alloc_tag.c
MEMORY CONTROLLER DRIVERS
diff --git a/include/linux/codetag.h b/include/linux/codetag.h
index ddae7484ca45..a25a085c2df1 100644
--- a/include/linux/codetag.h
+++ b/include/linux/codetag.h
@@ -77,6 +77,8 @@ struct codetag_iterator {
void codetag_lock_module_list(struct codetag_type *cttype);
bool codetag_trylock_module_list(struct codetag_type *cttype);
void codetag_unlock_module_list(struct codetag_type *cttype);
+unsigned long codetag_get_content_id(struct codetag_type *cttype);
+unsigned int codetag_get_count(struct codetag_type *cttype);
struct codetag_iterator codetag_get_ct_iter(struct codetag_type *cttype);
struct codetag *codetag_next_ct(struct codetag_iterator *iter);
diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h
new file mode 100644
index 000000000000..901199bad514
--- /dev/null
+++ b/include/uapi/linux/alloc_tag.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * include/linux/alloc_tag.h
+ */
+
+#ifndef _UAPI_ALLOC_TAG_H
+#define _UAPI_ALLOC_TAG_H
+
+#include <linux/types.h>
+
+#define ALLOCINFO_STR_SIZE 64
+
+struct allocinfo_content_id {
+ __u64 id;
+};
+
+struct allocinfo_tag {
+ /* Longer names are trimmed */
+ char modname[ALLOCINFO_STR_SIZE];
+ char function[ALLOCINFO_STR_SIZE];
+ char filename[ALLOCINFO_STR_SIZE];
+ __u64 lineno;
+};
+
+/* The alignment ensures 32-bit compatible interfaces are not broken */
+struct allocinfo_counter {
+ __u64 bytes;
+ __u64 calls;
+ __u8 accurate;
+} __attribute__((aligned(8)));
+
+struct allocinfo_tag_data {
+ struct allocinfo_tag tag;
+ struct allocinfo_counter counter;
+};
+
+struct allocinfo_get_at {
+ __u64 pos; /* input */
+ struct allocinfo_tag_data data;
+};
+
+#define _ALLOCINFO_IOC_CONTENT_ID 0
+#define _ALLOCINFO_IOC_GET_AT 1
+#define _ALLOCINFO_IOC_GET_NEXT 2
+
+#define ALLOCINFO_IOC_BASE 0xA6
+#define ALLOCINFO_IOC_CONTENT_ID _IOR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_CONTENT_ID, \
+ struct allocinfo_content_id)
+#define ALLOCINFO_IOC_GET_AT _IOWR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_GET_AT, \
+ struct allocinfo_get_at)
+#define ALLOCINFO_IOC_GET_NEXT _IOR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_GET_NEXT, \
+ struct allocinfo_tag_data)
+
+#endif /* _UAPI_ALLOC_TAG_H */
diff --git a/lib/alloc_tag.c b/lib/alloc_tag.c
index d9be1cf5187d..a0577215eb3d 100644
--- a/lib/alloc_tag.c
+++ b/lib/alloc_tag.c
@@ -5,6 +5,7 @@
#include <linux/gfp.h>
#include <linux/kallsyms.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/page_ext.h>
#include <linux/pgalloc_tag.h>
#include <linux/proc_fs.h>
@@ -14,6 +15,7 @@
#include <linux/string_choices.h>
#include <linux/vmalloc.h>
#include <linux/kmemleak.h>
+#include <uapi/linux/alloc_tag.h>
#define ALLOCINFO_FILE_NAME "allocinfo"
#define MODULE_ALLOC_TAG_VMAP_SIZE (100000UL * sizeof(struct alloc_tag))
@@ -47,6 +49,10 @@ struct allocinfo_private {
struct codetag_iterator iter;
struct codetag_iterator reported_iter;
bool print_header;
+ /* ioctl uses a separate iterator not to interfere with reads */
+ struct codetag_iterator ioctl_iter;
+ bool positioned; /* seq_open_private() sets to 0 */
+ struct mutex ioctl_lock;
};
static void *allocinfo_start(struct seq_file *m, loff_t *pos)
@@ -130,6 +136,229 @@ static const struct seq_operations allocinfo_seq_op = {
.show = allocinfo_show,
};
+/*
+ * Initializes seq_file operations and allocates private state when opening
+ * the /proc/allocinfo procfs entry.
+ */
+static int allocinfo_open(struct inode *inode, struct file *file)
+{
+ int ret;
+
+ ret = seq_open_private(file, &allocinfo_seq_op,
+ sizeof(struct allocinfo_private));
+ if (!ret) {
+ struct seq_file *m = file->private_data;
+ struct allocinfo_private *priv = m->private;
+
+ mutex_init(&priv->ioctl_lock);
+ }
+ return ret;
+}
+
+/*
+ * Cleans up the seq_file state and frees up the private state allocated in
+ * allocinfo_open() when closing the /proc/allocinfo file descriptor.
+ */
+static int allocinfo_release(struct inode *inode, struct file *file)
+{
+ return seq_release_private(inode, file);
+}
+
+/*
+ * Returns a pointer to the suffix of a string so that its length fits within
+ * ALLOCINFO_STR_SIZE, preserving the trailing characters.
+ */
+static const char *allocinfo_str(const char *str)
+{
+ size_t len = strlen(str);
+
+ /* Keep an extra space for the trailing NULL. */
+ if (len >= ALLOCINFO_STR_SIZE)
+ str += (len - ALLOCINFO_STR_SIZE) + 1;
+ return str;
+}
+
+/* Copy a string and trim from the beginning if it's too long */
+static void allocinfo_copy_str(char *dest, const char *src)
+{
+ strscpy_pad(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE);
+}
+
+/*
+ * Populates the UAPI allocinfo_tag_data structure with active runtime
+ * profiling counters extracted from the given kernel codetag.
+ */
+static void allocinfo_to_params(struct codetag *ct,
+ struct allocinfo_tag_data *data)
+{
+ struct alloc_tag *tag = ct_to_alloc_tag(ct);
+ struct alloc_tag_counters counter = alloc_tag_read(tag);
+
+ if (ct->modname)
+ allocinfo_copy_str(data->tag.modname, ct->modname);
+ else
+ data->tag.modname[0] = '\0';
+ allocinfo_copy_str(data->tag.function, ct->function);
+ allocinfo_copy_str(data->tag.filename, ct->filename);
+ data->tag.lineno = ct->lineno;
+ data->counter.bytes = counter.bytes;
+ data->counter.calls = counter.calls;
+ data->counter.accurate = !alloc_tag_is_inaccurate(tag);
+}
+
+/*
+ * Retrieves the unique content ID representing the current allocation tag module
+ * layout, allowing userspace to detect if modules were loaded / unloaded.
+ */
+static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg)
+{
+ struct allocinfo_content_id params;
+
+ codetag_lock_module_list(alloc_tag_cttype);
+ params.id = codetag_get_content_id(alloc_tag_cttype);
+ codetag_unlock_module_list(alloc_tag_cttype);
+ if (copy_to_user(arg, ¶ms, sizeof(params)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * Seeks the ioctl iterator to the specified 0-indexed tag position, reads its
+ * profiling data and returns it to userspace.
+ */
+static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
+{
+ struct allocinfo_private *priv;
+ struct codetag *ct;
+ __u64 pos;
+ struct allocinfo_get_at params = {0};
+
+ if (copy_from_user(¶ms, arg, sizeof(params)))
+ return -EFAULT;
+
+ priv = m->private;
+ pos = params.pos;
+
+ mutex_lock(&priv->ioctl_lock);
+ codetag_lock_module_list(alloc_tag_cttype);
+
+ if (pos >= codetag_get_count(alloc_tag_cttype)) {
+ codetag_unlock_module_list(alloc_tag_cttype);
+ mutex_unlock(&priv->ioctl_lock);
+ return -ENOENT;
+ }
+
+ /* Find the codetag */
+ priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype);
+ ct = codetag_next_ct(&priv->ioctl_iter);
+ while (ct && pos--)
+ ct = codetag_next_ct(&priv->ioctl_iter);
+ if (ct) {
+ allocinfo_to_params(ct, ¶ms.data);
+ priv->positioned = true;
+ }
+
+ codetag_unlock_module_list(alloc_tag_cttype);
+ mutex_unlock(&priv->ioctl_lock);
+
+ if (!ct)
+ return -ENOENT;
+
+ if (copy_to_user(arg, ¶ms, sizeof(params)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * Advances the ioctl iterator to the next allocation tag in the sequence and
+ * returns its profiling data to userspace.
+ */
+static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg)
+{
+ struct allocinfo_private *priv;
+ struct codetag *ct;
+ struct allocinfo_tag_data params;
+ int ret = 0;
+
+ memset(¶ms, 0, sizeof(params));
+ priv = m->private;
+
+ mutex_lock(&priv->ioctl_lock);
+ codetag_lock_module_list(alloc_tag_cttype);
+
+ if (!priv->positioned) {
+ priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype);
+ priv->positioned = true;
+ }
+
+ ct = codetag_next_ct(&priv->ioctl_iter);
+ if (ct)
+ allocinfo_to_params(ct, ¶ms);
+
+ if (!ct) {
+ priv->positioned = false;
+ ret = -ENOENT;
+ }
+ codetag_unlock_module_list(alloc_tag_cttype);
+ mutex_unlock(&priv->ioctl_lock);
+
+ if (ret == 0) {
+ if (copy_to_user(arg, ¶ms, sizeof(params)))
+ return -EFAULT;
+ }
+ return ret;
+}
+
+/*
+ * Entry point ioctl function for /proc/allocinfo routing requests to fetch the
+ * layout content ID, seek to a specific tag, or read sequential tags.
+ */
+static long allocinfo_ioctl(struct file *file, unsigned int cmd,
+ unsigned long __arg)
+{
+ void __user *arg = (void __user *)__arg;
+ int ret;
+
+ switch (cmd) {
+ case ALLOCINFO_IOC_CONTENT_ID:
+ ret = allocinfo_ioctl_get_content_id(file->private_data, arg);
+ break;
+ case ALLOCINFO_IOC_GET_AT:
+ ret = allocinfo_ioctl_get_at(file->private_data, arg);
+ break;
+ case ALLOCINFO_IOC_GET_NEXT:
+ ret = allocinfo_ioctl_get_next(file->private_data, arg);
+ break;
+ default:
+ ret = -ENOIOCTLCMD;
+ break;
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_COMPAT
+static long allocinfo_compat_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ return allocinfo_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
+static const struct proc_ops allocinfo_proc_ops = {
+ .proc_open = allocinfo_open,
+ .proc_read_iter = seq_read_iter,
+ .proc_lseek = seq_lseek,
+ .proc_release = allocinfo_release,
+ .proc_ioctl = allocinfo_ioctl,
+#ifdef CONFIG_COMPAT
+ .proc_compat_ioctl = allocinfo_compat_ioctl,
+#endif
+
+};
+
size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep)
{
struct codetag_iterator iter;
@@ -993,8 +1222,7 @@ static int __init alloc_tag_init(void)
return 0;
}
- if (!proc_create_seq_private(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_seq_op,
- sizeof(struct allocinfo_private), NULL)) {
+ if (!proc_create(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_proc_ops)) {
pr_err("Failed to create %s file\n", ALLOCINFO_FILE_NAME);
shutdown_mem_profiling(false);
return -ENOMEM;
diff --git a/lib/codetag.c b/lib/codetag.c
index 4001a7ea6675..a9cda4c962a3 100644
--- a/lib/codetag.c
+++ b/lib/codetag.c
@@ -19,6 +19,8 @@ struct codetag_type {
struct codetag_type_desc desc;
/* generates unique sequence number for module load */
unsigned long next_mod_seq;
+ /* bumped on every module load and unload */
+ unsigned long content_id;
};
struct codetag_range {
@@ -50,6 +52,20 @@ void codetag_unlock_module_list(struct codetag_type *cttype)
up_read(&cttype->mod_lock);
}
+unsigned long codetag_get_content_id(struct codetag_type *cttype)
+{
+ lockdep_assert_held(&cttype->mod_lock);
+
+ return cttype->content_id;
+}
+
+unsigned int codetag_get_count(struct codetag_type *cttype)
+{
+ lockdep_assert_held(&cttype->mod_lock);
+
+ return cttype->count;
+}
+
struct codetag_iterator codetag_get_ct_iter(struct codetag_type *cttype)
{
struct codetag_iterator iter = {
@@ -204,6 +220,7 @@ static int codetag_module_init(struct codetag_type *cttype, struct module *mod)
down_write(&cttype->mod_lock);
cmod->mod_seq = ++cttype->next_mod_seq;
+ ++cttype->content_id;
mod_id = idr_alloc(&cttype->mod_idr, cmod, 0, 0, GFP_KERNEL);
if (mod_id >= 0) {
if (cttype->desc.module_load) {
@@ -368,6 +385,7 @@ void codetag_unload_module(struct module *mod)
cttype->count -= range_size(cttype, &cmod->range);
idr_remove(&cttype->mod_idr, mod_id);
kfree(cmod);
+ ++cttype->content_id;
}
up_write(&cttype->mod_lock);
if (found && cttype->desc.free_section_mem)
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v3 3/6] alloc_tag: add size-based filtering to ioctl
From: Abhishek Bapat @ 2026-06-05 23:36 UTC (permalink / raw)
To: Suren Baghdasaryan, Andrew Morton, Kent Overstreet, Hao Ge
Cc: Shuah Khan, Jonathan Corbet, linux-doc, linux-kernel, linux-mm,
Sourav Panda, Abhishek Bapat
In-Reply-To: <cover.1780701922.git.abhishekbapat@google.com>
Extend the allocinfo filtering mechanism to allow users to filter tags
based on the total number of bytes allocated [min_size, max_size]. The
size range is inclusive.
Filtering by size involves retrieving allocinfo per-CPU counters, which
is an expensive operation. Hence, the performance of size-based
filtering will be worse than other filters.
Signed-off-by: Abhishek Bapat <abhishekbapat@google.com>
---
include/uapi/linux/alloc_tag.h | 8 +++-
lib/alloc_tag.c | 68 +++++++++++++++++++++++++++-------
2 files changed, 62 insertions(+), 14 deletions(-)
diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h
index cffb0c46e0b1..0e648192df4d 100644
--- a/include/uapi/linux/alloc_tag.h
+++ b/include/uapi/linux/alloc_tag.h
@@ -39,13 +39,17 @@ enum {
ALLOCINFO_FILTER_FUNCTION,
ALLOCINFO_FILTER_FILENAME,
ALLOCINFO_FILTER_LINENO,
- __ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_LINENO
+ ALLOCINFO_FILTER_MIN_SIZE,
+ ALLOCINFO_FILTER_MAX_SIZE,
+ __ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_MAX_SIZE
};
#define ALLOCINFO_FILTER_MASK_MODNAME (1 << ALLOCINFO_FILTER_MODNAME)
#define ALLOCINFO_FILTER_MASK_FUNCTION (1 << ALLOCINFO_FILTER_FUNCTION)
#define ALLOCINFO_FILTER_MASK_FILENAME (1 << ALLOCINFO_FILTER_FILENAME)
#define ALLOCINFO_FILTER_MASK_LINENO (1 << ALLOCINFO_FILTER_LINENO)
+#define ALLOCINFO_FILTER_MASK_MIN_SIZE (1 << ALLOCINFO_FILTER_MIN_SIZE)
+#define ALLOCINFO_FILTER_MASK_MAX_SIZE (1 << ALLOCINFO_FILTER_MAX_SIZE)
#define ALLOCINFO_FILTER_MASKS \
((1 << (__ALLOCINFO_FILTER_LAST + 1)) - 1)
@@ -53,6 +57,8 @@ enum {
struct allocinfo_filter {
__u64 mask; /* bitmask of the filter fields used */
struct allocinfo_tag fields;
+ __u64 min_size;
+ __u64 max_size;
};
struct allocinfo_get_at {
diff --git a/lib/alloc_tag.c b/lib/alloc_tag.c
index 93bc976ac505..ddc6946f56ab 100644
--- a/lib/alloc_tag.c
+++ b/lib/alloc_tag.c
@@ -191,15 +191,26 @@ static int allocinfo_cmp_str(const char *str, const char *template)
return strncmp(allocinfo_str(str), template, ALLOCINFO_STR_SIZE);
}
+/* Fetch the per-CPU counters */
+static inline struct alloc_tag_counters allocinfo_prefetch_counters(struct codetag *ct)
+{
+ return alloc_tag_read(ct_to_alloc_tag(ct));
+}
+
/*
* Populates the UAPI allocinfo_tag_data structure with active runtime
* profiling counters extracted from the given kernel codetag.
*/
static void allocinfo_to_params(struct codetag *ct,
- struct allocinfo_tag_data *data)
+ struct allocinfo_tag_data *data,
+ struct alloc_tag_counters *counters)
{
- struct alloc_tag *tag = ct_to_alloc_tag(ct);
- struct alloc_tag_counters counter = alloc_tag_read(tag);
+ struct alloc_tag_counters local_counters;
+
+ if (!counters) {
+ local_counters = allocinfo_prefetch_counters(ct);
+ counters = &local_counters;
+ }
if (ct->modname)
allocinfo_copy_str(data->tag.modname, ct->modname);
@@ -208,9 +219,9 @@ static void allocinfo_to_params(struct codetag *ct,
allocinfo_copy_str(data->tag.function, ct->function);
allocinfo_copy_str(data->tag.filename, ct->filename);
data->tag.lineno = ct->lineno;
- data->counter.bytes = counter.bytes;
- data->counter.calls = counter.calls;
- data->counter.accurate = !alloc_tag_is_inaccurate(tag);
+ data->counter.bytes = counters->bytes;
+ data->counter.calls = counters->calls;
+ data->counter.accurate = !alloc_tag_is_inaccurate(ct_to_alloc_tag(ct));
}
/*
@@ -234,7 +245,9 @@ static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg)
* Verifies whether a given codetag satisfies the active filtering criteria by
* matching it's characteristics against the specified filter.
*/
-static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter)
+static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter,
+ struct alloc_tag_counters *counters,
+ bool *fetched_counters)
{
if (!filter || !filter->mask)
return true;
@@ -247,20 +260,34 @@ static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter)
return false;
} else if (allocinfo_cmp_str(ct->modname, filter->fields.modname))
return false;
+ }
}
if ((filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) &&
- ct->function && (allocinfo_cmp_str(ct->function, filter->fields.function)))
+ ct->function && allocinfo_cmp_str(ct->function, filter->fields.function))
return false;
if ((filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) &&
- ct->filename && (allocinfo_cmp_str(ct->filename, filter->fields.filename)))
+ ct->filename && allocinfo_cmp_str(ct->filename, filter->fields.filename))
return false;
if ((filter->mask & ALLOCINFO_FILTER_MASK_LINENO) &&
ct->lineno != filter->fields.lineno)
return false;
+ if (filter->mask & (ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE)) {
+ if (!*fetched_counters) {
+ *counters = allocinfo_prefetch_counters(ct);
+ *fetched_counters = true;
+ }
+ if ((filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) &&
+ counters->bytes < filter->min_size)
+ return false;
+ if ((filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) &&
+ counters->bytes > filter->max_size)
+ return false;
+ }
+
return true;
}
@@ -274,6 +301,8 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
struct codetag *ct;
struct allocinfo_get_at params = {0};
__u64 skip_count;
+ struct alloc_tag_counters counters;
+ bool fetched_counters;
if (copy_from_user(¶ms, arg, sizeof(params)))
return -EFAULT;
@@ -281,6 +310,11 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS)
return -EINVAL;
+ if ((params.filter.mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) &&
+ (params.filter.mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) &&
+ params.filter.min_size > params.filter.max_size)
+ return -EINVAL;
+
priv = m->private;
mutex_lock(&priv->ioctl_lock);
@@ -304,7 +338,8 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
ct = codetag_next_ct(&priv->ioctl_iter);
while (ct) {
- if (matches_filter(ct, &priv->filter)) {
+ fetched_counters = false;
+ if (matches_filter(ct, &priv->filter, &counters, &fetched_counters)) {
if (skip_count == 0)
break;
skip_count--;
@@ -313,7 +348,7 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
}
if (ct) {
- allocinfo_to_params(ct, ¶ms.data);
+ allocinfo_to_params(ct, ¶ms.data, fetched_counters ? &counters : NULL);
priv->positioned = true;
}
@@ -339,6 +374,8 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg)
struct codetag *ct;
struct allocinfo_tag_data params;
int ret = 0;
+ struct alloc_tag_counters counters;
+ bool fetched_counters;
memset(¶ms, 0, sizeof(params));
priv = m->private;
@@ -352,10 +389,15 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg)
}
ct = codetag_next_ct(&priv->ioctl_iter);
- while (ct && !matches_filter(ct, &priv->filter))
+ while (ct) {
+ fetched_counters = false;
+ if (matches_filter(ct, &priv->filter, &counters, &fetched_counters))
+ break;
ct = codetag_next_ct(&priv->ioctl_iter);
+ }
+
if (ct)
- allocinfo_to_params(ct, ¶ms);
+ allocinfo_to_params(ct, ¶ms, fetched_counters ? &counters : NULL);
if (!ct) {
priv->positioned = false;
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v3 2/6] alloc_tag: add ioctl filters to /proc/allocinfo
From: Abhishek Bapat @ 2026-06-05 23:36 UTC (permalink / raw)
To: Suren Baghdasaryan, Andrew Morton, Kent Overstreet, Hao Ge
Cc: Shuah Khan, Jonathan Corbet, linux-doc, linux-kernel, linux-mm,
Sourav Panda, Abhishek Bapat
In-Reply-To: <cover.1780701922.git.abhishekbapat@google.com>
Extend the capability of the IOCTL mechanism to filter allocations based
on tag's module name, function name, file name and line number.
Signed-off-by: Abhishek Bapat <abhishekbapat@google.com>
---
include/uapi/linux/alloc_tag.h | 26 ++++++++++++-
lib/alloc_tag.c | 68 ++++++++++++++++++++++++++++++++--
2 files changed, 89 insertions(+), 5 deletions(-)
diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h
index 901199bad514..cffb0c46e0b1 100644
--- a/include/uapi/linux/alloc_tag.h
+++ b/include/uapi/linux/alloc_tag.h
@@ -34,8 +34,32 @@ struct allocinfo_tag_data {
struct allocinfo_counter counter;
};
+enum {
+ ALLOCINFO_FILTER_MODNAME,
+ ALLOCINFO_FILTER_FUNCTION,
+ ALLOCINFO_FILTER_FILENAME,
+ ALLOCINFO_FILTER_LINENO,
+ __ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_LINENO
+};
+
+#define ALLOCINFO_FILTER_MASK_MODNAME (1 << ALLOCINFO_FILTER_MODNAME)
+#define ALLOCINFO_FILTER_MASK_FUNCTION (1 << ALLOCINFO_FILTER_FUNCTION)
+#define ALLOCINFO_FILTER_MASK_FILENAME (1 << ALLOCINFO_FILTER_FILENAME)
+#define ALLOCINFO_FILTER_MASK_LINENO (1 << ALLOCINFO_FILTER_LINENO)
+
+#define ALLOCINFO_FILTER_MASKS \
+ ((1 << (__ALLOCINFO_FILTER_LAST + 1)) - 1)
+
+struct allocinfo_filter {
+ __u64 mask; /* bitmask of the filter fields used */
+ struct allocinfo_tag fields;
+};
+
struct allocinfo_get_at {
- __u64 pos; /* input */
+ /* inputs */
+ __u64 pos;
+ struct allocinfo_filter filter;
+ /* output */
struct allocinfo_tag_data data;
};
diff --git a/lib/alloc_tag.c b/lib/alloc_tag.c
index a0577215eb3d..93bc976ac505 100644
--- a/lib/alloc_tag.c
+++ b/lib/alloc_tag.c
@@ -49,6 +49,7 @@ struct allocinfo_private {
struct codetag_iterator iter;
struct codetag_iterator reported_iter;
bool print_header;
+ struct allocinfo_filter filter;
/* ioctl uses a separate iterator not to interfere with reads */
struct codetag_iterator ioctl_iter;
bool positioned; /* seq_open_private() sets to 0 */
@@ -184,6 +185,12 @@ static void allocinfo_copy_str(char *dest, const char *src)
strscpy_pad(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE);
}
+/* Compare two strings and only consider the trimmed suffix if s1 is too long */
+static int allocinfo_cmp_str(const char *str, const char *template)
+{
+ return strncmp(allocinfo_str(str), template, ALLOCINFO_STR_SIZE);
+}
+
/*
* Populates the UAPI allocinfo_tag_data structure with active runtime
* profiling counters extracted from the given kernel codetag.
@@ -223,6 +230,40 @@ static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg)
return 0;
}
+/*
+ * Verifies whether a given codetag satisfies the active filtering criteria by
+ * matching it's characteristics against the specified filter.
+ */
+static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter)
+{
+ if (!filter || !filter->mask)
+ return true;
+
+ if (filter->mask & ALLOCINFO_FILTER_MASK_MODNAME) {
+ /* user wants to filter by modname but ct->modname is NULL */
+ if (!ct->modname) {
+ /* validate if user was attempting to filter for built-in allocations */
+ if (filter->fields.modname[0] != '\0')
+ return false;
+ } else if (allocinfo_cmp_str(ct->modname, filter->fields.modname))
+ return false;
+ }
+
+ if ((filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) &&
+ ct->function && (allocinfo_cmp_str(ct->function, filter->fields.function)))
+ return false;
+
+ if ((filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) &&
+ ct->filename && (allocinfo_cmp_str(ct->filename, filter->fields.filename)))
+ return false;
+
+ if ((filter->mask & ALLOCINFO_FILTER_MASK_LINENO) &&
+ ct->lineno != filter->fields.lineno)
+ return false;
+
+ return true;
+}
+
/*
* Seeks the ioctl iterator to the specified 0-indexed tag position, reads its
* profiling data and returns it to userspace.
@@ -231,29 +272,46 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
{
struct allocinfo_private *priv;
struct codetag *ct;
- __u64 pos;
struct allocinfo_get_at params = {0};
+ __u64 skip_count;
if (copy_from_user(¶ms, arg, sizeof(params)))
return -EFAULT;
+ if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS)
+ return -EINVAL;
+
priv = m->private;
- pos = params.pos;
mutex_lock(&priv->ioctl_lock);
codetag_lock_module_list(alloc_tag_cttype);
- if (pos >= codetag_get_count(alloc_tag_cttype)) {
+ if (params.pos >= codetag_get_count(alloc_tag_cttype)) {
codetag_unlock_module_list(alloc_tag_cttype);
mutex_unlock(&priv->ioctl_lock);
return -ENOENT;
}
+ skip_count = params.pos;
+
+ if (params.filter.mask)
+ priv->filter = params.filter;
+ else
+ priv->filter.mask = 0;
+
/* Find the codetag */
priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype);
ct = codetag_next_ct(&priv->ioctl_iter);
- while (ct && pos--)
+
+ while (ct) {
+ if (matches_filter(ct, &priv->filter)) {
+ if (skip_count == 0)
+ break;
+ skip_count--;
+ }
ct = codetag_next_ct(&priv->ioctl_iter);
+ }
+
if (ct) {
allocinfo_to_params(ct, ¶ms.data);
priv->positioned = true;
@@ -294,6 +352,8 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg)
}
ct = codetag_next_ct(&priv->ioctl_iter);
+ while (ct && !matches_filter(ct, &priv->filter))
+ ct = codetag_next_ct(&priv->ioctl_iter);
if (ct)
allocinfo_to_params(ct, ¶ms);
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v3 4/6] alloc_tag: add accuracy based filtering to ioctl
From: Abhishek Bapat @ 2026-06-05 23:36 UTC (permalink / raw)
To: Suren Baghdasaryan, Andrew Morton, Kent Overstreet, Hao Ge
Cc: Shuah Khan, Jonathan Corbet, linux-doc, linux-kernel, linux-mm,
Sourav Panda, Abhishek Bapat
In-Reply-To: <cover.1780701922.git.abhishekbapat@google.com>
Extend the allocinfo filtering mechanism to allow users to filter tags
based on their accuracy.
Signed-off-by: Abhishek Bapat <abhishekbapat@google.com>
---
include/uapi/linux/alloc_tag.h | 3 +++
lib/alloc_tag.c | 8 ++++++++
2 files changed, 11 insertions(+)
diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h
index 0e648192df4d..42445bdb11c5 100644
--- a/include/uapi/linux/alloc_tag.h
+++ b/include/uapi/linux/alloc_tag.h
@@ -20,6 +20,7 @@ struct allocinfo_tag {
char function[ALLOCINFO_STR_SIZE];
char filename[ALLOCINFO_STR_SIZE];
__u64 lineno;
+ __u64 inaccurate;
};
/* The alignment ensures 32-bit compatible interfaces are not broken */
@@ -39,6 +40,7 @@ enum {
ALLOCINFO_FILTER_FUNCTION,
ALLOCINFO_FILTER_FILENAME,
ALLOCINFO_FILTER_LINENO,
+ ALLOCINFO_FILTER_INACCURATE,
ALLOCINFO_FILTER_MIN_SIZE,
ALLOCINFO_FILTER_MAX_SIZE,
__ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_MAX_SIZE
@@ -48,6 +50,7 @@ enum {
#define ALLOCINFO_FILTER_MASK_FUNCTION (1 << ALLOCINFO_FILTER_FUNCTION)
#define ALLOCINFO_FILTER_MASK_FILENAME (1 << ALLOCINFO_FILTER_FILENAME)
#define ALLOCINFO_FILTER_MASK_LINENO (1 << ALLOCINFO_FILTER_LINENO)
+#define ALLOCINFO_FILTER_MASK_INACCURATE (1 << ALLOCINFO_FILTER_INACCURATE)
#define ALLOCINFO_FILTER_MASK_MIN_SIZE (1 << ALLOCINFO_FILTER_MIN_SIZE)
#define ALLOCINFO_FILTER_MASK_MAX_SIZE (1 << ALLOCINFO_FILTER_MAX_SIZE)
diff --git a/lib/alloc_tag.c b/lib/alloc_tag.c
index ddc6946f56ab..cbcd12c4ef9c 100644
--- a/lib/alloc_tag.c
+++ b/lib/alloc_tag.c
@@ -249,6 +249,8 @@ static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter,
struct alloc_tag_counters *counters,
bool *fetched_counters)
{
+ bool inaccurate;
+
if (!filter || !filter->mask)
return true;
@@ -275,6 +277,12 @@ static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter,
ct->lineno != filter->fields.lineno)
return false;
+ if (filter->mask & ALLOCINFO_FILTER_MASK_INACCURATE) {
+ inaccurate = !!(ct->flags & CODETAG_FLAG_INACCURATE);
+ if (inaccurate != !!(filter->fields.inaccurate))
+ return false;
+ }
+
if (filter->mask & (ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE)) {
if (!*fetched_counters) {
*counters = allocinfo_prefetch_counters(ct);
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v3 5/6] kselftest: alloc_tag: add kselftest for ioctl interface
From: Abhishek Bapat @ 2026-06-05 23:36 UTC (permalink / raw)
To: Suren Baghdasaryan, Andrew Morton, Kent Overstreet, Hao Ge
Cc: Shuah Khan, Jonathan Corbet, linux-doc, linux-kernel, linux-mm,
Sourav Panda, Abhishek Bapat
In-Reply-To: <cover.1780701922.git.abhishekbapat@google.com>
Introduce a kselftest to verify the new IOCTL-based interface for
/proc/allocinfo. The test covers:
1. Validation of the filename filter.
2. Validation of the function filter.
The first test validates the functionality of the filename filter. Using
"mm/memory.c" as the candidate filename filter, it retrieves filtered
entries from both procfs and ioctl and matches the first VEC_MAX_ENTRIES
entries.
The second test validates the functionality of the function filter.
It uses "dup_mm" as the candidate function as we do not expect this
function name to change frequently and hence won't be needing to modify
this test often.
Note that both the tests match line no, function name and file name
fields. Bytes allocated and calls are not matched as those values may
change in the time when the data is being read from procfs and ioctl and
hence can lead to false negatives.
Signed-off-by: Abhishek Bapat <abhishekbapat@google.com>
---
MAINTAINERS | 1 +
tools/testing/selftests/alloc_tag/Makefile | 9 +
.../alloc_tag/allocinfo_ioctl_test.c | 313 ++++++++++++++++++
3 files changed, 323 insertions(+)
create mode 100644 tools/testing/selftests/alloc_tag/Makefile
create mode 100644 tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 77f3fc487691..80560f5f1292 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16713,6 +16713,7 @@ F: include/linux/alloc_tag.h
F: include/linux/pgalloc_tag.h
F: include/uapi/linux/alloc_tag.h
F: lib/alloc_tag.c
+F: tools/testing/selftests/alloc_tag/
MEMORY CONTROLLER DRIVERS
M: Krzysztof Kozlowski <krzk@kernel.org>
diff --git a/tools/testing/selftests/alloc_tag/Makefile b/tools/testing/selftests/alloc_tag/Makefile
new file mode 100644
index 000000000000..f2b8fc022c3b
--- /dev/null
+++ b/tools/testing/selftests/alloc_tag/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_GEN_PROGS := allocinfo_ioctl_test
+
+CFLAGS += -Wall
+CFLAGS += -I../../../../usr/include
+
+include ../lib.mk
+
diff --git a/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
new file mode 100644
index 000000000000..5c3c16e86c23
--- /dev/null
+++ b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/* kselftest for allocinfo ioctl
+ * allocinfo ioctl retrives allocinfo data through ioctl
+ * Copyright (C) 2026 Google, Inc.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <linux/types.h>
+#include <linux/alloc_tag.h>
+#include "../kselftest.h"
+
+#define MAX_LINE_LEN 512
+#define ALLOCINFO_PROC "/proc/allocinfo"
+
+enum ioctl_ret {
+ IOCTL_SUCCESS = 0,
+ IOCTL_FAILURE = 1,
+ IOCTL_INVALID_DATA = 2,
+};
+
+#define VEC_MAX_ENTRIES 32
+
+struct allocinfo_tag_data_vec {
+ struct allocinfo_tag_data tag[VEC_MAX_ENTRIES];
+ __u64 count;
+};
+
+static inline int __allocinfo_get_content_id(int dev_fd, struct allocinfo_content_id *params)
+{
+ return ioctl(dev_fd, ALLOCINFO_IOC_CONTENT_ID, params);
+}
+
+static inline int __allocinfo_get_at(int dev_fd, struct allocinfo_get_at *params)
+{
+ return ioctl(dev_fd, ALLOCINFO_IOC_GET_AT, params);
+}
+
+static inline int __allocinfo_get_next(int dev_fd, struct allocinfo_tag_data *params)
+{
+ return ioctl(dev_fd, ALLOCINFO_IOC_GET_NEXT, params);
+}
+
+static bool match_entry(const struct allocinfo_tag_data *procfs_entry,
+ const struct allocinfo_tag_data *tag_data,
+ bool match_bytes, bool match_calls, bool match_lineno,
+ bool match_function, bool match_filename)
+{
+ if (match_bytes && tag_data->counter.bytes != procfs_entry->counter.bytes) {
+ ksft_print_msg("size retrieved through ioctl does not match procfs\n");
+ return false;
+ }
+
+ if (match_calls && tag_data->counter.calls != procfs_entry->counter.calls) {
+ ksft_print_msg("call count retrieved through ioctl does not match procfs\n");
+ return false;
+ }
+
+ if (match_lineno && tag_data->tag.lineno != procfs_entry->tag.lineno) {
+ ksft_print_msg("lineno retrieved through ioctl does not match procfs\n");
+ return false;
+ }
+
+ if (match_function &&
+ strncmp(tag_data->tag.function, procfs_entry->tag.function, ALLOCINFO_STR_SIZE)) {
+ ksft_print_msg("function retrieved through ioctl does not match procfs\n");
+ return false;
+ }
+
+ if (match_filename &&
+ strncmp(tag_data->tag.filename, procfs_entry->tag.filename, ALLOCINFO_STR_SIZE)) {
+ ksft_print_msg("filename retrieved through ioctl does not match procfs\n");
+ return false;
+ }
+ return true;
+}
+
+static bool match_entries(const struct allocinfo_tag_data_vec *procfs_entries,
+ const struct allocinfo_tag_data_vec *tags,
+ bool match_bytes, bool match_calls, bool match_lineno,
+ bool match_function, bool match_filename)
+{
+ __u64 i;
+
+ if (procfs_entries->count != tags->count) {
+ ksft_print_msg("Entry count mismatch. ioctl entries: %llu, proc entries: %llu\n",
+ tags->count, procfs_entries->count);
+ return false;
+ }
+ for (i = 0; i < procfs_entries->count; i++) {
+ if (!match_entry(&procfs_entries->tag[i], &tags->tag[i],
+ match_bytes, match_calls, match_lineno,
+ match_function, match_filename)) {
+ ksft_print_msg("%lluth entry does not match.\n", i);
+ return false;
+ }
+ }
+ return true;
+}
+
+static int get_filtered_procfs_entries(struct allocinfo_tag_data_vec *procfs_entries,
+ const struct allocinfo_filter *filter, int fd)
+{
+ FILE *fp = fdopen(fd, "r");
+ char line[MAX_LINE_LEN];
+ int matches;
+ struct allocinfo_tag_data procfs_entry;
+
+ if (!fp) {
+ ksft_print_msg("Failed to open " ALLOCINFO_PROC " for reading\n");
+ return 1;
+ }
+ memset(procfs_entries, 0, sizeof(*procfs_entries));
+ while (fgets(line, sizeof(line), fp) && procfs_entries->count < VEC_MAX_ENTRIES) {
+
+ memset(&procfs_entry, 0, sizeof(procfs_entry));
+ matches = sscanf(line, "%llu %llu %[^:]:%llu func:%s",
+ &procfs_entry.counter.bytes,
+ &procfs_entry.counter.calls,
+ procfs_entry.tag.filename,
+ &procfs_entry.tag.lineno,
+ procfs_entry.tag.function);
+
+ if (matches != 5)
+ continue;
+
+ if (filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) {
+ if (strncmp(procfs_entry.tag.filename,
+ filter->fields.filename, ALLOCINFO_STR_SIZE))
+ continue;
+ }
+ if (filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) {
+ if (strncmp(procfs_entry.tag.function,
+ filter->fields.function, ALLOCINFO_STR_SIZE))
+ continue;
+ }
+ if (filter->mask & ALLOCINFO_FILTER_MASK_LINENO) {
+ if (procfs_entry.tag.lineno != filter->fields.lineno)
+ continue;
+ }
+ if (filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) {
+ if (procfs_entry.counter.bytes < filter->min_size)
+ continue;
+ }
+ if (filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) {
+ if (procfs_entry.counter.bytes > filter->max_size)
+ continue;
+ }
+
+ memcpy(&procfs_entries->tag[procfs_entries->count++], &procfs_entry,
+ sizeof(procfs_entry));
+ }
+ return 0;
+}
+
+static enum ioctl_ret get_filtered_ioctl_entries(struct allocinfo_tag_data_vec *tags,
+ const struct allocinfo_filter *filter, int fd,
+ __u64 start_pos)
+{
+ struct allocinfo_content_id start_cont_id, end_cont_id;
+ struct allocinfo_get_at get_at_params;
+ const int max_retries = 10;
+ int retry_count = 0;
+ int status;
+
+ /*
+ * __allocinfo_get_content_id may return different values if a kernel module was loaded
+ * between the two calls. If that happens, the data gathered cannot be considered consistent
+ * and hence needs to be fetched again to avoid flakiness.
+ */
+ do {
+ if (__allocinfo_get_content_id(fd, &start_cont_id)) {
+ ksft_print_msg("allocinfo_get_content_id failed\n");
+ return IOCTL_FAILURE;
+ }
+
+ memset(tags, 0, sizeof(*tags));
+ memset(&get_at_params, 0, sizeof(get_at_params));
+ memcpy(&get_at_params.filter, filter, sizeof(*filter));
+ get_at_params.pos = start_pos;
+ if (__allocinfo_get_at(fd, &get_at_params)) {
+ ksft_print_msg("allocinfo_get_at failed\n");
+ return IOCTL_FAILURE;
+ }
+ memcpy(&tags->tag[tags->count++], &get_at_params.data, sizeof(get_at_params.data));
+
+ while (tags->count < VEC_MAX_ENTRIES &&
+ __allocinfo_get_next(fd, &tags->tag[tags->count]) == 0)
+ tags->count++;
+
+ if (__allocinfo_get_content_id(fd, &end_cont_id)) {
+ ksft_print_msg("allocinfo_get_content_id failed\n");
+ return IOCTL_FAILURE;
+ }
+
+ if (start_cont_id.id == end_cont_id.id) {
+ status = IOCTL_SUCCESS;
+ } else {
+ ksft_print_msg("allocinfo_get_content_id mismatch, retrying...\n");
+ status = IOCTL_INVALID_DATA;
+ }
+ } while (status == IOCTL_INVALID_DATA && retry_count++ < max_retries);
+
+ return status;
+}
+
+static int run_filter_test(const struct allocinfo_filter *filter)
+{
+ int fd;
+ struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
+ struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries));
+ int ioctl_status;
+ int ret = KSFT_PASS;
+
+ if (!tags || !procfs_entries) {
+ ksft_print_msg("Memory allocation failed.\n");
+ ret = KSFT_FAIL;
+ goto freemem;
+ }
+
+ fd = open(ALLOCINFO_PROC, O_RDONLY);
+ if (fd < 0) {
+ ksft_exit_skip("Failed to open " ALLOCINFO_PROC ": %s\n", strerror(errno));
+ ret = KSFT_FAIL;
+ goto freemem;
+ }
+
+ if (get_filtered_procfs_entries(procfs_entries, filter, fd)) {
+ ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n");
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+
+ if (procfs_entries->count == 0) {
+ ksft_print_msg("No entries found in " ALLOCINFO_PROC ", skipping test\n");
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+
+ ioctl_status = get_filtered_ioctl_entries(tags, filter, fd, 0);
+ if (ioctl_status == IOCTL_INVALID_DATA) {
+ ksft_print_msg("Trouble retrieving valid IOCTL entries, skipping.\n");
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+ if (ioctl_status == IOCTL_FAILURE) {
+ ksft_print_msg("Error retrieving IOCTL entries.\n");
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+
+ if (!match_entries(procfs_entries, tags, false, false, true, true, true))
+ ret = KSFT_FAIL;
+
+exit:
+ close(fd);
+freemem:
+ free(tags);
+ free(procfs_entries);
+ return ret;
+}
+
+static int test_filename_filter(void)
+{
+ struct allocinfo_filter filter;
+ const char *target_filename = "mm/memory.c";
+
+ memset(&filter, 0, sizeof(filter));
+ filter.mask |= ALLOCINFO_FILTER_MASK_FILENAME;
+ strncpy(filter.fields.filename, target_filename, ALLOCINFO_STR_SIZE);
+
+ return run_filter_test(&filter);
+}
+
+static int test_function_filter(void)
+{
+ struct allocinfo_filter filter;
+ const char *target_function = "dup_mm";
+
+ memset(&filter, 0, sizeof(filter));
+ filter.mask |= ALLOCINFO_FILTER_MASK_FUNCTION;
+ strncpy(filter.fields.function, target_function, ALLOCINFO_STR_SIZE);
+
+ return run_filter_test(&filter);
+}
+
+int main(int argc, char *argv[])
+{
+ int ret;
+
+ ksft_set_plan(2);
+
+ ret = test_filename_filter();
+ if (ret == KSFT_SKIP)
+ ksft_test_result_skip("Skipping test_filename_filter\n");
+ else
+ ksft_test_result(ret == KSFT_PASS, "test_filename_filter\n");
+
+ ret = test_function_filter();
+ if (ret == KSFT_SKIP)
+ ksft_test_result_skip("Skipping test_function_filter\n");
+ else
+ ksft_test_result(ret == KSFT_PASS, "test_function_filter\n");
+
+ ksft_finished();
+}
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* [PATCH v3 6/6] kselftest: alloc_tag: extend the allocinfo ioctl kselftest
From: Abhishek Bapat @ 2026-06-05 23:36 UTC (permalink / raw)
To: Suren Baghdasaryan, Andrew Morton, Kent Overstreet, Hao Ge
Cc: Shuah Khan, Jonathan Corbet, linux-doc, linux-kernel, linux-mm,
Sourav Panda, Abhishek Bapat
In-Reply-To: <cover.1780701922.git.abhishekbapat@google.com>
Add the following 2 scenarios to the allocinfo ioctl kselftest:
1. Validate size based filtering
2. Validate lineno based filtering
The first test uses "do_init_module" as the candidate function for the
test. This is because the associated site will only allocate memory when
a kernel module is loaded. The return value of get_content_id() changes
every time modules are loaded or unloaded. Hence, as long as
get_content_id() values at the start and the end of the test are the
same, the memory allocated by the do_init_module call site should also
remain the same. Consequently, the test can assume consistency between
the value returned by the ioctl and the procfs resulting in less
flakiness.
Signed-off-by: Abhishek Bapat <abhishekbapat@google.com>
---
.../alloc_tag/allocinfo_ioctl_test.c | 194 +++++++++++++++++-
1 file changed, 193 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
index 5c3c16e86c23..ce3576e3cd9b 100644
--- a/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
+++ b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
@@ -291,11 +291,191 @@ static int test_function_filter(void)
return run_filter_test(&filter);
}
+static int test_size_filter(void)
+{
+ int fd;
+ struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
+ struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries));
+ struct allocinfo_filter filter;
+ int ret = KSFT_PASS;
+ __u64 target_size, i;
+ bool found = false;
+ const char *target_function = "do_init_module";
+
+ if (!tags || !procfs_entries) {
+ ksft_print_msg("Memory allocation failed.\n");
+ ret = KSFT_FAIL;
+ goto freemem;
+ }
+
+ fd = open(ALLOCINFO_PROC, O_RDONLY);
+ if (fd < 0) {
+ ksft_exit_skip("Failed to open " ALLOCINFO_PROC ": %s\n", strerror(errno));
+ ret = KSFT_FAIL;
+ goto freemem;
+ }
+
+ memset(&filter, 0, sizeof(filter));
+ filter.mask |= ALLOCINFO_FILTER_MASK_FUNCTION;
+ strncpy(filter.fields.function, target_function, ALLOCINFO_STR_SIZE);
+
+ if (get_filtered_procfs_entries(procfs_entries, &filter, fd)) {
+ ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n");
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+
+ if (procfs_entries->count == 0) {
+ ksft_print_msg("Function %s not found in procfs\n", target_function);
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+
+ /*
+ * We depend on the result of procfs entries to create the ioctl_filter. Hence we
+ * cannot recycle the run_filter_test function here.
+ */
+ target_size = procfs_entries->tag[0].counter.bytes;
+
+ memset(&filter, 0, sizeof(filter));
+ filter.mask |= ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE;
+ filter.min_size = target_size;
+ filter.max_size = target_size;
+
+ __u64 pos = 0;
+ enum ioctl_ret ioctl_status;
+
+ /*
+ * This loop is required because the first 32 entries fetched by the IOCTL based on
+ * the size parameter might not contain the exact entry that was used from procfs.
+ * If that happens, we must update pos and fetch again until we find the exact entry.
+ */
+ while (1) {
+ ioctl_status = get_filtered_ioctl_entries(tags, &filter, fd, pos);
+ if (ioctl_status == IOCTL_INVALID_DATA) {
+ ksft_print_msg("Trouble retrieving valid IOCTL entries, skipping.\n");
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+ if (ioctl_status == IOCTL_FAILURE) {
+ ksft_print_msg("Error retrieving IOCTL entries.\n");
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+
+ for (i = 0; i < tags->count; i++) {
+ if (strcmp(tags->tag[i].tag.function, target_function) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found)
+ break;
+
+ if (tags->count < VEC_MAX_ENTRIES)
+ break;
+
+ pos += tags->count;
+ }
+
+ if (!found) {
+ ksft_print_msg("Entry with function %s not found in IOCTL results\n",
+ target_function);
+ ret = KSFT_FAIL;
+ }
+
+exit:
+ close(fd);
+freemem:
+ free(tags);
+ free(procfs_entries);
+ return ret;
+}
+
+static int test_lineno_filter(void)
+{
+ int fd;
+ struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
+ struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries));
+ struct allocinfo_filter filter;
+ enum ioctl_ret ioctl_status;
+ int ret = KSFT_PASS;
+ __u64 target_lineno, i;
+
+ if (!tags || !procfs_entries) {
+ ksft_print_msg("Memory allocation failed.\n");
+ ret = KSFT_FAIL;
+ goto freemem;
+ }
+
+ fd = open(ALLOCINFO_PROC, O_RDONLY);
+ if (fd < 0) {
+ ksft_exit_skip("Failed to open " ALLOCINFO_PROC ": %s\n", strerror(errno));
+ ret = KSFT_FAIL;
+ goto freemem;
+ }
+
+ memset(&filter, 0, sizeof(filter));
+
+ if (get_filtered_procfs_entries(procfs_entries, &filter, fd)) {
+ ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n");
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+ if (procfs_entries->count == 0) {
+ ksft_print_msg("Could not retrieve procfs entries\n");
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+ /*
+ * We depend on the result of procfs entries to create the ioctl_filter. Hence we
+ * cannot recycle the run_filter_test function here.
+ */
+ target_lineno = procfs_entries->tag[0].tag.lineno;
+
+ filter.mask |= ALLOCINFO_FILTER_MASK_LINENO;
+ filter.fields.lineno = target_lineno;
+
+ ioctl_status = get_filtered_ioctl_entries(tags, &filter, fd, 0);
+ if (ioctl_status == IOCTL_INVALID_DATA) {
+ ksft_print_msg("Trouble retrieving valid IOCTL entries, skipping.\n");
+ ret = KSFT_SKIP;
+ goto exit;
+ }
+ if (ioctl_status == IOCTL_FAILURE) {
+ ksft_print_msg("Error retrieving IOCTL entries.\n");
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+
+ if (tags->count == 0) {
+ ksft_print_msg("IOCTL returned 0 matches for target lineno %llu.\n", target_lineno);
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+ for (i = 0; i < tags->count; i++) {
+ if (tags->tag[i].tag.lineno != target_lineno) {
+ ksft_print_msg("IOCTL entry %llu has incorrect lineno %llu.\n",
+ i, tags->tag[i].tag.lineno);
+ ret = KSFT_FAIL;
+ goto exit;
+ }
+ }
+
+exit:
+ close(fd);
+freemem:
+ free(tags);
+ free(procfs_entries);
+ return ret;
+}
+
int main(int argc, char *argv[])
{
int ret;
- ksft_set_plan(2);
+ ksft_set_plan(4);
ret = test_filename_filter();
if (ret == KSFT_SKIP)
@@ -309,5 +489,17 @@ int main(int argc, char *argv[])
else
ksft_test_result(ret == KSFT_PASS, "test_function_filter\n");
+ ret = test_size_filter();
+ if (ret == KSFT_SKIP)
+ ksft_test_result_skip("Skipping test_size_filter\n");
+ else
+ ksft_test_result(ret == KSFT_PASS, "test_size_filter\n");
+
+ ret = test_lineno_filter();
+ if (ret == KSFT_SKIP)
+ ksft_test_result_skip("Skipping test_lineno_filter\n");
+ else
+ ksft_test_result(ret == KSFT_PASS, "test_lineno_filter\n");
+
ksft_finished();
}
--
2.54.0.1032.g2f8565e1d1-goog
^ permalink raw reply related
* Re: [PATCH v3 0/6] alloc_tag: introduce IOCTL-based filtering for MAP
From: Andrew Morton @ 2026-06-06 0:08 UTC (permalink / raw)
To: Abhishek Bapat
Cc: Suren Baghdasaryan, Kent Overstreet, Hao Ge, Shuah Khan,
Jonathan Corbet, linux-doc, linux-kernel, linux-mm, Sourav Panda
In-Reply-To: <cover.1780701922.git.abhishekbapat@google.com>
On Fri, 5 Jun 2026 23:36:45 +0000 Abhishek Bapat <abhishekbapat@google.com> wrote:
> Currently, memory allocation profiling data is primarily exposed through
> /proc/allocinfo. While useful for manual inspection, this text-based
> interface poses challenges for production monitoring and large-scale
> analysis:
>
> 1. Userspace must parse large amounts of text to extract specific
> fields.
> 2. To find specific tags, userspace must read the entire dataset,
> requiring many context switches and high data copying.
> 3. The kernel currently aggregates per-CPU counters for every allocation
> size, even those the user intends to filter out immediately.
>
> This series introduces a new IOCTL-based binary interface for allocinfo
> that supports kernel-side filtering. By allowing the user to specify a
> filter mask, we significantly reduce the work performed in-kernel and
> the amount of data transferred to userspace.
Thanks. AI review found several things - you'll want to address at
least the first few.
https://sashiko.dev/#/patchset/cover.1780701922.git.abhishekbapat@google.com
^ permalink raw reply
* Re: [PATCH] Document: Fix missing reference pages
From: Jakub Kicinski @ 2026-06-06 0:14 UTC (permalink / raw)
To: [Ko Han Chen]
Cc: David S . Miller, Eric Dumazet, Paolo Abeni, Jonathan Corbet,
Simon Horman, Shuah Khan, netdev, linux-doc, linux-kernel
In-Reply-To: <20260605084130.13363-1-urbaner3@gmail.com>
On Fri, 5 Jun 2026 16:41:30 +0800 [Ko Han Chen] wrote:
> Subject: [PATCH] Document: Fix missing reference pages
> Date: Fri, 5 Jun 2026 16:41:30 +0800
> X-Mailer: git-send-email 2.48.1
>
> Today, my friend and I are discussing a wwan driver produced by
> Mediatek. I am reading the related document and reference link,
> then I found the page is missing. So after a short search I am
> giving the better link and test the two pages with my bash
> program for more dead links. It turns out only this one. I check
> the page and the following documents. Details are covered in
> this markdown [page](https://hackmd.io/@Urbaner/lk_patch_doc).
Nothing really wrong with your commit message, but the narrative
format is quite unusual. Please rewrite it as if you were telling
the code base what to do.
Correct the links to ... in ...
> Signed-off-by: [Ko Han Chen] <urbaner3@gmail.com>
And please remove the brackets around your name in the Author line and
the Signed line.
^ permalink raw reply
* Re: [PATCH mm-unstable v19 00/14] khugepaged: add mTHP collapse support
From: Andrew Morton @ 2026-06-06 0:38 UTC (permalink / raw)
To: Nico Pache
Cc: linux-doc, linux-kernel, linux-mm, linux-trace-kernel, aarcange,
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,
lance.yang, 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: <20260605161422.213817-1-npache@redhat.com>
On Fri, 5 Jun 2026 10:14:07 -0600 Nico Pache <npache@redhat.com> wrote:
> The following series provides khugepaged with the capability to collapse
> anonymous memory regions to mTHPs.
Thanks, I updated mm.git's mm-unstable branch to this version.
Sashiko said things:
https://sashiko.dev/#/patchset/20260605161422.213817-1-npache@redhat.com
> V19 Changes:
> - Rebased onto mm-unstable (base: 9282f9bdbdf2, where v18 was based)
> - Added RBs/Acks
> - [patch 04] Make max_ptes_none const; guard pr_warn_once with
> check so we only warn for non-zero intermediate values and use a single
> "return 0" path (David)
> - [patch 06] Add comment explaining the mmu_notifier_range covers only
> the modified PTE range (David); change BUG_ON to VM_WARN_ON_ONCE
> (David); fix update_mmu_cache_range() arch safety issue: reinstall
> PMD via pmd_populate() before calling map_anon_folio_pte_nopf() and
> hold the PTE lock nested inside pmd_ptl during the operation (David,
> Lance); drop the now-unnecessary smp_wmb() as __folio_mark_uptodate()
> provides the required ordering (David, Lance)
> - [patch 07] Clarify commit message: "mTHP to a folio of equal or
> smaller size, possibly resulting in a partially mapped source folio"
> (David)
> - [patch 08] Add Lorenzo's RB and David's Ack; move TODO comment about
> shared pages to patch 4 (David)
> - [patch 10] Rename collapse_allowable_orders() to
> collapse_possible_orders() and add collapse_possible() boolean wrapper
> for callers that only need a yes/no answer (David)
> - [patch 11] Major rework: replace the DFS stack-based algorithm with a
> simpler linear forward-walking approach using offset + order (David);
> remove mthp_range struct, mthp_bitmap_stack[], stack push/pop
> functions, and MTHP_STACK_SIZE; add max_order_from_offset() helper
> using __ffs(offset) for natural alignment; rename mthp_bitmap to
> mthp_present_ptes (David); remove temporary mthp_bitmap_mask and use
> bitmap_weight_from() directly (David); fix result propagation so
> mthp_collapse() returns enum scan_result and properly propagates
> SCAN_ALLOC_HUGE_PAGE_FAIL and SCAN_PTE_MAPPED_HUGEPAGE to callers
> (Lance); fold in v18 fixup for potential use-after-free of vma in
> mthp_collapse() by passing NULL to collapse_max_ptes_none()
> - [patch 12] Rework to match new linear algorithm; expand the set of
> failures that allow retrying at a lower order
> - [patch 14] Re-add note about file/shmem still only collapsing to
> PMD-sized THPs (David)
Here's how v19 altered mm.git:
mm/khugepaged.c | 222 ++++++++++++++++++++--------------------------
1 file changed, 100 insertions(+), 122 deletions(-)
--- a/mm/khugepaged.c~b
+++ a/mm/khugepaged.c
@@ -100,28 +100,6 @@ static DEFINE_READ_MOSTLY_HASHTABLE(mm_s
static struct kmem_cache *mm_slot_cache __ro_after_init;
#define KHUGEPAGED_MIN_MTHP_ORDER 2
-/*
- * mthp_collapse() does an iterative DFS over a binary tree, from
- * HPAGE_PMD_ORDER down to KHUGEPAGED_MIN_MTHP_ORDER. The max stack
- * size needed for a DFS on a binary tree is height + 1, where
- * height = HPAGE_PMD_ORDER - KHUGEPAGED_MIN_MTHP_ORDER.
- *
- * ilog2 is used in place of HPAGE_PMD_ORDER because some architectures
- * (e.g. ppc64le) do not define HPAGE_PMD_ORDER until after build time.
- */
-#define MTHP_STACK_SIZE (ilog2(MAX_PTRS_PER_PTE) - KHUGEPAGED_MIN_MTHP_ORDER + 1)
-
-/*
- * Defines a range of PTE entries in a PTE page table which are being
- * considered for mTHP collapse.
- *
- * @offset: the offset of the first PTE entry in a PMD range.
- * @order: the order of the PTE entries being considered for collapse.
- */
-struct mthp_range {
- u16 offset;
- u8 order;
-};
struct collapse_control {
bool is_khugepaged;
@@ -136,10 +114,7 @@ struct collapse_control {
nodemask_t alloc_nmask;
/* Each bit represents a single occupied (!none/zero) page. */
- DECLARE_BITMAP(mthp_bitmap, MAX_PTRS_PER_PTE);
- /* A mask of the current range being considered for mTHP collapse. */
- DECLARE_BITMAP(mthp_bitmap_mask, MAX_PTRS_PER_PTE);
- struct mthp_range mthp_bitmap_stack[MTHP_STACK_SIZE];
+ DECLARE_BITMAP(mthp_present_ptes, MAX_PTRS_PER_PTE);
};
/**
@@ -584,8 +559,11 @@ void __khugepaged_enter(struct mm_struct
wake_up_interruptible(&khugepaged_wait);
}
-/* Check what orders are allowed based on the vma and collapse type */
-static unsigned long collapse_allowable_orders(struct vm_area_struct *vma,
+/*
+ * Check what orders are possible based on the vma and collapse type.
+ * This is used to determine if mTHP collapse is a viable option.
+ */
+static unsigned long collapse_possible_orders(struct vm_area_struct *vma,
vm_flags_t vm_flags, enum tva_type tva_flags)
{
unsigned long orders;
@@ -599,11 +577,17 @@ static unsigned long collapse_allowable_
return thp_vma_allowable_orders(vma, vm_flags, tva_flags, orders);
}
+static bool collapse_possible(struct vm_area_struct *vma,
+ vm_flags_t vm_flags, enum tva_type tva_flags)
+{
+ return collapse_possible_orders(vma, vm_flags, tva_flags);
+}
+
void khugepaged_enter_vma(struct vm_area_struct *vma,
vm_flags_t vm_flags)
{
if (!mm_flags_test(MMF_VM_HUGEPAGE, vma->vm_mm) && hugepage_enabled()
- && collapse_allowable_orders(vma, vm_flags, TVA_KHUGEPAGED))
+ && collapse_possible(vma, vm_flags, TVA_KHUGEPAGED))
__khugepaged_enter(vma->vm_mm);
}
@@ -844,7 +828,7 @@ static void __collapse_huge_page_copy_su
struct list_head *compound_pagelist)
{
const unsigned long nr_pages = 1UL << order;
- unsigned long end = address + (PAGE_SIZE << order);
+ unsigned long end = address + (PAGE_SIZE * nr_pages);
struct folio *src, *tmp;
pte_t pteval;
pte_t *_pte;
@@ -1074,7 +1058,13 @@ static enum scan_result hugepage_vma_rev
if (!vma)
return SCAN_VMA_NULL;
- /* Always check the PMD order to ensure its not shared by another VMA */
+ /*
+ * We cannot collapse VMA regions that do not span the full PMD. This is
+ * due to the potential of the PMD being shared by another VMA leaving
+ * us vulnerable to a race condition. Always check the PMD order here to
+ * ensure its not shared by another VMA. We'd need to lock all VMAs in
+ * the PMD range to support this.
+ */
if (!thp_vma_suitable_order(vma, address, PMD_ORDER))
return SCAN_ADDRESS_RANGE;
if (!thp_vma_allowable_orders(vma, vma->vm_flags, type, BIT(order)))
@@ -1390,7 +1380,7 @@ static enum scan_result collapse_huge_pa
if (unlikely(result != SCAN_SUCCEED)) {
spin_lock(pmd_ptl);
- WARN_ON_ONCE(!pmd_none(*pmd));
+ VM_WARN_ON_ONCE(!pmd_none(*pmd));
/*
* We can only use set_pmd_at when establishing
* hugepmds and never for establishing regular pmds that
@@ -1433,13 +1423,18 @@ static enum scan_result collapse_huge_pa
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.
+ * Some architectures (e.g. MIPS) walk the live page table in
+ * their implementation. update_mmu_cache_range() must be called
+ * with a valid page table hierarchy and the PTE lock held.
+ * Acquire it nested inside pmd_ptl when they are distinct locks.
*/
- map_anon_folio_pte_nopf(folio, pte, vma, start_addr, /*uffd_wp=*/ false);
- smp_wmb(); /* make PTEs visible before PMD. See pmd_install() */
+ if (pte_ptl != pmd_ptl)
+ spin_lock_nested(pte_ptl, SINGLE_DEPTH_NESTING);
pmd_populate(mm, pmd, pmd_pgtable(_pmd));
+ map_anon_folio_pte_nopf(folio, pte, vma, start_addr,
+ /*uffd_wp=*/ false);
+ if (pte_ptl != pmd_ptl)
+ spin_unlock(pte_ptl);
}
spin_unlock(pmd_ptl);
@@ -1459,58 +1454,23 @@ out_nolock:
return result;
}
-static void collapse_mthp_stack_push(struct collapse_control *cc, int *stack_size,
- u16 offset, u8 order)
-{
- const int size = *stack_size;
- struct mthp_range *stack = &cc->mthp_bitmap_stack[size];
-
- VM_WARN_ON_ONCE(size >= MTHP_STACK_SIZE);
- stack->order = order;
- stack->offset = offset;
- (*stack_size)++;
-}
-
-static struct mthp_range collapse_mthp_stack_pop(struct collapse_control *cc,
- int *stack_size)
+/* Return the highest naturally aligned order that fits at @offset within a PMD. */
+static unsigned int max_order_from_offset(unsigned int offset)
{
- const int size = *stack_size;
+ if (offset == 0)
+ return HPAGE_PMD_ORDER;
- VM_WARN_ON_ONCE(size <= 0);
- (*stack_size)--;
- return cc->mthp_bitmap_stack[size - 1];
-}
-
-static unsigned int collapse_mthp_count_present(struct collapse_control *cc,
- u16 offset, unsigned int nr_ptes)
-{
- bitmap_zero(cc->mthp_bitmap_mask, MAX_PTRS_PER_PTE);
- bitmap_set(cc->mthp_bitmap_mask, offset, nr_ptes);
- return bitmap_weight_and(cc->mthp_bitmap, cc->mthp_bitmap_mask, MAX_PTRS_PER_PTE);
+ return min_t(unsigned int, __ffs(offset), HPAGE_PMD_ORDER);
}
/*
* mthp_collapse() consumes the bitmap that is generated during
* collapse_scan_pmd() to determine what regions and mTHP orders fit best.
*
- * Each bit in cc->mthp_bitmap represents a single occupied (!none/zero) page.
- * A stack structure cc->mthp_bitmap_stack is used to check different regions
- * of the bitmap for collapse eligibility. The stack maintains a pair of
- * variables (offset, order), indicating the number of PTEs from the start of
- * the PMD, and the order of the potential collapse candidate respectively. We
- * start at the PMD order and check if it is eligible for collapse; if not, we
- * add two entries to the stack at a lower order to represent the left and right
- * halves of the PTE page table we are examining.
- *
- * offset mid_offset
- * | |
- * | |
- * v v
- * --------------------------------------
- * | cc->mthp_bitmap |
- * --------------------------------------
- * <-------><------->
- * order-1 order-1
+ * Each bit in cc->mthp_present_ptes represents a single occupied (!none/zero)
+ * page. We start at the PMD order and check if it is eligible for collapse;
+ * if not, we check the left and right halves of the PTE page table we are
+ * examining at a lower order.
*
* For each of these, we determine how many PTE entries are occupied in the
* range of PTE entries we propose to collapse, then we compare this to a
@@ -1520,35 +1480,30 @@ static unsigned int collapse_mthp_count_
* If a collapse is permitted, we attempt to collapse the PTE range into a
* mTHP.
*/
-static int mthp_collapse(struct mm_struct *mm, unsigned long address,
- int referenced, int unmapped, struct collapse_control *cc,
- unsigned long enabled_orders)
+static enum scan_result mthp_collapse(struct mm_struct *mm,
+ unsigned long address, int referenced, int unmapped,
+ struct collapse_control *cc, unsigned long enabled_orders)
{
unsigned int nr_occupied_ptes, nr_ptes, max_ptes_none;
- int collapsed = 0, stack_size = 0;
+ enum scan_result last_result = SCAN_FAIL;
+ int collapsed = 0;
+ bool alloc_failed = false;
unsigned long collapse_address;
- struct mthp_range range;
- u16 offset;
- u8 order;
-
- collapse_mthp_stack_push(cc, &stack_size, 0, HPAGE_PMD_ORDER);
-
- while (stack_size) {
- range = collapse_mthp_stack_pop(cc, &stack_size);
- order = range.order;
- offset = range.offset;
+ unsigned int offset = 0;
+ unsigned int order = HPAGE_PMD_ORDER;
+
+ while (offset < HPAGE_PMD_NR) {
nr_ptes = 1UL << order;
if (!test_bit(order, &enabled_orders))
goto next_order;
max_ptes_none = collapse_max_ptes_none(cc, NULL, order);
-
- nr_occupied_ptes = collapse_mthp_count_present(cc, offset,
- nr_ptes);
+ nr_occupied_ptes = bitmap_weight_from(cc->mthp_present_ptes, offset,
+ offset + nr_ptes);
if (nr_occupied_ptes >= nr_ptes - max_ptes_none) {
- int ret;
+ enum scan_result ret;
collapse_address = address + offset * PAGE_SIZE;
ret = collapse_huge_page(mm, collapse_address, referenced,
@@ -1560,8 +1515,11 @@ static int mthp_collapse(struct mm_struc
collapsed += nr_ptes;
fallthrough;
case SCAN_PTE_MAPPED_HUGEPAGE:
- continue;
+ goto next_offset;
/* Cases where lower orders might still succeed */
+ case SCAN_ALLOC_HUGE_PAGE_FAIL:
+ alloc_failed = true;
+ fallthrough;
case SCAN_LACK_REFERENCED_PAGE:
case SCAN_EXCEED_NONE_PTE:
case SCAN_EXCEED_SWAP_PTE:
@@ -1572,27 +1530,48 @@ static int mthp_collapse(struct mm_struc
case SCAN_DEL_PAGE_LRU:
case SCAN_PTE_NON_PRESENT:
case SCAN_PTE_UFFD_WP:
- case SCAN_ALLOC_HUGE_PAGE_FAIL:
case SCAN_PAGE_LAZYFREE:
+ last_result = ret;
goto next_order;
/* Cases where no further collapse is possible */
+ case SCAN_PMD_MAPPED:
+ fallthrough;
default:
- return collapsed;
+ last_result = ret;
+ goto done;
}
}
next_order:
- if ((BIT(order) - 1) & enabled_orders) {
- const u8 next_order = order - 1;
- const u16 mid_offset = offset + (nr_ptes / 2);
-
- collapse_mthp_stack_push(cc, &stack_size, mid_offset,
- next_order);
- collapse_mthp_stack_push(cc, &stack_size, offset,
- next_order);
- }
- }
- return collapsed;
+ /*
+ * Continue with the next smaller order if there is still
+ * any smaller order enabled. When at the smallest order
+ * we must always move to the next offset.
+ */
+ if (order > KHUGEPAGED_MIN_MTHP_ORDER &&
+ (enabled_orders & GENMASK(order - 1, 0))) {
+ order--;
+ continue;
+ }
+next_offset:
+ /*
+ * Advance past the region we just processed and determine the
+ * highest order we can attempt next. Since huge pages must be
+ * naturally aligned, the max order we can attempt next is
+ * limited by the alignment of the new offset.
+ * E.g. if we collapsed a order-2 mTHP at offset 0, offset
+ * becomes 4 and __ffs(4) == 2, so the next attempt starts at
+ * order 2.
+ */
+ offset += nr_ptes;
+ order = max_order_from_offset(offset);
+ }
+done:
+ if (collapsed)
+ return SCAN_SUCCEED;
+ if (alloc_failed)
+ return SCAN_ALLOC_HUGE_PAGE_FAIL;
+ return last_result;
}
static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
@@ -1606,7 +1585,7 @@ static enum scan_result collapse_scan_pm
pmd_t *pmd;
pte_t *pte, *_pte, pteval;
int i;
- int none_or_zero = 0, shared = 0, nr_collapsed = 0, referenced = 0;
+ int none_or_zero = 0, shared = 0, referenced = 0;
enum scan_result result = SCAN_FAIL;
struct page *page = NULL;
struct folio *folio = NULL;
@@ -1623,11 +1602,11 @@ static enum scan_result collapse_scan_pm
goto out;
}
- bitmap_zero(cc->mthp_bitmap, MAX_PTRS_PER_PTE);
+ bitmap_zero(cc->mthp_present_ptes, MAX_PTRS_PER_PTE);
memset(cc->node_load, 0, sizeof(cc->node_load));
nodes_clear(cc->alloc_nmask);
- enabled_orders = collapse_allowable_orders(vma, vma->vm_flags, tva_flags);
+ enabled_orders = collapse_possible_orders(vma, vma->vm_flags, tva_flags);
/*
* If PMD is the only enabled order, enforce max_ptes_none, otherwise
@@ -1730,7 +1709,7 @@ static enum scan_result collapse_scan_pm
}
/* Set bit for occupied pages */
- __set_bit(i, cc->mthp_bitmap);
+ __set_bit(i, cc->mthp_present_ptes);
/*
* Record which node the original page is from and save this
* information to cc->node_load[].
@@ -1789,11 +1768,10 @@ out_unmap:
if (result == SCAN_SUCCEED) {
/* collapse_huge_page expects the lock to be dropped before calling */
mmap_read_unlock(mm);
- nr_collapsed = mthp_collapse(mm, start_addr, referenced,
- unmapped, cc, enabled_orders);
+ result = mthp_collapse(mm, 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;
}
out:
trace_mm_khugepaged_scan_pmd(mm, folio, referenced,
@@ -2892,7 +2870,7 @@ static void collapse_scan_mm_slot(unsign
cc->progress++;
break;
}
- if (!collapse_allowable_orders(vma, vma->vm_flags, TVA_KHUGEPAGED)) {
+ if (!collapse_possible(vma, vma->vm_flags, TVA_KHUGEPAGED)) {
cc->progress++;
continue;
}
@@ -3202,7 +3180,7 @@ int madvise_collapse(struct vm_area_stru
BUG_ON(vma->vm_start > start);
BUG_ON(vma->vm_end < end);
- if (!collapse_allowable_orders(vma, vma->vm_flags, TVA_FORCED_COLLAPSE))
+ if (!collapse_possible(vma, vma->vm_flags, TVA_FORCED_COLLAPSE))
return -EINVAL;
cc = kmalloc_obj(*cc);
_
^ permalink raw reply
* Re: [PATCH] Docs/damon: add TLB flush policy document
From: SeongJae Park @ 2026-06-06 0:54 UTC (permalink / raw)
To: Kunwu Chan
Cc: SeongJae Park, akpm, david, ljs, liam, vbabka, rppt, surenb,
mhocko, corbet, skhan, damon, linux-mm, linux-doc, linux-kernel,
Wang Lian
In-Reply-To: <20260605031008.397328-1-kunwu.chan@linux.dev>
Hi Kunwu and Lian,
On Fri, 5 Jun 2026 11:10:08 +0800 Kunwu Chan <kunwu.chan@gmail.com> wrote:
> From: Kunwu Chan <kunwu.chan@gmail.com>
>
> DAMON avoids TLB flushes after clearing PTE Accessed bits for sampling.
> The overhead was measured and found significant [1]. Production
> workloads with large working sets flush TLB buffers naturally, so
> accuracy impact is negligible.
>
> On systems with large TLB buffers and small test workloads, stale TLB
> entries persist across sampling intervals and produce false negatives.
> This comes up repeatedly on the mailing list and in private inquiries
> [2][3].
>
> Add a document on the design decision, trade-offs, test environment
> problems, and recommendations.
>
> Link: https://lore.kernel.org/20200403103059.12762-1-sjpark@amazon.com [1]
> Link: https://lore.kernel.org/20260117020731.226785-3-sj@kernel.org [2]
> Link: https://lore.kernel.org/all/20260526145034.91594-1-sj@kernel.org [3]
Thank you for this great patch!
>
> Co-developed-by: Wang Lian <lianux.mm@gmail.com>
> Signed-off-by: Wang Lian <lianux.mm@gmail.com>
> Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com>
> ---
> Documentation/mm/damon/index.rst | 1 +
> Documentation/mm/damon/tlb_flush.rst | 131 +++++++++++++++++++++++++++
> 2 files changed, 132 insertions(+)
> create mode 100644 Documentation/mm/damon/tlb_flush.rst
>
> diff --git a/Documentation/mm/damon/index.rst b/Documentation/mm/damon/index.rst
> index 318f6a7bfea4..5e239437dab3 100644
> --- a/Documentation/mm/damon/index.rst
> +++ b/Documentation/mm/damon/index.rst
> @@ -19,6 +19,7 @@ DAMON is a Linux kernel subsystem for efficient :ref:`data access monitoring
>
> faq
> design
> + tlb_flush
> api
> maintainer-profile
>
> diff --git a/Documentation/mm/damon/tlb_flush.rst b/Documentation/mm/damon/tlb_flush.rst
> new file mode 100644
[...]
Great document! That said, it feels like a good complete article or a paper,
rather than DAMON documentation that pursue to be short and essential. I feel
like this fit more to be published in a blog like DAMON project blog [1], or
news site like LWN. If you'd like to, please feel free to upload a PR or send
patch for DAMON project blog source [2].
Mainly due to the verbosity, as I above mentionedd, I'm not sure if the current
shape of this patch is the best to be merged as is. I also find the background
part of the document is a kind of duplicate of some information in design.rst.
What about putting only essential information in a condensed way on the
design.rst?
[1] https://damonitor.github.io/site_about
[2] https://github.com/damonitor/damonitor.github.io/tree/master/blog_src
Thanks,
SJ
[...]
^ permalink raw reply
* [PATCH v1] docs/ja_JP: translate submitting-patches.rst (interleaved-replies)
From: Akiyoshi Kurita @ 2026-06-06 3:59 UTC (permalink / raw)
To: linux-doc; +Cc: linux-kernel, corbet, akiyks, Akiyoshi Kurita
Translate the "Use trimmed interleaved replies in email discussions"
and "Don't get discouraged - or impatient" sections in
Documentation/translations/ja_JP/process/submitting-patches.rst.
Keep the wording close to the English text and wrap lines to match
the style used in the surrounding Japanese translation.
Signed-off-by: Akiyoshi Kurita <weibu@redadmin.org>
---
.../ja_JP/process/submitting-patches.rst | 47 +++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/Documentation/translations/ja_JP/process/submitting-patches.rst b/Documentation/translations/ja_JP/process/submitting-patches.rst
index 165cb3ed94ec..d31d469909e4 100644
--- a/Documentation/translations/ja_JP/process/submitting-patches.rst
+++ b/Documentation/translations/ja_JP/process/submitting-patches.rst
@@ -355,3 +355,50 @@ cover letter または個々のパッチに ``patch changelog`` を追加し、
メールクライアントとメーリングリストでの作法についての推奨事項は、
Documentation/process/email-clients.rst を参照してください。
+
+メール議論では不要な引用を削った interleaved replies を使う
+------------------------------------------------------------
+
+Linux カーネル開発の議論では、top-posting は強く非推奨とされています。
+Interleaved replies、または ``inline`` replies を使うと、会話の流れを
+ずっと追いやすくなります。詳細は次を参照してください:
+https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
+
+メーリングリストでは、よく次のように引用されます::
+
+ A: http://en.wikipedia.org/wiki/Top_post
+ Q: Where do I find info about this thing called top-posting?
+ A: Because it messes up the order in which people normally read text.
+ Q: Why is top-posting such a bad thing?
+ A: Top-posting.
+ Q: What is the most annoying thing in e-mail?
+
+同様に、返信に関係のない不要な引用はすべて削ってください。
+そうすることで、返答を見つけやすくなり、時間と容量を節約できます。
+詳細は次を参照してください: http://daringfireball.net/2007/07/on_top ::
+
+ A: No.
+ Q: Should I include quotations after my reply?
+
+
+落胆しない、そして急がない
+--------------------------
+
+変更を投稿した後は、辛抱強く待ってください。レビューアは忙しい人たちであり、
+あなたのパッチをすぐに見られるとは限りません。
+
+かつては、パッチが何のコメントもなく虚空へ消えていくこともありましたが、
+現在の開発プロセスはそれよりも円滑に機能しています。数週間以内、
+通常は 2〜3 週間以内にコメントを受け取るはずです。そうならない場合は、
+パッチを正しい場所へ送ったか確認してください。再投稿したりレビューアに
+ping したりする前に、少なくとも 1 週間は待ってください。merge window の
+ような忙しい時期には、さらに長く待つ方がよい場合もあります。
+
+数週間後に、subject line に "RESEND" を追加して、パッチまたは
+パッチシリーズを再送しても構いません::
+
+ [PATCH Vx RESEND] sub/sys: Condensed patch summary
+
+パッチまたはパッチシリーズの修正版を投稿する場合は、"RESEND" を
+追加しないでください。"RESEND" は、前回の投稿から一切変更していない
+パッチまたはパッチシリーズを再送する場合にのみ使います。
--
2.52.0
^ permalink raw reply related
* [PATCH net-next v4 03/16] net: ethernet: oa_tc6: Move oa_tc6.c to its own directory
From: Selvamani Rajagopal via B4 Relay @ 2026-06-06 5:42 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260605-s2500-mac-phy-support-v4-0-de0fbc13c6d8@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Moving oa_tc6.c to its own directory, drivers/net/ethernet/oa_tc6. This
will facilitate adding more files to support other features
defined by OPEN Alliance 10BASE-T1x Serial Interface specification
This patch series is adding two files, one for hardware
timestamp related functions and one for PTP related APIs.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
MAINTAINERS | 2 +-
drivers/net/ethernet/Kconfig | 12 +-----------
drivers/net/ethernet/Makefile | 2 +-
drivers/net/ethernet/oa_tc6/Kconfig | 16 ++++++++++++++++
drivers/net/ethernet/oa_tc6/Makefile | 7 +++++++
drivers/net/ethernet/{ => oa_tc6}/oa_tc6.c | 0
6 files changed, 26 insertions(+), 13 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index eb8cdcc76324..7e6b28202e88 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20001,7 +20001,7 @@ M: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/networking/oa-tc6-framework.rst
-F: drivers/net/ethernet/oa_tc6.c
+F: drivers/net/ethernet/oa_tc6/oa_tc6*
F: include/linux/oa_tc6.h
OPEN FIRMWARE AND FLATTENED DEVICE TREE
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index 78c79ad7bba5..49d93488ba52 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -134,6 +134,7 @@ source "drivers/net/ethernet/netronome/Kconfig"
source "drivers/net/ethernet/8390/Kconfig"
source "drivers/net/ethernet/nvidia/Kconfig"
source "drivers/net/ethernet/nxp/Kconfig"
+source "drivers/net/ethernet/oa_tc6/Kconfig"
source "drivers/net/ethernet/oki-semi/Kconfig"
config ETHOC
@@ -146,17 +147,6 @@ config ETHOC
help
Say Y here if you want to use the OpenCores 10/100 Mbps Ethernet MAC.
-config OA_TC6
- tristate "OPEN Alliance TC6 10BASE-T1x MAC-PHY support" if COMPILE_TEST
- depends on SPI
- select PHYLIB
- help
- This library implements OPEN Alliance TC6 10BASE-T1x MAC-PHY
- Serial Interface protocol for supporting 10BASE-T1x MAC-PHYs.
-
- To know the implementation details, refer documentation in
- <file:Documentation/networking/oa-tc6-framework.rst>.
-
source "drivers/net/ethernet/pasemi/Kconfig"
source "drivers/net/ethernet/pensando/Kconfig"
source "drivers/net/ethernet/qlogic/Kconfig"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index bba55d9af387..77b11d5a7abf 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -71,6 +71,7 @@ obj-$(CONFIG_NET_VENDOR_NETRONOME) += netronome/
obj-$(CONFIG_NET_VENDOR_NI) += ni/
obj-$(CONFIG_NET_VENDOR_NVIDIA) += nvidia/
obj-$(CONFIG_LPC_ENET) += nxp/
+obj-$(CONFIG_OA_TC6) += oa_tc6/
obj-$(CONFIG_NET_VENDOR_OKI) += oki-semi/
obj-$(CONFIG_ETHOC) += ethoc.o
obj-$(CONFIG_NET_VENDOR_PASEMI) += pasemi/
@@ -104,4 +105,3 @@ obj-$(CONFIG_NET_VENDOR_XILINX) += xilinx/
obj-$(CONFIG_NET_VENDOR_XIRCOM) += xircom/
obj-$(CONFIG_NET_VENDOR_SYNOPSYS) += synopsys/
obj-$(CONFIG_NET_VENDOR_PENSANDO) += pensando/
-obj-$(CONFIG_OA_TC6) += oa_tc6.o
diff --git a/drivers/net/ethernet/oa_tc6/Kconfig b/drivers/net/ethernet/oa_tc6/Kconfig
new file mode 100644
index 000000000000..97345f345fb9
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# OA TC6 10BASE-T1x MAC-PHY configuration
+#
+
+config OA_TC6
+ tristate "OPEN Alliance TC6 10BASE-T1x MAC-PHY support"
+ depends on SPI
+ select PHYLIB
+ help
+ This library implements OPEN Alliance TC6 10BASE-T1x MAC-PHY
+ Serial Interface protocol for supporting 10BASE-T1x MAC-PHYs.
+
+ To know the implementation details, refer documentation in
+ <file:Documentation/networking/oa-tc6-framework.rst>.
+
diff --git a/drivers/net/ethernet/oa_tc6/Makefile b/drivers/net/ethernet/oa_tc6/Makefile
new file mode 100644
index 000000000000..f24aae852ef2
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for OA TC6 10BASE-T1x MAC-PHY
+#
+
+obj-$(CONFIG_OA_TC6) := oa_tc6_mod.o
+oa_tc6_mod-objs := oa_tc6.o
diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
similarity index 100%
rename from drivers/net/ethernet/oa_tc6.c
rename to drivers/net/ethernet/oa_tc6/oa_tc6.c
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v4 02/16] net: phy: Helper to modify PHY loopback mode only.
From: Selvamani Rajagopal via B4 Relay @ 2026-06-06 5:42 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260605-s2500-mac-phy-support-v4-0-de0fbc13c6d8@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Generic helper function to modify loopback bit of the PHY without
modifying any other bit. This will help the PHYs that may have fixed
speed, like 10Base-T1S or PHYs that don't need any other settings
to set them in loopback mode.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
drivers/net/phy/dp83867.c | 11 +----------
drivers/net/phy/phy_device.c | 20 ++++++++++++++++++++
include/linux/phy.h | 2 ++
3 files changed, 23 insertions(+), 10 deletions(-)
diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c
index 88255e92b4cd..01ea2e8dd253 100644
--- a/drivers/net/phy/dp83867.c
+++ b/drivers/net/phy/dp83867.c
@@ -991,15 +991,6 @@ static void dp83867_link_change_notify(struct phy_device *phydev)
}
}
-static int dp83867_loopback(struct phy_device *phydev, bool enable, int speed)
-{
- if (enable && speed)
- return -EOPNOTSUPP;
-
- return phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK,
- enable ? BMCR_LOOPBACK : 0);
-}
-
static int
dp83867_led_brightness_set(struct phy_device *phydev,
u8 index, enum led_brightness brightness)
@@ -1204,7 +1195,7 @@ static struct phy_driver dp83867_driver[] = {
.resume = dp83867_resume,
.link_change_notify = dp83867_link_change_notify,
- .set_loopback = dp83867_loopback,
+ .set_loopback = genphy_loopback_fixed_speed,
.led_brightness_set = dp83867_led_brightness_set,
.led_hw_is_supported = dp83867_led_hw_is_supported,
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 20c92fde4fbb..738c995e11d2 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -2836,6 +2836,26 @@ int genphy_phy_write_mmd(struct phy_device *phydev, int devnum,
}
EXPORT_SYMBOL(genphy_phy_write_mmd);
+/**
+ * genphy_loopback_fixed_speed - Helper to modify the PHY loopback mode
+ * without affecting any other settings.
+ * @phydev: The phy_device struct
+ * @enable: Flag to enable or disable the PHY level loopback.
+ * @speed: Speed setting. Not expected to be set. Error if it is set.
+ *
+ * Returns: 0 if successful, negative error code on failure.
+ */
+int genphy_loopback_fixed_speed(struct phy_device *phydev, bool enable,
+ int speed)
+{
+ if (enable && speed)
+ return -EOPNOTSUPP;
+
+ return phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK,
+ enable ? BMCR_LOOPBACK : 0);
+}
+EXPORT_SYMBOL(genphy_loopback_fixed_speed);
+
int genphy_suspend(struct phy_device *phydev)
{
return phy_set_bits(phydev, MII_BMCR, BMCR_PDOWN);
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 8266dd4a8dbe..61bcd71a3143 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -2301,6 +2301,8 @@ int genphy_read_mmd_unsupported(struct phy_device *phdev, int devad,
u16 regnum);
int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum,
u16 regnum, u16 val);
+int genphy_loopback_fixed_speed(struct phy_device *phydev, bool enable,
+ int speed);
int genphy_phy_write_mmd(struct phy_device *phydev, int devnum,
u16 regnum, u16 val);
int genphy_phy_read_mmd(struct phy_device *phydev, int devnum,
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v4 00/16] Support for onsemi's S2500 10Base-T1S MAC-PHY
From: Selvamani Rajagopal via B4 Relay @ 2026-06-06 5:42 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
This patch series brings support for onsemi's S2500 that is IEEE 802.3cg
compliant Ethernet transceiver with an integrated Media Access
Controller (MAC-PHY)
Previous versions were submitted unthreaded, without coverletter
and changelog.
Changes in v4:
- Added return value comment for genphy_read/write_phy_mmd functions
- Added genphy_loopback_fixed_speed helper function to be used in
set_loopback callbacks
- Updated networking documentation for OA TC6 framework to elaborate
on what is expected in the ptp_clock_info structure for registration.
- added spi-max-frequency in YAML file based on alert from sashiko-bot
- Removed model/version from the onsemi driver's private structure as
they were useful as "information-only" data.
- Replaced the non-standard selftest with Linux's standard selftest
and made it as a separate patch
- Changed bit manipulation, shift operations to use macros so that
it is clean and readable.
- added new read_register and write_register apis with _mms postfix
so that MMS (memory map selector) can be given as a parameter.
- Fixed the wrong condition check with NETIF_F_RXFCS to subtract
FCS size from the length of the frame.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
Selvamani Rajagopal (16):
net: phy: Helper to read and write through C45 without lock
net: phy: Helper to modify PHY loopback mode only.
net: ethernet: oa_tc6: Move oa_tc6.c to its own directory
net: phy: microchip_t1s: Use generic APIs for C45 read and write
net: ethernet: oa_tc6: Move constant definitions to header file
net: ethernet: oa_tc6: Support for hardware timestamp
net: ethernet: oa_tc6: Support for vendor specific MMS
net: ethernet: oa_tc6: Remove FCS size in RX frame
net: ethernet: oa_tc6: read, write interface with MMS option
net: phy: ncn26000: Support for onsemi's S2500 internal phy
net: phy: ncn26000: Enable enhanced noise immunity
net: phy: ncn26000: Support for loopback support
onsemi: s2500: Add driver support for TS2500 MAC-PHY
onsemi: s2500: Added selftest support to onsemi's S2500 driver
dt-bindings: net: add onsemi's S2500
Documentation: networking: Add timestamp related APIs to OA TC6 framework
.../devicetree/bindings/net/onnn,s2500.yaml | 67 +++
Documentation/networking/oa-tc6-framework.rst | 76 +++
MAINTAINERS | 13 +-
drivers/net/ethernet/Kconfig | 12 +-
drivers/net/ethernet/Makefile | 2 +-
drivers/net/ethernet/microchip/lan865x/lan865x.c | 61 +-
drivers/net/ethernet/oa_tc6/Kconfig | 16 +
drivers/net/ethernet/oa_tc6/Makefile | 7 +
drivers/net/ethernet/{ => oa_tc6}/oa_tc6.c | 476 ++++++++++------
drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c | 67 +++
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 190 +++++++
drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c | 202 +++++++
drivers/net/ethernet/onsemi/Kconfig | 21 +
drivers/net/ethernet/onsemi/Makefile | 7 +
drivers/net/ethernet/onsemi/s2500/Kconfig | 22 +
drivers/net/ethernet/onsemi/s2500/Makefile | 7 +
drivers/net/ethernet/onsemi/s2500/s2500_ethtool.c | 354 ++++++++++++
drivers/net/ethernet/onsemi/s2500/s2500_hw_def.h | 225 ++++++++
drivers/net/ethernet/onsemi/s2500/s2500_main.c | 632 +++++++++++++++++++++
drivers/net/ethernet/onsemi/s2500/s2500_ptp.c | 233 ++++++++
drivers/net/phy/dp83867.c | 11 +-
drivers/net/phy/microchip_t1s.c | 32 +-
drivers/net/phy/ncn26000.c | 63 +-
drivers/net/phy/phy_device.c | 75 +++
include/linux/oa_tc6.h | 36 ++
include/linux/phy.h | 6 +
26 files changed, 2662 insertions(+), 251 deletions(-)
---
base-commit: 903db046d5579bef0ea699eae4b279dd6455fc9f
change-id: 20260601-s2500-mac-phy-support-4f3ae920fb73
Best regards,
--
Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
^ permalink raw reply
* [PATCH net-next v4 01/16] net: phy: Helper to read and write through C45 without lock
From: Selvamani Rajagopal via B4 Relay @ 2026-06-06 5:42 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260605-s2500-mac-phy-support-v4-0-de0fbc13c6d8@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Generic helper function to initiate read and write through C45 bus
protocol without mdio bus lock. This will help PHYs to avoid indirect C22
API calls for C45 bus protocol which may not be supported by the PHY.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
drivers/net/phy/phy_device.c | 55 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/phy.h | 4 ++++
2 files changed, 59 insertions(+)
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 3370eb822017..20c92fde4fbb 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -2781,6 +2781,61 @@ int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum,
}
EXPORT_SYMBOL(genphy_write_mmd_unsupported);
+/**
+ * genphy_phy_read_mmd - Helper for reading a register without lock
+ * from the given MMD and PHY.
+ * @phydev: The phy_device struct
+ * @devnum: The MMD to read from
+ * @regnum: The register on the MMD to read
+ *
+ * Description: PHYs can have both C22 and C45 registers space. Once PHY
+ * is discovered via C22 bus protocol, it uses C22 indirect access to
+ * access C45 registers. Some PHYs, like 10Base-T1S PHYs defined by OPEN
+ * Alliance 10BASE‑T1x, support only direct access.
+ *
+ * If PHY indicates C45 support through DTS entry, it avoid C22 APIs
+ * entirely and therefore generic MDIO registers are inaccessible.
+ *
+ * MDIO bus isn't locked here because when called through read_mmd
+ * callback of phy_driver, caller is expected to lock the bus as
+ * implemented in phy_read_mmd.
+ *
+ * Returns: Register value if successful, negative error code on failure.
+ */
+int genphy_phy_read_mmd(struct phy_device *phydev, int devnum,
+ u16 regnum)
+{
+ struct mii_bus *bus = phydev->mdio.bus;
+ int addr = phydev->mdio.addr;
+
+ lockdep_assert_held(&bus->mdio_lock);
+ return __mdiobus_c45_read(bus, addr, devnum, regnum);
+}
+EXPORT_SYMBOL(genphy_phy_read_mmd);
+
+/**
+ * genphy_phy_write_mmd - Helper for writing a register without lock
+ * to the given MMD and PHY.
+ * @phydev: The phy_device struct
+ * @devnum: The MMD to write to
+ * @regnum: The register on the MMD to write
+ * @val: Value to write
+ *
+ * Description: Similar to genphy_phy_read_mmd
+ *
+ * Returns: 0 if successful, negative error code on failure.
+ */
+int genphy_phy_write_mmd(struct phy_device *phydev, int devnum,
+ u16 regnum, u16 val)
+{
+ struct mii_bus *bus = phydev->mdio.bus;
+ int addr = phydev->mdio.addr;
+
+ lockdep_assert_held(&bus->mdio_lock);
+ return __mdiobus_c45_write(bus, addr, devnum, regnum, val);
+}
+EXPORT_SYMBOL(genphy_phy_write_mmd);
+
int genphy_suspend(struct phy_device *phydev)
{
return phy_set_bits(phydev, MII_BMCR, BMCR_PDOWN);
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 199a7aaa341b..8266dd4a8dbe 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -2301,6 +2301,10 @@ int genphy_read_mmd_unsupported(struct phy_device *phdev, int devad,
u16 regnum);
int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum,
u16 regnum, u16 val);
+int genphy_phy_write_mmd(struct phy_device *phydev, int devnum,
+ u16 regnum, u16 val);
+int genphy_phy_read_mmd(struct phy_device *phydev, int devnum,
+ u16 regnum);
/* Clause 37 */
int genphy_c37_config_aneg(struct phy_device *phydev);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v4 04/16] net: phy: microchip_t1s: Use generic APIs for C45 read and write
From: Selvamani Rajagopal via B4 Relay @ 2026-06-06 5:42 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260605-s2500-mac-phy-support-v4-0-de0fbc13c6d8@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Replace vendor implementation with generic API to read and write
PHY registers using C45 bus protocol.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
drivers/net/phy/microchip_t1s.c | 32 ++------------------------------
1 file changed, 2 insertions(+), 30 deletions(-)
diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c
index e601d56b2507..0c4dc70641d8 100644
--- a/drivers/net/phy/microchip_t1s.c
+++ b/drivers/net/phy/microchip_t1s.c
@@ -506,34 +506,6 @@ static int lan86xx_read_status(struct phy_device *phydev)
return 0;
}
-/* OPEN Alliance 10BASE-T1x compliance MAC-PHYs will have both C22 and
- * C45 registers space. If the PHY is discovered via C22 bus protocol it assumes
- * it uses C22 protocol and always uses C22 registers indirect access to access
- * C45 registers. This is because, we don't have a clean separation between
- * C22/C45 register space and C22/C45 MDIO bus protocols. Resulting, PHY C45
- * registers direct access can't be used which can save multiple SPI bus access.
- * To support this feature, set .read_mmd/.write_mmd in the PHY driver to call
- * .read_c45/.write_c45 in the OPEN Alliance framework
- * drivers/net/ethernet/oa_tc6.c
- */
-static int lan865x_phy_read_mmd(struct phy_device *phydev, int devnum,
- u16 regnum)
-{
- struct mii_bus *bus = phydev->mdio.bus;
- int addr = phydev->mdio.addr;
-
- return __mdiobus_c45_read(bus, addr, devnum, regnum);
-}
-
-static int lan865x_phy_write_mmd(struct phy_device *phydev, int devnum,
- u16 regnum, u16 val)
-{
- struct mii_bus *bus = phydev->mdio.bus;
- int addr = phydev->mdio.addr;
-
- return __mdiobus_c45_write(bus, addr, devnum, regnum, val);
-}
-
static struct phy_driver microchip_t1s_driver[] = {
{
PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1),
@@ -584,8 +556,8 @@ static struct phy_driver microchip_t1s_driver[] = {
.features = PHY_BASIC_T1S_P2MP_FEATURES,
.config_init = lan865x_revb_config_init,
.read_status = lan86xx_read_status,
- .read_mmd = lan865x_phy_read_mmd,
- .write_mmd = lan865x_phy_write_mmd,
+ .read_mmd = genphy_phy_read_mmd,
+ .write_mmd = genphy_phy_write_mmd,
.get_plca_cfg = genphy_c45_plca_get_cfg,
.set_plca_cfg = lan86xx_plca_set_cfg,
.get_plca_status = genphy_c45_plca_get_status,
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v4 08/16] net: ethernet: oa_tc6: Remove FCS size in RX frame
From: Selvamani Rajagopal via B4 Relay @ 2026-06-06 5:42 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260605-s2500-mac-phy-support-v4-0-de0fbc13c6d8@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
OA TC6 MAC-PHY appends FCS to the incoming frame.
It is removed from the frame before being passed to the stack.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
drivers/net/ethernet/oa_tc6/oa_tc6.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
index 2f9ace655e81..26033373f16f 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
@@ -782,12 +782,22 @@ static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6)
{
oa_tc6_update_ts_in_rx_skb(tc6);
+ /* MAC-PHY delivers each frame with its Ethernet FCS attached.
+ * Strip it before handing over to the stack, unless the user
+ * has asked to keep it via NETIF_F_RXFCS. Keeping the FCS
+ * in the frame is harmless for IP traffic, but is parsed as
+ * a (malformed) suffix TLV by PTP, which makes ptp4l reject
+ * every message with "bad message" error.
+ */
+ if (!(tc6->netdev->features & NETIF_F_RXFCS) &&
+ tc6->rx_skb->len > ETH_FCS_LEN)
+ skb_trim(tc6->rx_skb, tc6->rx_skb->len - ETH_FCS_LEN);
+
tc6->rx_skb->protocol = eth_type_trans(tc6->rx_skb, tc6->netdev);
tc6->netdev->stats.rx_packets++;
tc6->netdev->stats.rx_bytes += tc6->rx_skb->len;
netif_rx(tc6->rx_skb);
-
tc6->rx_skb = NULL;
}
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v4 06/16] net: ethernet: oa_tc6: Support for hardware timestamp
From: Selvamani Rajagopal via B4 Relay @ 2026-06-06 5:42 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260605-s2500-mac-phy-support-v4-0-de0fbc13c6d8@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
PTP register/unregister calls are implemented in oa_tc6_ptp.c.
The APIs that work with the hardware for timestamp is provided
by vendor code as it may be vendor dependent.
Interface for ndo_hwtstamp_set/get, ioctl, control and status
callback for ethtool are provided to support hardware timestamp
feature.
Besides ioctl interface, hardware timestamp functions that handles
header and footer data are in oa_tc6.c. Helper functions are in
oa_tc6_tstamp.c.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
MAINTAINERS | 1 +
drivers/net/ethernet/oa_tc6/Makefile | 2 +-
drivers/net/ethernet/oa_tc6/oa_tc6.c | 214 +++++++++++++++++++++++++--
drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c | 67 +++++++++
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 33 +++++
drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c | 202 +++++++++++++++++++++++++
include/linux/oa_tc6.h | 12 ++
7 files changed, 516 insertions(+), 15 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 7e6b28202e88..63b71197d385 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19998,6 +19998,7 @@ F: drivers/rtc/rtc-optee.c
OPEN ALLIANCE 10BASE-T1S MACPHY SERIAL INTERFACE FRAMEWORK
M: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
+M: Selva Rajagopal <selvamani.rajagopal@onsemi.com> (timestamp support)
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/networking/oa-tc6-framework.rst
diff --git a/drivers/net/ethernet/oa_tc6/Makefile b/drivers/net/ethernet/oa_tc6/Makefile
index f24aae852ef2..964f668efc2d 100644
--- a/drivers/net/ethernet/oa_tc6/Makefile
+++ b/drivers/net/ethernet/oa_tc6/Makefile
@@ -4,4 +4,4 @@
#
obj-$(CONFIG_OA_TC6) := oa_tc6_mod.o
-oa_tc6_mod-objs := oa_tc6.o
+oa_tc6_mod-objs := oa_tc6.o oa_tc6_ptp.o oa_tc6_tstamp.o
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
index c7d70d37ba53..9410cecfdc2a 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
@@ -13,6 +13,15 @@
#include "oa_tc6_std_def.h"
+struct oa_tc6_ts_info_rx {
+ bool rtsa;
+ bool rtsp;
+};
+
+struct oa_tc6_ts_info_tx {
+ u8 tsc;
+};
+
static int oa_tc6_spi_transfer(struct oa_tc6 *tc6,
enum oa_tc6_header_type header_type, u16 length)
{
@@ -47,6 +56,152 @@ static int oa_tc6_get_parity(u32 p)
return !((p >> 28) & 1);
}
+static struct oa_tc6_ts_info_tx *oa_tc6_tsinfo_tx(struct sk_buff *skb)
+{
+ return (struct oa_tc6_ts_info_tx *)(skb->cb);
+}
+
+static struct oa_tc6_ts_info_rx *oa_tc6_tsinfo_rx(struct sk_buff *skb)
+{
+ return (struct oa_tc6_ts_info_rx *)(skb->cb);
+}
+
+static void oa_tc6_defer_for_hwtstamp(struct oa_tc6 *tc6,
+ struct sk_buff *skb)
+{
+ if (!tc6->hw_tstamp_enabled)
+ return;
+ if (!skb || (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) == 0)
+ return;
+ if (tc6->ts_config.tx_type != HWTSTAMP_TX_ON) {
+ tc6->tx_hwtstamp_lost++;
+ return;
+ }
+
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+ u8 ret = tc6->tx_ts_idx++;
+
+ if (ret == OA_TC6_TTSCC_REG_ID)
+ tc6->tx_ts_idx = OA_TC6_TTSCA_REG_ID;
+ oa_tc6_tsinfo_tx(skb)->tsc = ret;
+
+ list_add_tail(&skb->list, &tc6->tx_ts_skb_q);
+}
+
+static int oa_tc6_process_deferred_skb(struct oa_tc6 *tc6, u8 tsc)
+{
+ struct skb_shared_hwtstamps tstamp;
+ struct oa_tc6_ts_info_tx *ski;
+ struct sk_buff *skb, *tmp;
+ bool found = false;
+ int ret = 0;
+
+ /* Size of data must match OA_TC6_TSTAMP_SZ */
+ u32 data[2];
+
+ list_for_each_entry_safe(skb, tmp, &tc6->tx_ts_skb_q, list) {
+ ski = oa_tc6_tsinfo_tx(skb);
+ if (ski->tsc != tsc)
+ continue;
+ if (found) {
+ dev_warn_ratelimited(&tc6->spi->dev,
+ "Multiple skbs. tsc = %d\n",
+ tsc);
+ tc6->tx_hwtstamp_err++;
+ }
+ found = true;
+ list_del(&skb->list);
+
+ /* Retrieve the timestamping info */
+ ret = oa_tc6_read_registers(tc6,
+ OA_TC6_REG_TTSCA_HIGH +
+ 2 * (tsc - 1), &data[0], 2);
+
+ if (!ret) {
+ tstamp.hwtstamp = ktime_set(data[0], data[1]);
+ skb_tstamp_tx(skb, &tstamp);
+ tc6->tx_hwtstamp_pkts++;
+ }
+
+ dev_kfree_skb(skb);
+ }
+ return ret;
+}
+
+static void oa_tc6_events_handle(struct oa_tc6 *tc6, u32 val)
+{
+ /* Check TX timestamping */
+ if (val & STATUS0_TTSCAA)
+ oa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCA_REG_ID);
+
+ if (val & STATUS0_TTSCAB)
+ oa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCB_REG_ID);
+
+ if (val & STATUS0_TTSCAC)
+ oa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCC_REG_ID);
+}
+
+static void oa_tc6_update_ts_in_rx_skb(struct oa_tc6 *tc6)
+{
+ struct sk_buff *skb = tc6->rx_skb;
+ struct oa_tc6_ts_info_rx *ski;
+ u32 ts[2];
+
+ if (!tc6->hw_tstamp_enabled)
+ return;
+ ski = oa_tc6_tsinfo_rx(skb);
+ if (!ski->rtsa)
+ return;
+
+ ts[0] = be32_to_cpu(*((u32 *)(skb->data)));
+ ts[1] = be32_to_cpu(*((u32 *)(skb->data) + 1));
+
+ /* Check parity */
+ if ((oa_tc6_get_parity(ts[0]) ^ oa_tc6_get_parity(ts[1])) ==
+ !ski->rtsp) {
+ struct skb_shared_hwtstamps *hw_ts;
+
+ /* Report timestamp to the upper layers */
+ hw_ts = skb_hwtstamps(skb);
+ memset(hw_ts, 0, sizeof(*hw_ts));
+ hw_ts->hwtstamp = ktime_set(ts[0], ts[1]);
+ }
+ skb_pull(skb, sizeof(ts));
+}
+
+static int oa_tc6_update_standard_capability(struct oa_tc6 *tc6)
+{
+ u32 regval = 0;
+ int ret;
+
+ ret = oa_tc6_read_register(tc6, OA_TC6_REG_STDCAP, ®val);
+ if (ret)
+ return ret;
+ if (regval & STDCAP_FRAME_TIMESTAMP_CAPABILITY)
+ tc6->hw_tstamp_supported = true;
+ return 0;
+}
+
+/**
+ * oa_tc6_ioctl - generic ioctl interface for MAC-PHY drivers.
+ * @tc6: oa_tc6 struct.
+ * @rq: request from socket interface
+ * @cmd: value to set/get timestamp configuration
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd)
+{
+ if (!netif_running(tc6->netdev))
+ return -EINVAL;
+
+ if (cmd == SIOCSHWTSTAMP || cmd == SIOCGHWTSTAMP)
+ return oa_tc6_tstamp_ioctl(tc6, rq, cmd);
+ else
+ return phy_do_ioctl_running(tc6->netdev, rq, cmd);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_ioctl);
+
static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length,
enum oa_tc6_register_op reg_op)
{
@@ -538,6 +693,9 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
return ret;
}
+ if ((value & STATUS0_TTSCA_MASK) != 0)
+ oa_tc6_events_handle(tc6, value & STATUS0_TTSCA_MASK);
+
/* Clear the error interrupts status */
ret = oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, value);
if (ret) {
@@ -609,6 +767,8 @@ static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer)
static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6)
{
+ oa_tc6_update_ts_in_rx_skb(tc6);
+
tc6->rx_skb->protocol = eth_type_trans(tc6->rx_skb, tc6->netdev);
tc6->netdev->stats.rx_packets++;
tc6->netdev->stats.rx_bytes += tc6->rx_skb->len;
@@ -623,24 +783,29 @@ static void oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length)
memcpy(skb_put(tc6->rx_skb, length), payload, length);
}
-static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6)
+static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6, u32 footer)
{
+ struct oa_tc6_ts_info_rx *ski;
+
tc6->rx_skb = netdev_alloc_skb_ip_align(tc6->netdev, tc6->netdev->mtu +
- ETH_HLEN + ETH_FCS_LEN);
+ ETH_HLEN + ETH_FCS_LEN + OA_TC6_TSTAMP_SZ);
if (!tc6->rx_skb) {
tc6->netdev->stats.rx_dropped++;
return -ENOMEM;
}
+ ski = oa_tc6_tsinfo_rx(tc6->rx_skb);
+ ski->rtsa = FIELD_GET(OA_TC6_DATA_FOOTER_RTSA_VALID, footer);
+ ski->rtsp = FIELD_GET(OA_TC6_DATA_FOOTER_RTSP_VALID, footer);
return 0;
}
static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload,
- u16 size)
+ u16 size, u32 footer)
{
int ret;
- ret = oa_tc6_allocate_rx_skb(tc6);
+ ret = oa_tc6_allocate_rx_skb(tc6, footer);
if (ret)
return ret;
@@ -651,11 +816,11 @@ static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload,
return 0;
}
-static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size)
+static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size, u32 footer)
{
int ret;
- ret = oa_tc6_allocate_rx_skb(tc6);
+ ret = oa_tc6_allocate_rx_skb(tc6, footer);
if (ret)
return ret;
@@ -700,7 +865,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
size = end_byte_offset + 1 - start_byte_offset;
return oa_tc6_prcs_complete_rx_frame(tc6,
&data[start_byte_offset],
- size);
+ size, footer);
}
/* Process the chunk with only rx frame start */
@@ -708,7 +873,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset;
return oa_tc6_prcs_rx_frame_start(tc6,
&data[start_byte_offset],
- size);
+ size, footer);
}
/* Process the chunk with only rx frame end */
@@ -733,7 +898,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset;
return oa_tc6_prcs_rx_frame_start(tc6,
&data[start_byte_offset],
- size);
+ size, footer);
}
/* Process the chunk with ongoing rx frame data */
@@ -787,13 +952,15 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)
}
static __be32 oa_tc6_prepare_data_header(bool data_valid, bool start_valid,
- bool end_valid, u8 end_byte_offset)
+ bool end_valid, u8 end_byte_offset,
+ u8 tsc)
{
u32 header = FIELD_PREP(OA_TC6_DATA_HEADER_DATA_NOT_CTRL,
OA_TC6_DATA_HEADER) |
FIELD_PREP(OA_TC6_DATA_HEADER_DATA_VALID, data_valid) |
FIELD_PREP(OA_TC6_DATA_HEADER_START_VALID, start_valid) |
FIELD_PREP(OA_TC6_DATA_HEADER_END_VALID, end_valid) |
+ FIELD_PREP(OA_TC6_DATA_HEADER_TSC_OFFSET, tsc) |
FIELD_PREP(OA_TC6_DATA_HEADER_END_BYTE_OFFSET,
end_byte_offset);
@@ -812,6 +979,7 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6)
enum oa_tc6_data_start_valid_info start_valid;
u8 end_byte_offset = 0;
u16 length_to_copy;
+ u8 tsc = 0;
/* Initial value is assigned here to avoid more than 80 characters in
* the declaration place.
@@ -821,8 +989,10 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6)
/* Set start valid if the current tx chunk contains the start of the tx
* ethernet frame.
*/
- if (!tc6->tx_skb_offset)
+ if (!tc6->tx_skb_offset) {
start_valid = OA_TC6_DATA_START_VALID;
+ tsc = oa_tc6_tsinfo_tx(tc6->ongoing_tx_skb)->tsc;
+ }
/* If the remaining tx skb length is more than the chunk payload size of
* 64 bytes then copy only 64 bytes and leave the ongoing tx skb for
@@ -843,12 +1013,17 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6)
tc6->tx_skb_offset = 0;
tc6->netdev->stats.tx_bytes += tc6->ongoing_tx_skb->len;
tc6->netdev->stats.tx_packets++;
- kfree_skb(tc6->ongoing_tx_skb);
+
+ /* Free the ones that are not saved for later processing,
+ * like timestamping.
+ */
+ if (!(skb_shinfo(tc6->ongoing_tx_skb)->tx_flags & SKBTX_IN_PROGRESS))
+ kfree_skb(tc6->ongoing_tx_skb);
tc6->ongoing_tx_skb = NULL;
}
*tx_buf = oa_tc6_prepare_data_header(OA_TC6_DATA_VALID, start_valid,
- end_valid, end_byte_offset);
+ end_valid, end_byte_offset, tsc);
tc6->spi_data_tx_buf_offset += OA_TC6_CHUNK_SIZE;
}
@@ -866,6 +1041,8 @@ static u16 oa_tc6_prepare_spi_tx_buf_for_tx_skbs(struct oa_tc6 *tc6)
tc6->ongoing_tx_skb = tc6->waiting_tx_skb;
tc6->waiting_tx_skb = NULL;
spin_unlock_bh(&tc6->tx_skb_lock);
+ oa_tc6_defer_for_hwtstamp(tc6,
+ tc6->ongoing_tx_skb);
}
if (!tc6->ongoing_tx_skb)
break;
@@ -882,7 +1059,7 @@ static void oa_tc6_add_empty_chunks_to_spi_buf(struct oa_tc6 *tc6,
header = oa_tc6_prepare_data_header(OA_TC6_DATA_INVALID,
OA_TC6_DATA_START_INVALID,
- OA_TC6_DATA_END_INVALID, 0);
+ OA_TC6_DATA_END_INVALID, 0, false);
while (needed_empty_chunks--) {
__be32 *tx_buf = tc6->spi_data_tx_buf +
@@ -1073,6 +1250,7 @@ netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb)
spin_lock_bh(&tc6->tx_skb_lock);
tc6->waiting_tx_skb = skb;
spin_unlock_bh(&tc6->tx_skb_lock);
+ oa_tc6_tsinfo_tx(skb)->tsc = 0;
/* Wake spi kthread to perform spi transfer */
wake_up_interruptible(&tc6->spi_wq);
@@ -1103,6 +1281,8 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
SET_NETDEV_DEV(netdev, &spi->dev);
mutex_init(&tc6->spi_ctrl_lock);
spin_lock_init(&tc6->tx_skb_lock);
+ tc6->tx_ts_idx = OA_TC6_TTSCA_REG_ID;
+ INIT_LIST_HEAD(&tc6->tx_ts_skb_q);
/* Set the SPI controller to pump at realtime priority */
tc6->spi->rt = true;
@@ -1168,6 +1348,12 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
goto phy_exit;
}
+ ret = oa_tc6_update_standard_capability(tc6);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "Failed to read capability\n");
+ goto phy_exit;
+ }
+
init_waitqueue_head(&tc6->spi_wq);
tc6->spi_thread = kthread_run(oa_tc6_spi_thread_handler, tc6,
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c
new file mode 100644
index 000000000000..921191ec6829
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Support for hardware timestamping feature for OPEN Alliance
+ * 10BASE‑T1x MAC‑PHY Serial Interface framework
+ *
+ * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+ */
+
+#include <linux/hrtimer.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/phylink.h>
+#include <linux/spi/spi.h>
+#include <linux/oa_tc6.h>
+#include <linux/net_tstamp.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/ktime.h>
+#include <linux/errno.h>
+
+#include "oa_tc6_std_def.h"
+
+/**
+ * oa_tc6_ptp_register - Registers clock related callbacks
+ * @tc6: oa_tc6 struct.
+ * @info: Describes a PTP hardware clock
+ *
+ * Description: Vendors are expected to set the hardware timestamp
+ * related callbacks before calling this function.
+ */
+int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info)
+{
+ /* Not supporting hardware timestamp isn't an error */
+ if (!tc6->hw_tstamp_supported)
+ return 0;
+
+ snprintf(info->name, sizeof(info->name), "%s",
+ "OA TC6 PTP clock");
+ tc6->ptp_clock = ptp_clock_register(info, &tc6->spi->dev);
+ if (IS_ERR(tc6->ptp_clock)) {
+ dev_err(&tc6->spi->dev, "Registration of %s failed",
+ info->name);
+ return -EFAULT;
+ }
+ dev_info(&tc6->spi->dev, "%s registered. index %d", info->name,
+ ptp_clock_index(tc6->ptp_clock));
+ return 0;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_ptp_register);
+
+/**
+ * oa_tc6_ptp_unregister - Unregisters clock related callbacks
+ * @tc6: oa_tc6 struct.
+ */
+void oa_tc6_ptp_unregister(struct oa_tc6 *tc6)
+{
+ if (tc6->ptp_clock)
+ ptp_clock_unregister(tc6->ptp_clock);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_ptp_unregister);
+
+MODULE_DESCRIPTION("OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface Lib");
+MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
index 2d8e28fb46fc..3a12b3228f30 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
@@ -22,6 +22,7 @@
/* Standard Capabilities Register */
#define OA_TC6_REG_STDCAP 0x0002
#define STDCAP_DIRECT_PHY_REG_ACCESS BIT(8)
+#define STDCAP_FRAME_TIMESTAMP_CAPABILITY BIT(6)
/* Reset Control and Status Register */
#define OA_TC6_REG_RESET 0x0003
@@ -31,9 +32,14 @@
#define OA_TC6_REG_CONFIG0 0x0004
#define CONFIG0_SYNC BIT(15)
#define CONFIG0_ZARFE_ENABLE BIT(12)
+#define CONFIG0_FTSE_ENABLE BIT(7)
/* Status Register #0 */
#define OA_TC6_REG_STATUS0 0x0008
+#define STATUS0_TTSCAC BIT(10)
+#define STATUS0_TTSCAB BIT(9)
+#define STATUS0_TTSCAA BIT(8)
+#define STATUS0_TTSCA_MASK GENMASK(10, 8)
#define STATUS0_RESETC BIT(6) /* Reset Complete */
#define STATUS0_HEADER_ERROR BIT(5)
#define STATUS0_LOSS_OF_FRAME_ERROR BIT(4)
@@ -47,6 +53,7 @@
/* Interrupt Mask Register #0 */
#define OA_TC6_REG_INT_MASK0 0x000C
+#define INT_MASK0_TTSCA_MASK GENMASK(10, 8)
#define INT_MASK0_HEADER_ERR_MASK BIT(5)
#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4)
#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3)
@@ -56,6 +63,9 @@
#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00
#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F
+/* Tx timestamp capture register A (high) */
+#define OA_TC6_REG_TTSCA_HIGH (0x1010)
+
/* Control command header */
#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31)
#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29)
@@ -71,6 +81,7 @@
#define OA_TC6_DATA_HEADER_START_WORD_OFFSET GENMASK(19, 16)
#define OA_TC6_DATA_HEADER_END_VALID BIT(14)
#define OA_TC6_DATA_HEADER_END_BYTE_OFFSET GENMASK(13, 8)
+#define OA_TC6_DATA_HEADER_TSC_OFFSET GENMASK(7, 6)
#define OA_TC6_DATA_HEADER_PARITY BIT(0)
/* Data footer */
@@ -82,6 +93,8 @@
#define OA_TC6_DATA_FOOTER_START_VALID BIT(20)
#define OA_TC6_DATA_FOOTER_START_WORD_OFFSET GENMASK(19, 16)
#define OA_TC6_DATA_FOOTER_END_VALID BIT(14)
+#define OA_TC6_DATA_FOOTER_RTSA_VALID BIT(7)
+#define OA_TC6_DATA_FOOTER_RTSP_VALID BIT(6)
#define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8)
#define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1)
@@ -103,6 +116,12 @@
#define STATUS0_RESETC_POLL_DELAY 1000
#define STATUS0_RESETC_POLL_TIMEOUT 1000000
+#define OA_TC6_TSTAMP_SZ 8
+
+#define OA_TC6_TTSCA_REG_ID 1
+#define OA_TC6_TTSCB_REG_ID 2
+#define OA_TC6_TTSCC_REG_ID 3
+
/* Internal structure for MAC-PHY drivers */
struct oa_tc6 {
struct device *dev;
@@ -127,6 +146,17 @@ struct oa_tc6 {
u8 rx_chunks_available;
bool rx_buf_overflow;
bool int_flag;
+ struct ptp_clock_info ptp_clock_info;
+ struct hwtstamp_config ts_config;
+ struct list_head tx_ts_skb_q;
+ struct ptp_clock *ptp_clock;
+ bool hw_tstamp_supported;
+ bool hw_tstamp_enabled;
+ u32 tx_hwtstamp_pkts;
+ u32 tx_hwtstamp_lost;
+ u32 tx_hwtstamp_err;
+ int vend1_mms;
+ u8 tx_ts_idx;
};
enum oa_tc6_header_type {
@@ -153,5 +183,8 @@ enum oa_tc6_data_end_valid_info {
OA_TC6_DATA_END_INVALID,
OA_TC6_DATA_END_VALID,
};
+
+int oa_tc6_tstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd);
+
#endif /* OA_TC6_STD_DEF_H */
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
new file mode 100644
index 000000000000..272701a4081d
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface framework
+ *
+ * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/iopoll.h>
+#include <linux/mdio.h>
+#include <linux/phy.h>
+#include <linux/oa_tc6.h>
+
+#include "oa_tc6_std_def.h"
+
+static int oa_tc6_set_hwtstamp_settings(struct oa_tc6 *tc6)
+{
+ u32 cfg0, irqm, status0;
+ int ret;
+
+ ret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, &cfg0);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "Failed to read CFG0 register\n");
+ goto out;
+ }
+
+ ret = oa_tc6_read_register(tc6, OA_TC6_REG_INT_MASK0, &irqm);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "failed to read IRQM register\n");
+ goto out;
+ }
+
+ if (tc6->ts_config.tx_type == HWTSTAMP_TX_ON ||
+ tc6->ts_config.rx_filter == HWTSTAMP_FILTER_ALL)
+ cfg0 |= CONFIG0_FTSE_ENABLE;
+ else
+ cfg0 &= ~CONFIG0_FTSE_ENABLE;
+
+ if (tc6->ts_config.tx_type == HWTSTAMP_TX_ON)
+ irqm &= ~INT_MASK0_TTSCA_MASK;
+ else
+ irqm |= INT_MASK0_TTSCA_MASK;
+
+ /* Clear timestamp related IRQs */
+ status0 = STATUS0_TTSCA_MASK;
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, status0);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "failed to write STATUS0 register\n");
+ goto out;
+ }
+
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, irqm);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "failed to write IRQM register\n");
+ goto out;
+ }
+
+ ret = oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, cfg0);
+ if (ret) {
+ dev_err(&tc6->spi->dev, "failed to write CFG0 register\n");
+ goto out;
+ }
+ if (cfg0 & CONFIG0_FTSE_ENABLE)
+ tc6->hw_tstamp_enabled = true;
+ else
+ tc6->hw_tstamp_enabled = false;
+out:
+ return ret;
+}
+
+/**
+ * oa_tc6_hwtstamp_get - gets hardware timestamp config
+ * @tc6: oa_tc6 struct.
+ * @cfg: kernel copy of hardware timestamp config
+ */
+void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg)
+{
+ hwtstamp_config_to_kernel(cfg, &tc6->ts_config);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_hwtstamp_get);
+
+/**
+ * oa_tc6_hwtstamp_set - sets hardware timestamp config
+ * @tc6: oa_tc6 struct.
+ * @cfg: kernel copy of hardware timestamp config
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg)
+{
+ if (!netif_running(tc6->netdev))
+ return -EIO;
+
+ if (!tc6->hw_tstamp_supported)
+ return -EOPNOTSUPP;
+
+ switch (cfg->tx_type) {
+ case HWTSTAMP_TX_OFF:
+ case HWTSTAMP_TX_ON:
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (cfg->rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ case HWTSTAMP_FILTER_ALL:
+ case HWTSTAMP_FILTER_SOME:
+ case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ case HWTSTAMP_FILTER_NTP_ALL:
+ break;
+ default:
+ return -ERANGE;
+ }
+ hwtstamp_config_from_kernel(&tc6->ts_config, cfg);
+
+ /* Supports timestamping all traffic */
+ if (cfg->rx_filter != HWTSTAMP_FILTER_NONE)
+ tc6->ts_config.rx_filter = HWTSTAMP_FILTER_ALL;
+ return oa_tc6_set_hwtstamp_settings(tc6);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_hwtstamp_set);
+
+/**
+ * oa_tc6_get_ts_stats - Provides timestamping stats
+ * @tc6: oa_tc6 struct.
+ * @ts_stats: ethtool data structure to fill in
+ */
+void oa_tc6_get_ts_stats(struct oa_tc6 *tc6,
+ struct ethtool_ts_stats *stats)
+{
+ stats->pkts = tc6->tx_hwtstamp_pkts;
+ stats->err = tc6->tx_hwtstamp_err;
+ stats->lost = tc6->tx_hwtstamp_lost;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_get_ts_stats);
+
+int oa_tc6_tstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd)
+{
+ struct kernel_hwtstamp_config kcfg;
+ struct hwtstamp_config tscfg;
+ int ret = 0;
+
+ if (!tc6->hw_tstamp_supported)
+ return -EOPNOTSUPP;
+
+ if (cmd == SIOCSHWTSTAMP) {
+ if (copy_from_user(&tscfg, rq->ifr_data,
+ sizeof(tscfg)))
+ return -EFAULT;
+
+ if (tscfg.flags)
+ return -EINVAL;
+ hwtstamp_config_to_kernel(&kcfg, &tscfg);
+ ret = oa_tc6_hwtstamp_set(tc6, &kcfg);
+ if (ret)
+ return ret;
+ }
+ if (copy_to_user(rq->ifr_data, &tc6->ts_config,
+ sizeof(tc6->ts_config)))
+ ret = -EFAULT;
+ return ret;
+}
+
+/**
+ * oa_tc6_get_ts_info - Provides timestamp info for ethtool
+ * @tc6: oa_tc6 struct.
+ * @info: ethtool timestamping info structure
+ * @ts_stats: ethtool data structure to fill in
+ */
+int oa_tc6_get_ts_info(struct oa_tc6 *tc6,
+ struct kernel_ethtool_ts_info *info)
+{
+ if (!tc6->ptp_clock)
+ return ethtool_op_get_ts_info(tc6->netdev, info);
+
+ info->so_timestamping = SOF_TIMESTAMPING_RAW_HARDWARE |
+ SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE;
+ info->phc_index = ptp_clock_index(tc6->ptp_clock);
+ info->tx_types = BIT(HWTSTAMP_TX_ON);
+ info->rx_filters = BIT(HWTSTAMP_FILTER_ALL);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_get_ts_info);
+
+MODULE_DESCRIPTION("OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface Lib");
+MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 39b80033dfa9..4047c22a366a 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -12,6 +12,7 @@
#include <linux/etherdevice.h>
#include <linux/spi/spi.h>
+#include <linux/ptp_clock_kernel.h>
/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in
* the OPEN Alliance specification.
@@ -36,4 +37,15 @@ int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
u8 length);
netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb);
int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6);
+int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info);
+int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd);
+int oa_tc6_get_ts_info(struct oa_tc6 *tc6,
+ struct kernel_ethtool_ts_info *ts_info);
+void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg);
+void oa_tc6_get_ts_stats(struct oa_tc6 *tc6,
+ struct ethtool_ts_stats *ts_stats);
+int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg);
+void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);
#endif /* _LINUX_OA_TC6_H */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v4 05/16] net: ethernet: oa_tc6: Move constant definitions to header file
From: Selvamani Rajagopal via B4 Relay @ 2026-06-06 5:42 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260605-s2500-mac-phy-support-v4-0-de0fbc13c6d8@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
To help other source files within the module share the
constant definitions, they are moved to a header file.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
drivers/net/ethernet/oa_tc6/oa_tc6.c | 145 +------------------------
drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 157 +++++++++++++++++++++++++++
include/linux/oa_tc6.h | 15 +++
3 files changed, 173 insertions(+), 144 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
index 91a906a7918a..c7d70d37ba53 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
@@ -11,150 +11,7 @@
#include <linux/phy.h>
#include <linux/oa_tc6.h>
-/* OPEN Alliance TC6 registers */
-/* Standard Capabilities Register */
-#define OA_TC6_REG_STDCAP 0x0002
-#define STDCAP_DIRECT_PHY_REG_ACCESS BIT(8)
-
-/* Reset Control and Status Register */
-#define OA_TC6_REG_RESET 0x0003
-#define RESET_SWRESET BIT(0) /* Software Reset */
-
-/* Configuration Register #0 */
-#define OA_TC6_REG_CONFIG0 0x0004
-#define CONFIG0_SYNC BIT(15)
-#define CONFIG0_ZARFE_ENABLE BIT(12)
-
-/* Status Register #0 */
-#define OA_TC6_REG_STATUS0 0x0008
-#define STATUS0_RESETC BIT(6) /* Reset Complete */
-#define STATUS0_HEADER_ERROR BIT(5)
-#define STATUS0_LOSS_OF_FRAME_ERROR BIT(4)
-#define STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3)
-#define STATUS0_TX_PROTOCOL_ERROR BIT(0)
-
-/* Buffer Status Register */
-#define OA_TC6_REG_BUFFER_STATUS 0x000B
-#define BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8)
-#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0)
-
-/* Interrupt Mask Register #0 */
-#define OA_TC6_REG_INT_MASK0 0x000C
-#define INT_MASK0_HEADER_ERR_MASK BIT(5)
-#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4)
-#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3)
-#define INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0)
-
-/* PHY Clause 22 registers base address and mask */
-#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00
-#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F
-
-/* Control command header */
-#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31)
-#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29)
-#define OA_TC6_CTRL_HEADER_MEM_MAP_SELECTOR GENMASK(27, 24)
-#define OA_TC6_CTRL_HEADER_ADDR GENMASK(23, 8)
-#define OA_TC6_CTRL_HEADER_LENGTH GENMASK(7, 1)
-#define OA_TC6_CTRL_HEADER_PARITY BIT(0)
-
-/* Data header */
-#define OA_TC6_DATA_HEADER_DATA_NOT_CTRL BIT(31)
-#define OA_TC6_DATA_HEADER_DATA_VALID BIT(21)
-#define OA_TC6_DATA_HEADER_START_VALID BIT(20)
-#define OA_TC6_DATA_HEADER_START_WORD_OFFSET GENMASK(19, 16)
-#define OA_TC6_DATA_HEADER_END_VALID BIT(14)
-#define OA_TC6_DATA_HEADER_END_BYTE_OFFSET GENMASK(13, 8)
-#define OA_TC6_DATA_HEADER_PARITY BIT(0)
-
-/* Data footer */
-#define OA_TC6_DATA_FOOTER_EXTENDED_STS BIT(31)
-#define OA_TC6_DATA_FOOTER_RXD_HEADER_BAD BIT(30)
-#define OA_TC6_DATA_FOOTER_CONFIG_SYNC BIT(29)
-#define OA_TC6_DATA_FOOTER_RX_CHUNKS GENMASK(28, 24)
-#define OA_TC6_DATA_FOOTER_DATA_VALID BIT(21)
-#define OA_TC6_DATA_FOOTER_START_VALID BIT(20)
-#define OA_TC6_DATA_FOOTER_START_WORD_OFFSET GENMASK(19, 16)
-#define OA_TC6_DATA_FOOTER_END_VALID BIT(14)
-#define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8)
-#define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1)
-
-/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in the
- * OPEN Alliance specification.
- */
-#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */
-#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */
-#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */
-#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */
-#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */
-
-#define OA_TC6_CTRL_HEADER_SIZE 4
-#define OA_TC6_CTRL_REG_VALUE_SIZE 4
-#define OA_TC6_CTRL_IGNORED_SIZE 4
-#define OA_TC6_CTRL_MAX_REGISTERS 128
-#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\
- (OA_TC6_CTRL_MAX_REGISTERS *\
- OA_TC6_CTRL_REG_VALUE_SIZE) +\
- OA_TC6_CTRL_IGNORED_SIZE)
-#define OA_TC6_CHUNK_PAYLOAD_SIZE 64
-#define OA_TC6_DATA_HEADER_SIZE 4
-#define OA_TC6_CHUNK_SIZE (OA_TC6_DATA_HEADER_SIZE +\
- OA_TC6_CHUNK_PAYLOAD_SIZE)
-#define OA_TC6_MAX_TX_CHUNKS 48
-#define OA_TC6_SPI_DATA_BUF_SIZE (OA_TC6_MAX_TX_CHUNKS *\
- OA_TC6_CHUNK_SIZE)
-#define STATUS0_RESETC_POLL_DELAY 1000
-#define STATUS0_RESETC_POLL_TIMEOUT 1000000
-
-/* Internal structure for MAC-PHY drivers */
-struct oa_tc6 {
- struct device *dev;
- struct net_device *netdev;
- struct phy_device *phydev;
- struct mii_bus *mdiobus;
- struct spi_device *spi;
- struct mutex spi_ctrl_lock; /* Protects spi control transfer */
- spinlock_t tx_skb_lock; /* Protects tx skb handling */
- void *spi_ctrl_tx_buf;
- void *spi_ctrl_rx_buf;
- void *spi_data_tx_buf;
- void *spi_data_rx_buf;
- struct sk_buff *ongoing_tx_skb;
- struct sk_buff *waiting_tx_skb;
- struct sk_buff *rx_skb;
- struct task_struct *spi_thread;
- wait_queue_head_t spi_wq;
- u16 tx_skb_offset;
- u16 spi_data_tx_buf_offset;
- u16 tx_credits;
- u8 rx_chunks_available;
- bool rx_buf_overflow;
- bool int_flag;
-};
-
-enum oa_tc6_header_type {
- OA_TC6_CTRL_HEADER,
- OA_TC6_DATA_HEADER,
-};
-
-enum oa_tc6_register_op {
- OA_TC6_CTRL_REG_READ = 0,
- OA_TC6_CTRL_REG_WRITE = 1,
-};
-
-enum oa_tc6_data_valid_info {
- OA_TC6_DATA_INVALID,
- OA_TC6_DATA_VALID,
-};
-
-enum oa_tc6_data_start_valid_info {
- OA_TC6_DATA_START_INVALID,
- OA_TC6_DATA_START_VALID,
-};
-
-enum oa_tc6_data_end_valid_info {
- OA_TC6_DATA_END_INVALID,
- OA_TC6_DATA_END_VALID,
-};
+#include "oa_tc6_std_def.h"
static int oa_tc6_spi_transfer(struct oa_tc6 *tc6,
enum oa_tc6_header_type header_type, u16 length)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
new file mode 100644
index 000000000000..2d8e28fb46fc
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Register and driver related definitions to support
+ * OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface framework.
+ *
+ * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
+ */
+
+#ifndef OA_TC6_STD_DEF_H
+#define OA_TC6_STD_DEF_H
+
+#include <linux/ptp_clock_kernel.h>
+#include <linux/net_tstamp.h>
+#include <linux/netdevice.h>
+#include <linux/spi/spi.h>
+#include <linux/skbuff.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/phy.h>
+
+/* OPEN Alliance TC6 registers */
+/* Standard Capabilities Register */
+#define OA_TC6_REG_STDCAP 0x0002
+#define STDCAP_DIRECT_PHY_REG_ACCESS BIT(8)
+
+/* Reset Control and Status Register */
+#define OA_TC6_REG_RESET 0x0003
+#define RESET_SWRESET BIT(0) /* Software Reset */
+
+/* Configuration Register #0 */
+#define OA_TC6_REG_CONFIG0 0x0004
+#define CONFIG0_SYNC BIT(15)
+#define CONFIG0_ZARFE_ENABLE BIT(12)
+
+/* Status Register #0 */
+#define OA_TC6_REG_STATUS0 0x0008
+#define STATUS0_RESETC BIT(6) /* Reset Complete */
+#define STATUS0_HEADER_ERROR BIT(5)
+#define STATUS0_LOSS_OF_FRAME_ERROR BIT(4)
+#define STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3)
+#define STATUS0_TX_PROTOCOL_ERROR BIT(0)
+
+/* Buffer Status Register */
+#define OA_TC6_REG_BUFFER_STATUS 0x000B
+#define BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8)
+#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0)
+
+/* Interrupt Mask Register #0 */
+#define OA_TC6_REG_INT_MASK0 0x000C
+#define INT_MASK0_HEADER_ERR_MASK BIT(5)
+#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4)
+#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3)
+#define INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0)
+
+/* PHY Clause 22 registers base address and mask */
+#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00
+#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F
+
+/* Control command header */
+#define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31)
+#define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29)
+#define OA_TC6_CTRL_HEADER_MEM_MAP_SELECTOR GENMASK(27, 24)
+#define OA_TC6_CTRL_HEADER_ADDR GENMASK(23, 8)
+#define OA_TC6_CTRL_HEADER_LENGTH GENMASK(7, 1)
+#define OA_TC6_CTRL_HEADER_PARITY BIT(0)
+
+/* Data header */
+#define OA_TC6_DATA_HEADER_DATA_NOT_CTRL BIT(31)
+#define OA_TC6_DATA_HEADER_DATA_VALID BIT(21)
+#define OA_TC6_DATA_HEADER_START_VALID BIT(20)
+#define OA_TC6_DATA_HEADER_START_WORD_OFFSET GENMASK(19, 16)
+#define OA_TC6_DATA_HEADER_END_VALID BIT(14)
+#define OA_TC6_DATA_HEADER_END_BYTE_OFFSET GENMASK(13, 8)
+#define OA_TC6_DATA_HEADER_PARITY BIT(0)
+
+/* Data footer */
+#define OA_TC6_DATA_FOOTER_EXTENDED_STS BIT(31)
+#define OA_TC6_DATA_FOOTER_RXD_HEADER_BAD BIT(30)
+#define OA_TC6_DATA_FOOTER_CONFIG_SYNC BIT(29)
+#define OA_TC6_DATA_FOOTER_RX_CHUNKS GENMASK(28, 24)
+#define OA_TC6_DATA_FOOTER_DATA_VALID BIT(21)
+#define OA_TC6_DATA_FOOTER_START_VALID BIT(20)
+#define OA_TC6_DATA_FOOTER_START_WORD_OFFSET GENMASK(19, 16)
+#define OA_TC6_DATA_FOOTER_END_VALID BIT(14)
+#define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8)
+#define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1)
+
+#define OA_TC6_CTRL_HEADER_SIZE 4
+#define OA_TC6_CTRL_REG_VALUE_SIZE 4
+#define OA_TC6_CTRL_IGNORED_SIZE 4
+#define OA_TC6_CTRL_MAX_REGISTERS 128
+#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\
+ (OA_TC6_CTRL_MAX_REGISTERS *\
+ OA_TC6_CTRL_REG_VALUE_SIZE) +\
+ OA_TC6_CTRL_IGNORED_SIZE)
+#define OA_TC6_CHUNK_PAYLOAD_SIZE 64
+#define OA_TC6_DATA_HEADER_SIZE 4
+#define OA_TC6_CHUNK_SIZE (OA_TC6_DATA_HEADER_SIZE +\
+ OA_TC6_CHUNK_PAYLOAD_SIZE)
+#define OA_TC6_MAX_TX_CHUNKS 48
+#define OA_TC6_SPI_DATA_BUF_SIZE (OA_TC6_MAX_TX_CHUNKS *\
+ OA_TC6_CHUNK_SIZE)
+#define STATUS0_RESETC_POLL_DELAY 1000
+#define STATUS0_RESETC_POLL_TIMEOUT 1000000
+
+/* Internal structure for MAC-PHY drivers */
+struct oa_tc6 {
+ struct device *dev;
+ struct net_device *netdev;
+ struct phy_device *phydev;
+ struct mii_bus *mdiobus;
+ struct spi_device *spi;
+ struct mutex spi_ctrl_lock; /* Protects spi control transfer */
+ spinlock_t tx_skb_lock; /* Protects tx skb handling */
+ void *spi_ctrl_tx_buf;
+ void *spi_ctrl_rx_buf;
+ void *spi_data_tx_buf;
+ void *spi_data_rx_buf;
+ struct sk_buff *ongoing_tx_skb;
+ struct sk_buff *waiting_tx_skb;
+ struct sk_buff *rx_skb;
+ struct task_struct *spi_thread;
+ wait_queue_head_t spi_wq;
+ u16 tx_skb_offset;
+ u16 spi_data_tx_buf_offset;
+ u16 tx_credits;
+ u8 rx_chunks_available;
+ bool rx_buf_overflow;
+ bool int_flag;
+};
+
+enum oa_tc6_header_type {
+ OA_TC6_CTRL_HEADER,
+ OA_TC6_DATA_HEADER,
+};
+
+enum oa_tc6_register_op {
+ OA_TC6_CTRL_REG_READ = 0,
+ OA_TC6_CTRL_REG_WRITE = 1,
+};
+
+enum oa_tc6_data_valid_info {
+ OA_TC6_DATA_INVALID,
+ OA_TC6_DATA_VALID,
+};
+
+enum oa_tc6_data_start_valid_info {
+ OA_TC6_DATA_START_INVALID,
+ OA_TC6_DATA_START_VALID,
+};
+
+enum oa_tc6_data_end_valid_info {
+ OA_TC6_DATA_END_INVALID,
+ OA_TC6_DATA_END_VALID,
+};
+#endif /* OA_TC6_STD_DEF_H */
+
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 15f58e3c56c7..39b80033dfa9 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -7,9 +7,23 @@
* Author: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
*/
+#ifndef _LINUX_OA_TC6_H
+#define _LINUX_OA_TC6_H
+
#include <linux/etherdevice.h>
#include <linux/spi/spi.h>
+/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in
+ * the OPEN Alliance specification.
+ */
+#define OA_TC6_PHY_C45_MAC_MMS1 1 /* No MMD */
+#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */
+#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */
+#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */
+#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */
+#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */
+#define OA_TC6_PHY_C45_VS_MMS12 12 /* for vendors */
+
struct oa_tc6;
struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev);
@@ -22,3 +36,4 @@ int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
u8 length);
netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb);
int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6);
+#endif /* _LINUX_OA_TC6_H */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v4 07/16] net: ethernet: oa_tc6: Support for vendor specific MMS
From: Selvamani Rajagopal via B4 Relay @ 2026-06-06 5:42 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260605-s2500-mac-phy-support-v4-0-de0fbc13c6d8@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
OPEN Alliance 10BASE-T1x Serial Interface specification, table 6
allows vendors to use any memory map select (MMS) value between
10 and 15. This new API interface enables vendor to map one of
thes MMS values to MDIO_MMD_VEND1.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
drivers/net/ethernet/oa_tc6/oa_tc6.c | 20 +++++++++++++++++---
include/linux/oa_tc6.h | 1 +
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
index 9410cecfdc2a..2f9ace655e81 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
@@ -202,6 +202,17 @@ int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd)
}
EXPORT_SYMBOL_GPL(oa_tc6_ioctl);
+/**
+ * Add vendor specific MDIO_MMD to OA TC6 MMS mapper value.
+ * @tc6: oa_tc6 struct.
+ * @mms: vendor defined MMS value for VEND1 mdio device.
+ */
+void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms)
+{
+ tc6->vend1_mms = mms;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_set_vend1_mms);
+
static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length,
enum oa_tc6_register_op reg_op)
{
@@ -455,7 +466,7 @@ static int oa_tc6_mdiobus_write(struct mii_bus *bus, int addr, int regnum,
val);
}
-static int oa_tc6_get_phy_c45_mms(int devnum)
+static int oa_tc6_get_phy_c45_mms(struct oa_tc6 *tc6, int devnum)
{
switch (devnum) {
case MDIO_MMD_PCS:
@@ -468,6 +479,8 @@ static int oa_tc6_get_phy_c45_mms(int devnum)
return OA_TC6_PHY_C45_AUTO_NEG_MMS5;
case MDIO_MMD_POWER_UNIT:
return OA_TC6_PHY_C45_POWER_UNIT_MMS6;
+ case MDIO_MMD_VEND1:
+ return tc6->vend1_mms;
default:
return -EOPNOTSUPP;
}
@@ -480,7 +493,7 @@ static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,
u32 regval;
int ret;
- ret = oa_tc6_get_phy_c45_mms(devnum);
+ ret = oa_tc6_get_phy_c45_mms(tc6, devnum);
if (ret < 0)
return ret;
@@ -497,7 +510,7 @@ static int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
struct oa_tc6 *tc6 = bus->priv;
int ret;
- ret = oa_tc6_get_phy_c45_mms(devnum);
+ ret = oa_tc6_get_phy_c45_mms(tc6, devnum);
if (ret < 0)
return ret;
@@ -1281,6 +1294,7 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
SET_NETDEV_DEV(netdev, &spi->dev);
mutex_init(&tc6->spi_ctrl_lock);
spin_lock_init(&tc6->tx_skb_lock);
+ tc6->vend1_mms = -EOPNOTSUPP;
tc6->tx_ts_idx = OA_TC6_TTSCA_REG_ID;
INIT_LIST_HEAD(&tc6->tx_ts_skb_q);
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 4047c22a366a..a89151267713 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -47,5 +47,6 @@ void oa_tc6_get_ts_stats(struct oa_tc6 *tc6,
struct ethtool_ts_stats *ts_stats);
int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6,
struct kernel_hwtstamp_config *cfg);
+void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms);
void oa_tc6_ptp_unregister(struct oa_tc6 *tc6);
#endif /* _LINUX_OA_TC6_H */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v4 10/16] net: phy: ncn26000: Support for onsemi's S2500 internal phy
From: Selvamani Rajagopal via B4 Relay @ 2026-06-06 5:42 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260605-s2500-mac-phy-support-v4-0-de0fbc13c6d8@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Adding support for internal PHY of the integrated
media access controller S2500. PLCA tx opportunity timer's
default value is correct in this device, compared to
NCN26000.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
MAINTAINERS | 3 ++-
drivers/net/phy/ncn26000.c | 38 +++++++++++++++++++++++++++++++++-----
2 files changed, 35 insertions(+), 6 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 63b71197d385..98334235c924 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19971,7 +19971,8 @@ S: Maintained
F: arch/mips/boot/dts/ralink/omega2p.dts
ONSEMI ETHERNET PHY DRIVERS
-M: Piergiorgio Beruto <piergiorgio.beruto@gmail.com>
+M: Piergiorgio Beruto <pier.beruto@onsemi.com>
+M: Selva Rajagopal <selvamani.rajagopal@onsemi.com>
L: netdev@vger.kernel.org
S: Supported
W: http://www.onsemi.com
diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index cabdd83c614f..2c8601c3f94a 100644
--- a/drivers/net/phy/ncn26000.c
+++ b/drivers/net/phy/ncn26000.c
@@ -2,7 +2,7 @@
/*
* Driver for the onsemi 10BASE-T1S NCN26000 PHYs family.
*
- * Copyright 2022 onsemi
+ * Copyright 2026 onsemi
*/
#include <linux/kernel.h>
#include <linux/bitfield.h>
@@ -14,6 +14,7 @@
#include "mdio-open-alliance.h"
+#define PHY_ID_S2500 0x180FF411
#define PHY_ID_NCN26000 0x180FF5A1
#define NCN26000_REG_IRQ_CTL 16
@@ -37,13 +38,18 @@
static int ncn26000_config_init(struct phy_device *phydev)
{
+ int ret = 0;
+
/* HW bug workaround: the default value of the PLCA TO_TIMER should be
* 32, where the current version of NCN26000 reports 24. This will be
* fixed in future PHY versions. For the time being, we force the
* correct default here.
*/
- return phy_write_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_TOTMR,
- TO_TMR_DEFAULT);
+ if (phy_id_compare_model(phydev->drv->phy_id, PHY_ID_NCN26000))
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+ MDIO_OATC14_PLCA_TOTMR,
+ TO_TMR_DEFAULT);
+ return ret;
}
static int ncn26000_config_aneg(struct phy_device *phydev)
@@ -117,8 +123,8 @@ static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)
static int ncn26000_config_intr(struct phy_device *phydev)
{
- int ret;
u16 irqe;
+ int ret;
if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
// acknowledge IRQs
@@ -141,6 +147,26 @@ static int ncn26000_config_intr(struct phy_device *phydev)
}
static struct phy_driver ncn26000_driver[] = {
+ {
+ PHY_ID_MATCH_MODEL(PHY_ID_S2500),
+ .name = "S2500",
+ .features = PHY_BASIC_T1S_P2MP_FEATURES,
+ .config_init = ncn26000_config_init,
+ .config_intr = ncn26000_config_intr,
+ .config_aneg = ncn26000_config_aneg,
+ .read_status = ncn26000_read_status,
+ .handle_interrupt = ncn26000_handle_interrupt,
+ .set_plca_cfg = genphy_c45_plca_set_cfg,
+ .get_plca_cfg = genphy_c45_plca_get_cfg,
+ .get_plca_status = genphy_c45_plca_get_status,
+ .soft_reset = genphy_soft_reset,
+ .get_sqi = genphy_c45_oatc14_get_sqi,
+ .get_sqi_max = genphy_c45_oatc14_get_sqi_max,
+ .read_mmd = genphy_phy_read_mmd,
+ .write_mmd = genphy_phy_write_mmd,
+ .cable_test_get_status = genphy_c45_oatc14_cable_test_get_status,
+ .cable_test_start = genphy_c45_oatc14_cable_test_start,
+ },
{
PHY_ID_MATCH_MODEL(PHY_ID_NCN26000),
.name = "NCN26000",
@@ -161,11 +187,13 @@ module_phy_driver(ncn26000_driver);
static const struct mdio_device_id __maybe_unused ncn26000_tbl[] = {
{ PHY_ID_MATCH_MODEL(PHY_ID_NCN26000) },
+ { PHY_ID_MATCH_MODEL(PHY_ID_S2500) },
{ }
};
MODULE_DEVICE_TABLE(mdio, ncn26000_tbl);
-MODULE_AUTHOR("Piergiorgio Beruto");
+MODULE_AUTHOR("Piergiorgio Beruto <pier.beruto@onsemi.com>");
+MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>");
MODULE_DESCRIPTION("onsemi 10BASE-T1S PHY driver");
MODULE_LICENSE("Dual BSD/GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v4 09/16] net: ethernet: oa_tc6: read, write interface with MMS option
From: Selvamani Rajagopal via B4 Relay @ 2026-06-06 5:42 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260605-s2500-mac-phy-support-v4-0-de0fbc13c6d8@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
Vendors are allowed to use any memory map selector that
is between 10 and 15.
Current read/write API interface expects register address with
the value of MMS (memory map selector) embedded in it.
This requires vendors to encoding the address whenever the call
to read/write register is made. To avoid this extra step, and
to bring consistency in usage of the API by different vendors,
new APIs have been added to write and read registers with
MMS as one of the parameters.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
drivers/net/ethernet/microchip/lan865x/lan865x.c | 61 +++++++++------
drivers/net/ethernet/oa_tc6/oa_tc6.c | 97 +++++++++++++++++++++---
include/linux/oa_tc6.h | 8 ++
3 files changed, 131 insertions(+), 35 deletions(-)
diff --git a/drivers/net/ethernet/microchip/lan865x/lan865x.c b/drivers/net/ethernet/microchip/lan865x/lan865x.c
index 0277d9737369..3b555ee69804 100644
--- a/drivers/net/ethernet/microchip/lan865x/lan865x.c
+++ b/drivers/net/ethernet/microchip/lan865x/lan865x.c
@@ -13,27 +13,27 @@
#define DRV_NAME "lan8650"
/* MAC Network Control Register */
-#define LAN865X_REG_MAC_NET_CTL 0x00010000
+#define LAN865X_REG_MAC_NET_CTL 0x0
#define MAC_NET_CTL_TXEN BIT(3) /* Transmit Enable */
#define MAC_NET_CTL_RXEN BIT(2) /* Receive Enable */
/* MAC Network Configuration Reg */
-#define LAN865X_REG_MAC_NET_CFG 0x00010001
+#define LAN865X_REG_MAC_NET_CFG 0x1
#define MAC_NET_CFG_PROMISCUOUS_MODE BIT(4)
#define MAC_NET_CFG_MULTICAST_MODE BIT(6)
#define MAC_NET_CFG_UNICAST_MODE BIT(7)
/* MAC Hash Register Bottom */
-#define LAN865X_REG_MAC_L_HASH 0x00010020
+#define LAN865X_REG_MAC_L_HASH 0x20
/* MAC Hash Register Top */
-#define LAN865X_REG_MAC_H_HASH 0x00010021
+#define LAN865X_REG_MAC_H_HASH 0x21
/* MAC Specific Addr 1 Bottom Reg */
-#define LAN865X_REG_MAC_L_SADDR1 0x00010022
+#define LAN865X_REG_MAC_L_SADDR1 0x22
/* MAC Specific Addr 1 Top Reg */
-#define LAN865X_REG_MAC_H_SADDR1 0x00010023
+#define LAN865X_REG_MAC_H_SADDR1 0x23
/* MAC TSU Timer Increment Register */
-#define LAN865X_REG_MAC_TSU_TIMER_INCR 0x00010077
+#define LAN865X_REG_MAC_TSU_TIMER_INCR 0x77
#define MAC_TSU_TIMER_INCR_COUNT_NANOSECONDS 0x0028
struct lan865x_priv {
@@ -49,7 +49,8 @@ static int lan865x_set_hw_macaddr_low_bytes(struct oa_tc6 *tc6, const u8 *mac)
regval = (mac[3] << 24) | (mac[2] << 16) | (mac[1] << 8) | mac[0];
- return oa_tc6_write_register(tc6, LAN865X_REG_MAC_L_SADDR1, regval);
+ return oa_tc6_write_register_mms(tc6, LAN865X_REG_MAC_L_SADDR1,
+ OA_TC6_PHY_C45_MAC_MMS1, regval);
}
static int lan865x_set_hw_macaddr(struct lan865x_priv *priv, const u8 *mac)
@@ -65,8 +66,8 @@ static int lan865x_set_hw_macaddr(struct lan865x_priv *priv, const u8 *mac)
/* Prepare and configure MAC address high bytes */
regval = (mac[5] << 8) | mac[4];
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_SADDR1,
- regval);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_H_SADDR1,
+ OA_TC6_PHY_C45_MAC_MMS1, regval);
if (!ret)
return 0;
@@ -146,14 +147,16 @@ static int lan865x_set_specific_multicast_addr(struct lan865x_priv *priv)
}
/* Enabling specific multicast addresses */
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_HASH, hash_hi);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_H_HASH,
+ OA_TC6_PHY_C45_MAC_MMS1, hash_hi);
if (ret) {
netdev_err(priv->netdev, "Failed to write reg_hashh: %d\n",
ret);
return ret;
}
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_L_HASH, hash_lo);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_L_HASH,
+ OA_TC6_PHY_C45_MAC_MMS1, hash_lo);
if (ret)
netdev_err(priv->netdev, "Failed to write reg_hashl: %d\n",
ret);
@@ -166,16 +169,16 @@ static int lan865x_set_all_multicast_addr(struct lan865x_priv *priv)
int ret;
/* Enabling all multicast addresses */
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_HASH,
- 0xffffffff);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_H_HASH,
+ OA_TC6_PHY_C45_MAC_MMS1, 0xffffffff);
if (ret) {
netdev_err(priv->netdev, "Failed to write reg_hashh: %d\n",
ret);
return ret;
}
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_L_HASH,
- 0xffffffff);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_L_HASH,
+ OA_TC6_PHY_C45_MAC_MMS1, 0xffffffff);
if (ret)
netdev_err(priv->netdev, "Failed to write reg_hashl: %d\n",
ret);
@@ -187,14 +190,16 @@ static int lan865x_clear_all_multicast_addr(struct lan865x_priv *priv)
{
int ret;
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_H_HASH, 0);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_H_HASH,
+ OA_TC6_PHY_C45_MAC_MMS1, 0);
if (ret) {
netdev_err(priv->netdev, "Failed to write reg_hashh: %d\n",
ret);
return ret;
}
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_L_HASH, 0);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_L_HASH,
+ OA_TC6_PHY_C45_MAC_MMS1, 0);
if (ret)
netdev_err(priv->netdev, "Failed to write reg_hashl: %d\n",
ret);
@@ -235,7 +240,8 @@ static void lan865x_multicast_work_handler(struct work_struct *work)
if (lan865x_clear_all_multicast_addr(priv))
return;
}
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_NET_CFG, regval);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CFG,
+ OA_TC6_PHY_C45_MAC_MMS1, regval);
if (ret)
netdev_err(priv->netdev, "Failed to enable promiscuous/multicast/normal mode: %d\n",
ret);
@@ -260,12 +266,14 @@ static int lan865x_hw_disable(struct lan865x_priv *priv)
{
u32 regval;
- if (oa_tc6_read_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, ®val))
+ if (oa_tc6_read_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CTL,
+ OA_TC6_PHY_C45_MAC_MMS1, ®val))
return -ENODEV;
regval &= ~(MAC_NET_CTL_TXEN | MAC_NET_CTL_RXEN);
- if (oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, regval))
+ if (oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CTL,
+ OA_TC6_PHY_C45_MAC_MMS1, regval))
return -ENODEV;
return 0;
@@ -291,12 +299,14 @@ static int lan865x_hw_enable(struct lan865x_priv *priv)
{
u32 regval;
- if (oa_tc6_read_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, ®val))
+ if (oa_tc6_read_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CTL,
+ OA_TC6_PHY_C45_MAC_MMS1, ®val))
return -ENODEV;
regval |= MAC_NET_CTL_TXEN | MAC_NET_CTL_RXEN;
- if (oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_NET_CTL, regval))
+ if (oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_NET_CTL,
+ OA_TC6_PHY_C45_MAC_MMS1, regval))
return -ENODEV;
return 0;
@@ -359,8 +369,9 @@ static int lan865x_probe(struct spi_device *spi)
* stamping at the end of the Start of Frame Delimiter (SFD) and set the
* Timer Increment reg to 40 ns to be used as a 25 MHz internal clock.
*/
- ret = oa_tc6_write_register(priv->tc6, LAN865X_REG_MAC_TSU_TIMER_INCR,
- MAC_TSU_TIMER_INCR_COUNT_NANOSECONDS);
+ ret = oa_tc6_write_register_mms(priv->tc6, LAN865X_REG_MAC_TSU_TIMER_INCR,
+ OA_TC6_PHY_C45_MAC_MMS1,
+ MAC_TSU_TIMER_INCR_COUNT_NANOSECONDS);
if (ret) {
dev_err(&spi->dev, "Failed to config TSU Timer Incr reg: %d\n",
ret);
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
index 26033373f16f..a7d8c9bb1f28 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c
@@ -377,6 +377,83 @@ int oa_tc6_read_register(struct oa_tc6 *tc6, u32 address, u32 *value)
}
EXPORT_SYMBOL_GPL(oa_tc6_read_register);
+/**
+ * oa_tc6_read_registers_mms - function for reading multiple consecutive
+ * registers for the given address, memory map selector pair.
+ * @tc6: oa_tc6 struct.
+ * @address: address of the first register to be read in the MAC-PHY.
+ * @mms: Memory map selector for the registers to be read.
+ * @value: values to be read from the starting register address @address.
+ * @length: number of consecutive registers to be read from @address.
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_read_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 value[], u8 length)
+{
+ u32 mms_addr = (u32)mms << 16 | (u32)address;
+
+ return oa_tc6_read_registers(tc6, mms_addr, value, length);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_read_registers_mms);
+
+/**
+ * oa_tc6_read_register_mms - function for reading a MAC-PHY register
+ * for the given address, memory map selector pair.
+ * @tc6: oa_tc6 struct.
+ * @address: register address of the MAC-PHY to be read.
+ * @mms: Memory Map Selector for the given address
+ * @value: value read from the @address register address of the MAC-PHY.
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 *value)
+{
+ return oa_tc6_read_registers_mms(tc6, address, mms, value, 1);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_read_register_mms);
+
+/**
+ * oa_tc6_write_registers_mms - function for writing multiple consecutive
+ * registers for the given address, memory map selector pair.
+ * @tc6: oa_tc6 struct.
+ * @address: address of the first register to be written in the MAC-PHY.
+ * @mms: memory map Selector for the given register.
+ * @value: values to be written from the starting register address @address.
+ * @length: number of consecutive registers to be written from @address.
+ *
+ * Maximum of 128 consecutive registers can be written starting at @address.
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_write_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 value[], u8 length)
+{
+ u32 mms_addr = (u32)mms << 16 | (u32)address;
+
+ return oa_tc6_write_registers(tc6, mms_addr, value, length);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_write_registers_mms);
+
+/**
+ * oa_tc6_write_register_mms - function for writing a MAC-PHY register
+ * associated with the given memory map selector.
+ * @tc6: oa_tc6 struct.
+ * @address: register address of the MAC-PHY to be written.
+ * @mms: memory map selector for the given register.
+ * @value: value to be written in the @address register address of
+ * the MAC-PHY.
+ *
+ * Return: 0 on success otherwise failed.
+ */
+int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 value)
+{
+ return oa_tc6_write_registers_mms(tc6, address, mms, &value, 1);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_write_register_mms);
+
/**
* oa_tc6_write_registers - function for writing multiple consecutive registers.
* @tc6: oa_tc6 struct.
@@ -490,14 +567,14 @@ static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum,
int regnum)
{
struct oa_tc6 *tc6 = bus->priv;
+ int mms, ret;
u32 regval;
- int ret;
- ret = oa_tc6_get_phy_c45_mms(tc6, devnum);
- if (ret < 0)
- return ret;
+ mms = oa_tc6_get_phy_c45_mms(tc6, devnum);
+ if (mms < 0)
+ return mms;
- ret = oa_tc6_read_register(tc6, (ret << 16) | regnum, ®val);
+ ret = oa_tc6_read_register_mms(tc6, (u16)regnum, (u16)mms, ®val);
if (ret)
return ret;
@@ -508,13 +585,13 @@ static int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
int regnum, u16 val)
{
struct oa_tc6 *tc6 = bus->priv;
- int ret;
+ int mms;
- ret = oa_tc6_get_phy_c45_mms(tc6, devnum);
- if (ret < 0)
- return ret;
+ mms = oa_tc6_get_phy_c45_mms(tc6, devnum);
+ if (mms < 0)
+ return mms;
- return oa_tc6_write_register(tc6, (ret << 16) | regnum, val);
+ return oa_tc6_write_register_mms(tc6, (u16)regnum, (u16)mms, val);
}
static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index a89151267713..3d50971f0f5b 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -37,6 +37,14 @@ int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
u8 length);
netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb);
int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6);
+int oa_tc6_write_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 value[], u8 length);
+int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 value);
+int oa_tc6_read_registers_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 value[], u8 length);
+int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u16 address, u16 mms,
+ u32 *value);
int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info);
int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd);
int oa_tc6_get_ts_info(struct oa_tc6 *tc6,
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v4 11/16] net: phy: ncn26000: Enable enhanced noise immunity
From: Selvamani Rajagopal via B4 Relay @ 2026-06-06 5:42 UTC (permalink / raw)
To: Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn, Parthiban Veerasooran, Selva Rajagopal,
Richard Cochran, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan
Cc: netdev, linux-kernel, devicetree, linux-doc, Jerry Ray,
Selvamani Rajagopal
In-Reply-To: <20260605-s2500-mac-phy-support-v4-0-de0fbc13c6d8@onsemi.com>
From: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
By setting ENI bit, noise immunity is improved and it is
specifically meant for PLCA enabled nodes.
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
drivers/net/phy/ncn26000.c | 26 ++++++++++++++++++++++++--
1 file changed, 24 insertions(+), 2 deletions(-)
diff --git a/drivers/net/phy/ncn26000.c b/drivers/net/phy/ncn26000.c
index 2c8601c3f94a..c3a34b2c524d 100644
--- a/drivers/net/phy/ncn26000.c
+++ b/drivers/net/phy/ncn26000.c
@@ -36,6 +36,10 @@
#define TO_TMR_DEFAULT 32
+#define NCN26000_REG_PHYCFG1 0x8001
+#define NCN26000_PHYCFG1_ENI BIT(7)
+#define NCN26000_PHYCFG1_ENI_MASK BIT(7)
+
static int ncn26000_config_init(struct phy_device *phydev)
{
int ret = 0;
@@ -106,6 +110,24 @@ static int ncn26000_read_status(struct phy_device *phydev)
return 0;
}
+/* Intercept PLCA enable/disable request to
+ * set the proprietary, ENI mode accordingly
+ */
+static int ncn26000_c45_plca_set_cfg(struct phy_device *phydev,
+ const struct phy_plca_cfg *plca_cfg)
+{
+ int ret = genphy_c45_plca_set_cfg(phydev, plca_cfg);
+ u16 eni_cfg = 0;
+
+ if (ret || plca_cfg->enabled < 0)
+ return ret;
+
+ eni_cfg = (plca_cfg->enabled) ? NCN26000_PHYCFG1_ENI : 0;
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
+ NCN26000_REG_PHYCFG1,
+ NCN26000_PHYCFG1_ENI_MASK, eni_cfg);
+}
+
static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev)
{
int ret;
@@ -156,7 +178,7 @@ static struct phy_driver ncn26000_driver[] = {
.config_aneg = ncn26000_config_aneg,
.read_status = ncn26000_read_status,
.handle_interrupt = ncn26000_handle_interrupt,
- .set_plca_cfg = genphy_c45_plca_set_cfg,
+ .set_plca_cfg = ncn26000_c45_plca_set_cfg,
.get_plca_cfg = genphy_c45_plca_get_cfg,
.get_plca_status = genphy_c45_plca_get_status,
.soft_reset = genphy_soft_reset,
@@ -177,7 +199,7 @@ static struct phy_driver ncn26000_driver[] = {
.read_status = ncn26000_read_status,
.handle_interrupt = ncn26000_handle_interrupt,
.get_plca_cfg = genphy_c45_plca_get_cfg,
- .set_plca_cfg = genphy_c45_plca_set_cfg,
+ .set_plca_cfg = ncn26000_c45_plca_set_cfg,
.get_plca_status = genphy_c45_plca_get_status,
.soft_reset = genphy_soft_reset,
},
--
2.43.0
^ permalink raw reply related
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