* [PATCH v4 03/10] crypto: pkcs7: add tests for pkcs7_get_authattr
From: Blaise Boscaccy @ 2026-04-16 17:33 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260416173500.176716-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Add example code to the test module pkcs7_key_type.c that verifies a
message and then pulls out a known authenticated attribute.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
Acked-by: David Howells <dhowells@redhat.com>
---
crypto/asymmetric_keys/pkcs7_key_type.c | 44 ++++++++++++++++++++++++-
1 file changed, 43 insertions(+), 1 deletion(-)
diff --git a/crypto/asymmetric_keys/pkcs7_key_type.c b/crypto/asymmetric_keys/pkcs7_key_type.c
index b930d3bbf1af5..e0b1ce0202f6d 100644
--- a/crypto/asymmetric_keys/pkcs7_key_type.c
+++ b/crypto/asymmetric_keys/pkcs7_key_type.c
@@ -12,6 +12,7 @@
#include <linux/verification.h>
#include <linux/key-type.h>
#include <keys/user-type.h>
+#include <crypto/pkcs7.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("PKCS#7 testing key type");
@@ -51,16 +52,57 @@ static int pkcs7_view_content(void *ctx, const void *data, size_t len,
static int pkcs7_preparse(struct key_preparsed_payload *prep)
{
enum key_being_used_for usage = pkcs7_usage;
+ int ret;
+ struct pkcs7_message *pkcs7;
+ const void *data;
+ size_t len;
if (usage >= NR__KEY_BEING_USED_FOR) {
pr_err("Invalid usage type %d\n", usage);
return -EINVAL;
}
- return verify_pkcs7_signature(NULL, 0,
+ ret = verify_pkcs7_signature(NULL, 0,
prep->data, prep->datalen,
VERIFY_USE_SECONDARY_KEYRING, usage,
pkcs7_view_content, prep);
+ if (ret)
+ return ret;
+
+ pkcs7 = pkcs7_parse_message(prep->data, prep->datalen);
+ if (IS_ERR(pkcs7)) {
+ pr_err("pkcs7 parse error\n");
+ return PTR_ERR(pkcs7);
+ }
+
+ /*
+ * the parsed message has no trusted signer, so nothing should
+ * be returned here
+ */
+ ret = pkcs7_get_authattr(pkcs7, OID_messageDigest, &data, &len);
+ if (ret == 0) {
+ pr_err("OID returned when no trust in signer\n");
+ goto out;
+ }
+ /* add trust and check again */
+ ret = verify_pkcs7_message_sig(NULL, 0, pkcs7,
+ VERIFY_USE_SECONDARY_KEYRING, usage,
+ NULL, NULL);
+ if (ret) {
+ pr_err("verify_pkcs7_message_sig failed!!\n");
+ goto out;
+ }
+ /* now we should find the OID */
+ ret = pkcs7_get_authattr(pkcs7, OID_messageDigest, &data, &len);
+ if (ret) {
+ pr_err("Failed to get message digest\n");
+ goto out;
+ }
+ pr_info("Correctly Got message hash, size=%zu\n", len);
+
+ out:
+ pkcs7_free_message(pkcs7);
+ return 0;
}
/*
--
2.53.0
^ permalink raw reply related
* [PATCH v4 02/10] crypto: pkcs7: add ability to extract signed attributes by OID
From: Blaise Boscaccy @ 2026-04-16 17:33 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260416173500.176716-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Signers may add any information they like in signed attributes and
sometimes this information turns out to be relevant to specific
signing cases, so add an api pkcs7_get_authattr() to extract the value
of an authenticated attribute by specific OID. The current
implementation is designed for the single signer use case and simply
terminates the search when it finds the relevant OID.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
crypto/asymmetric_keys/Makefile | 4 +-
crypto/asymmetric_keys/pkcs7_aa.asn1 | 18 ++++++
crypto/asymmetric_keys/pkcs7_parser.c | 81 +++++++++++++++++++++++++++
include/crypto/pkcs7.h | 4 ++
4 files changed, 106 insertions(+), 1 deletion(-)
create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1
diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile
index bc65d3b98dcbf..f99b7169ae7cd 100644
--- a/crypto/asymmetric_keys/Makefile
+++ b/crypto/asymmetric_keys/Makefile
@@ -53,12 +53,14 @@ clean-files += pkcs8.asn1.c pkcs8.asn1.h
obj-$(CONFIG_PKCS7_MESSAGE_PARSER) += pkcs7_message.o
pkcs7_message-y := \
pkcs7.asn1.o \
+ pkcs7_aa.asn1.o \
pkcs7_parser.o \
pkcs7_trust.o \
pkcs7_verify.o
-$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h
+$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h $(obj)/pkcs7_aa.asn1.h
$(obj)/pkcs7.asn1.o: $(obj)/pkcs7.asn1.c $(obj)/pkcs7.asn1.h
+$(obj)/pkcs7_aa.asn1.o: $(obj)/pkcs7_aa.asn1.c $(obj)/pkcs7_aa.asn1.h
#
# PKCS#7 parser testing key
diff --git a/crypto/asymmetric_keys/pkcs7_aa.asn1 b/crypto/asymmetric_keys/pkcs7_aa.asn1
new file mode 100644
index 0000000000000..7a8857bdf56e1
--- /dev/null
+++ b/crypto/asymmetric_keys/pkcs7_aa.asn1
@@ -0,0 +1,18 @@
+-- SPDX-License-Identifier: BSD-3-Clause
+--
+-- Copyright (C) 2009 IETF Trust and the persons identified as authors
+-- of the code
+--
+-- https://www.rfc-editor.org/rfc/rfc5652#section-3
+
+AA ::= CHOICE {
+ aaSet [0] IMPLICIT AASet,
+ aaSequence [2] EXPLICIT SEQUENCE OF AuthenticatedAttribute
+}
+
+AASet ::= SET OF AuthenticatedAttribute
+
+AuthenticatedAttribute ::= SEQUENCE {
+ type OBJECT IDENTIFIER ({ pkcs7_aa_note_OID }),
+ values SET OF ANY ({ pkcs7_aa_note_attr })
+}
diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c
index 6e3ffdac83ace..d467866f7d930 100644
--- a/crypto/asymmetric_keys/pkcs7_parser.c
+++ b/crypto/asymmetric_keys/pkcs7_parser.c
@@ -15,6 +15,7 @@
#include <crypto/public_key.h>
#include "pkcs7_parser.h"
#include "pkcs7.asn1.h"
+#include "pkcs7_aa.asn1.h"
MODULE_DESCRIPTION("PKCS#7 parser");
MODULE_AUTHOR("Red Hat, Inc.");
@@ -211,6 +212,86 @@ int pkcs7_get_content_data(const struct pkcs7_message *pkcs7,
}
EXPORT_SYMBOL_GPL(pkcs7_get_content_data);
+struct pkcs7_aa_context {
+ bool found;
+ enum OID oid_to_find;
+ const void *data;
+ size_t len;
+};
+
+int pkcs7_aa_note_OID(void *context, size_t hdrlen,
+ unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct pkcs7_aa_context *ctx = context;
+ enum OID oid = look_up_OID(value, vlen);
+
+ ctx->found = (oid == ctx->oid_to_find);
+
+ return 0;
+}
+
+int pkcs7_aa_note_attr(void *context, size_t hdrlen,
+ unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct pkcs7_aa_context *ctx = context;
+
+ if (ctx->found) {
+ ctx->data = value;
+ ctx->len = vlen;
+ }
+
+ return 0;
+}
+
+/**
+ * pkcs7_get_authattr - get authenticated attribute by OID
+ * @pkcs7: The preparsed PKCS#7 message
+ * @oid: the enum value of the OID to find
+ * @_data: Place to return a pointer to the attribute value
+ * @_len: length of the attribute value
+ *
+ * Searches the authenticated attributes until one is found with a
+ * matching OID. Note that because the attributes are per signer
+ * there could be multiple signers with different values, but this
+ * routine will simply return the first one in parse order.
+ *
+ * Returns -ENODATA if the attribute can't be found
+ */
+int pkcs7_get_authattr(const struct pkcs7_message *pkcs7,
+ enum OID oid,
+ const void **_data, size_t *_len)
+{
+ struct pkcs7_signed_info *sinfo = pkcs7->signed_infos;
+ struct pkcs7_aa_context ctx;
+
+ ctx.data = NULL;
+ ctx.oid_to_find = oid;
+
+ for (; sinfo; sinfo = sinfo->next) {
+ int ret;
+
+ /* only extract OIDs from validated signers */
+ if (!sinfo->verified)
+ continue;
+
+ ret = asn1_ber_decoder(&pkcs7_aa_decoder, &ctx,
+ sinfo->authattrs, sinfo->authattrs_len);
+ if (ret < 0 || ctx.data != NULL)
+ break;
+ }
+
+ if (!ctx.data)
+ return -ENODATA;
+
+ *_data = ctx.data;
+ *_len = ctx.len;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pkcs7_get_authattr);
+
/*
* Note an OID when we find one for later processing when we know how
* to interpret it.
diff --git a/include/crypto/pkcs7.h b/include/crypto/pkcs7.h
index 38ec7f5f90411..bd83202cd805c 100644
--- a/include/crypto/pkcs7.h
+++ b/include/crypto/pkcs7.h
@@ -25,6 +25,10 @@ extern void pkcs7_free_message(struct pkcs7_message *pkcs7);
extern int pkcs7_get_content_data(const struct pkcs7_message *pkcs7,
const void **_data, size_t *_datalen,
size_t *_headerlen);
+extern int pkcs7_get_authattr(const struct pkcs7_message *pkcs7,
+ enum OID oid,
+ const void **_data, size_t *_len);
+
/*
* pkcs7_trust.c
--
2.53.0
^ permalink raw reply related
* [PATCH v4 01/10] crypto: pkcs7: add flag for validated trust on a signed info block
From: Blaise Boscaccy @ 2026-04-16 17:33 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
In-Reply-To: <20260416173500.176716-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Allow consumers of struct pkcs7_message to tell if any of the sinfo
fields has passed a trust validation. Note that this does not happen
in parsing, pkcs7_validate_trust() must be explicitly called or called
via validate_pkcs7_trust(). Since the way to get this trusted pkcs7
object is via verify_pkcs7_message_sig, export that so modules can use
it.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
certs/system_keyring.c | 1 +
crypto/asymmetric_keys/pkcs7_parser.h | 1 +
crypto/asymmetric_keys/pkcs7_trust.c | 1 +
3 files changed, 3 insertions(+)
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index e0761436ec7f4..9bda49295bd02 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -380,6 +380,7 @@ int verify_pkcs7_message_sig(const void *data, size_t len,
pr_devel("<==%s() = %d\n", __func__, ret);
return ret;
}
+EXPORT_SYMBOL(verify_pkcs7_message_sig);
/**
* verify_pkcs7_signature - Verify a PKCS#7-based signature on system data.
diff --git a/crypto/asymmetric_keys/pkcs7_parser.h b/crypto/asymmetric_keys/pkcs7_parser.h
index 6ef9f335bb17f..203062a33def6 100644
--- a/crypto/asymmetric_keys/pkcs7_parser.h
+++ b/crypto/asymmetric_keys/pkcs7_parser.h
@@ -20,6 +20,7 @@ struct pkcs7_signed_info {
unsigned index;
bool unsupported_crypto; /* T if not usable due to missing crypto */
bool blacklisted;
+ bool verified; /* T if this signer has validated trust */
/* Message digest - the digest of the Content Data (or NULL) */
const void *msgdigest;
diff --git a/crypto/asymmetric_keys/pkcs7_trust.c b/crypto/asymmetric_keys/pkcs7_trust.c
index 9a87c34ed1733..78ebfb6373b61 100644
--- a/crypto/asymmetric_keys/pkcs7_trust.c
+++ b/crypto/asymmetric_keys/pkcs7_trust.c
@@ -127,6 +127,7 @@ static int pkcs7_validate_trust_one(struct pkcs7_message *pkcs7,
for (p = sinfo->signer; p != x509; p = p->signer)
p->verified = true;
}
+ sinfo->verified = true;
kleave(" = 0");
return 0;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v4 00/10] Reintroduce Hornet LSM
From: Blaise Boscaccy @ 2026-04-16 17:33 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
Fan Wu, Ryan Foster, Randy Dunlap, linux-security-module,
linux-doc, linux-kernel, bpf, Song Liu
This patch series introduces the next iteration of the Hornet LSM.
Hornet’s goal is to provide a secure and extensible in-kernel
signature verification mechanism for eBPF programs.
Hornet addresses concerns from users who require strict audit trails and
verification guarantees for eBPF programs, especially in
security-sensitive environments. Many production systems need assurance
that only authorized, unmodified eBPF programs are loaded into the
kernel. Hornet provides this assurance through cryptographic signature
verification.
The currently accepted loader-plus-map signature verification scheme,
mandated by Alexei and KP, is simple to implement and generally
acceptable if users and administrators are satisfied with it. However,
verifying both the loader and the maps offers additional benefits
beyond verifying the loader alone:
1. Security and Audit Integrity
A key advantage is that the LSM hook for authorizing BPF program loads
can operate after signature verification. This ensures:
* Access control decisions are based on verified signature status.
* Accurate system state measurement and logging.
* Log entries claiming a verified signature are truthful, avoiding
misleading records where only the loader was verified while the actual
BPF program verification occurs later without logging.
2. TOCTOU Attack Prevention
The current map hash implementation may be vulnerable to a TOCTOU
attack because it allows unfrozen maps to cache a previously
calculated hash. The accepted “trusted loader” scheme cannot detect
this and may permit loading altered maps.
3. Supply Chain Integrity
Verify that eBPF programs and their associated map data have not been
modified since they were built and signed, in the kernel proper, may
aid in protecting against supply chain attacks.
This approach addresses concerns from users who require strict audit
trails and verification guarantees, especially in security-sensitive
environments. Map hashes for extended verification are passed via the
existing PKCS#7 UAPI and verified by the crypto subsystem. Hornet then
calculates the program’s verification state. Hornet itself does not
enforce a policy on whether unsigned or partially signed programs
should be rejected. It delegates that decision to downstream LSMs
hook, making it a composable building block in a larger security
architecture.
Changes in V4:
- IPE integration
- Arbitrary keyring support
Link to V3: https://lore.kernel.org/linux-security-module/20260326060655.2550595-1-bboscaccy@linux.microsoft.com/
Changes in V3:
- Updated for signed attribute patch series changes
- Added some new result enum values
- Minor documentation clarification
- Misc style fixes
- Added missing signed-off-by tags
Link to V2: https://lore.kernel.org/linux-security-module/20260227233930.2418522-1-bboscaccy@linux.microsoft.com/
Changes in V2:
- Addressed possible TocTou races in hash verification
- Improved documentation and tooling
- Added Alexie's nack
Link to RFC: https://lore.kernel.org/linux-security-module/20251211021257.1208712-1-bboscaccy@linux.microsoft.com/
Blaise Boscaccy (6):
lsm: security: Add additional enum values for bpf integrity checks
security: Hornet LSM
hornet: Introduce gen_sig
hornet: Add a light skeleton data extractor scripts
selftests/hornet: Add a selftest for the Hornet LSM
ipe: Add BPF program load policy enforcement via Hornet integration
James Bottomley (3):
crypto: pkcs7: add flag for validated trust on a signed info block
crypto: pkcs7: add ability to extract signed attributes by OID
crypto: pkcs7: add tests for pkcs7_get_authattr
Paul Moore (1):
lsm: framework for BPF integrity verification
Documentation/admin-guide/LSM/Hornet.rst | 321 +++++++++++++++
Documentation/admin-guide/LSM/index.rst | 1 +
MAINTAINERS | 9 +
certs/system_keyring.c | 1 +
crypto/asymmetric_keys/Makefile | 4 +-
crypto/asymmetric_keys/pkcs7_aa.asn1 | 18 +
crypto/asymmetric_keys/pkcs7_key_type.c | 44 ++-
crypto/asymmetric_keys/pkcs7_parser.c | 81 ++++
crypto/asymmetric_keys/pkcs7_parser.h | 1 +
crypto/asymmetric_keys/pkcs7_trust.c | 1 +
include/crypto/pkcs7.h | 4 +
include/linux/lsm_hook_defs.h | 5 +
include/linux/oid_registry.h | 3 +
include/linux/security.h | 28 ++
include/uapi/linux/lsm.h | 1 +
scripts/Makefile | 1 +
scripts/hornet/Makefile | 5 +
scripts/hornet/extract-insn.sh | 27 ++
scripts/hornet/extract-map.sh | 27 ++
scripts/hornet/extract-skel.sh | 27 ++
scripts/hornet/gen_sig.c | 392 +++++++++++++++++++
scripts/hornet/write-sig.sh | 27 ++
security/Kconfig | 3 +-
security/Makefile | 1 +
security/hornet/Kconfig | 11 +
security/hornet/Makefile | 7 +
security/hornet/hornet.asn1 | 13 +
security/hornet/hornet_lsm.c | 346 ++++++++++++++++
security/ipe/Kconfig | 14 +
security/ipe/audit.c | 15 +
security/ipe/eval.c | 73 +++-
security/ipe/eval.h | 5 +
security/ipe/hooks.c | 37 ++
security/ipe/hooks.h | 11 +
security/ipe/ipe.c | 3 +
security/ipe/policy.h | 14 +
security/ipe/policy_parser.c | 27 ++
security/security.c | 75 +++-
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/hornet/Makefile | 63 +++
tools/testing/selftests/hornet/loader.c | 21 +
tools/testing/selftests/hornet/trivial.bpf.c | 33 ++
42 files changed, 1794 insertions(+), 7 deletions(-)
create mode 100644 Documentation/admin-guide/LSM/Hornet.rst
create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1
create mode 100644 scripts/hornet/Makefile
create mode 100755 scripts/hornet/extract-insn.sh
create mode 100755 scripts/hornet/extract-map.sh
create mode 100755 scripts/hornet/extract-skel.sh
create mode 100644 scripts/hornet/gen_sig.c
create mode 100755 scripts/hornet/write-sig.sh
create mode 100644 security/hornet/Kconfig
create mode 100644 security/hornet/Makefile
create mode 100644 security/hornet/hornet.asn1
create mode 100644 security/hornet/hornet_lsm.c
create mode 100644 tools/testing/selftests/hornet/Makefile
create mode 100644 tools/testing/selftests/hornet/loader.c
create mode 100644 tools/testing/selftests/hornet/trivial.bpf.c
--
2.53.0
^ permalink raw reply
* Re: [PATCH v4 0/3] mm/memory-failure: add panic option for unrecoverable pages
From: Jiaqi Yan @ 2026-04-16 16:26 UTC (permalink / raw)
To: Breno Leitao
Cc: Miaohe Lin, Naoya Horiguchi, Andrew Morton, Jonathan Corbet,
Shuah Khan, David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
linux-mm, linux-kernel, linux-doc, kernel-team
In-Reply-To: <aeD6hpM3t0RZm5mW@gmail.com>
On Thu, Apr 16, 2026 at 8:32 AM Breno Leitao <leitao@debian.org> wrote:
>
> Hi Jiaqi,
>
> On Wed, Apr 15, 2026 at 01:56:35PM -0700, Jiaqi Yan wrote:
> > On Wed, Apr 15, 2026 at 5:55 AM Breno Leitao <leitao@debian.org> wrote:
> > >
> > > When the memory failure handler encounters an in-use kernel page that it
> > > cannot recover (slab, page tables, kernel stacks, vmalloc, etc.), it
> > > currently logs the error as "Ignored" and continues operation.
> > >
> > > This leaves corrupted data accessible to the kernel, which will inevitably
> > > cause either silent data corruption or a delayed crash when the poisoned memory
> > > is next accessed.
> > >
> > > This is a common problem on large fleets. We frequently observe multi-bit ECC
> > > errors hitting kernel slab pages, where memory_failure() fails to recover them
> > > and the system crashes later at an unrelated code path, making root cause
> > > analysis unnecessarily difficult.
> > >
> > > Here is one specific example from production on an arm64 server: a multi-bit
> > > ECC error hit a dentry cache slab page, memory_failure() failed to recover it
> > > (slab pages are not supported by the hwpoison recovery mechanism), and 67
> > > seconds later d_lookup() accessed the poisoned cache line causing
> > > a synchronous external abort:
> > >
> > > [88690.479680] [Hardware Error]: error_type: 3, multi-bit ECC
> > > [88690.498473] Memory failure: 0x40272d: unhandlable page.
> > > [88690.498619] Memory failure: 0x40272d: recovery action for
> > > get hwpoison page: Ignored
> > > ...
> > > [88757.847126] Internal error: synchronous external abort:
> > > 0000000096000410 [#1] SMP
> > > [88758.061075] pc : d_lookup+0x5c/0x220
> > >
> > > This series adds a new sysctl vm.panic_on_unrecoverable_memory_failure
> > > (default 0) that, when enabled, panics immediately on unrecoverable
> > > memory failures. This provides a clean crash dump at the time of the
> >
> > I get the fail-fast part, but wonder will kernel really be able to
> > provide clean crash dump useful for diagnosis?
>
> Yes, the kernel does provide a useful crash dump. With the sysctl enabled,
> here's what I observe:
>
> Kernel panic - not syncing: Memory failure: 0x1: unrecoverable page
> CPU: 40 UID: 0 PID: 682 Comm: bash Tainted: G B 7.0.0-next-20260414-upstream-00004-gcbb3af7bfd3b #93
> Tainted: [B]=BAD_PAGE
>
> Call Trace:
> <TASK>
> vpanic+0x399/0x700
> panic+0xb4/0xc0
> action_result+0x278/0x340 ← your new panic call site
> memory_failure+0x152b/0x1c80
>
>
> Without the patch (or with the sysctl disabled), you only get:
>
> Memory failure: 0x1: unhandlable page.
> Memory failure: 0x1: recovery action for reserved kernel page: Ignored
>
> Then the host continues running until it eventually accesses that poisoned
> memory, triggering a generic error similar to the d_lookup() case mentioned
> above.
>
> > In your example at 88757.847126, kernel was handling SEA and because
> > we are under kernel context, eventually has to die(). Apparently not
> > only your patch, but also memory-failure has no role to play there.
> > But at least SEA handling tried its best to show the kernel code that
> > consumed the memory error.
> >
> > So your code should apply to the memory failure handling at
> > 88690.498473, which is likely triggered from APEI GHES for poison
> > detection (I guess the example is from ARM64). Anything except SEA is
> > considered not synchronous (by APEI is_hest_sync_notify()). If kernel
> > panics there, I guess it will be in a random process context or a
> > kworker thread? How useful is it for diagnosis? Just the exact time an
> > error detected (which is already logged by kernel)?
>
> The kernel panics with a clear stack trace and explicit reason, making it
> straightforward to correlate and analyze the failure.
So we will always get the same stack trace below, right?
panic+0xb4/0xc0
action_result+0x278/0x340
memory_failure+0x152b/0x1c80
IIUC, this stack trace itself doesn't provide any useful information
about the memory error, right? What exactly can we use from the stack
trace? It is just a side-effect that we failed immediately.
You can still correlate failure with "Memory failure: 0x1: unhandlable
page" and keep running until the actual fatal poison consumption takes
down the system. Drawback is that these will be cascading events that
can be "noisy". What I see is the choice between failing fast versus
failing safe.
>
> My objective is to have a clean, immediate crash rather than allowing the
> system to continue running and potentially crash later (if at all).
>
> Working at a hyperscaler, I regularly see thousands of these "unhandlable
> page" messages, followed by later kernel crashes when the corrupted memory
> is eventually accessed.
>
> > On X86, for UCNA or SRAO type machine check exceptions, I think with
> > your patch the panic would also happen in random process context or
> > kworker thread,
> >
> > Can you share some clean crash dumps from your testing that show they
> > are more useful than the crash at SEA? Thanks!
>
> Certainly, here is the complete crash dump from the example above. This
> happened on a real production hardware:
>
> [88690.478913] [ T593001] {1}[Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 784
> [88690.479097] [ T593001] {1}[Hardware Error]: event severity: recoverable
> [88690.479184] [ T593001] {1}[Hardware Error]: imprecise tstamp: 2026-03-20 13:13:08
> [88690.479282] [ T593001] {1}[Hardware Error]: Error 0, type: recoverable
> [88690.479359] [ T593001] {1}[Hardware Error]: section_type: memory error
> [88690.479424] [ T593001] {1}[Hardware Error]: physical_address: 0x00000040272d5080
> [88690.479503] [ T593001] {1}[Hardware Error]: physical_address_mask: 0xfffffffffffff000
> [88690.479606] [ T593001] {1}[Hardware Error]: node:0 card:0 module:1 rank:1 bank:13 device:6 row:64114 column:832 requestor_id:0x0000000000000027
> [88690.479680] [ T593001] {1}[Hardware Error]: error_type: 3, multi-bit ECC
> [88690.479754] [ T593001] {1}[Hardware Error]: DIMM location: not present. DMI handle: 0x000e
> [88690.479882] [ T593001] EDAC MC0: 1 UE multi-bit ECC on unknown memory (node:0 card:0 module:1 rank:1 bank:13 device:6 row:64114 column:832 requestor_id:0x0000000000000027 DIMM location: not present. DMI handle: 0x000e page:0x40272d offset:0x5080 grain:4096 - APEI location: node:0 card:0 module:1 rank:1 bank:13 device:6 row:64114 column:832 requestor_id:0x0000000000000027 DIMM location: not present. DMI handle: 0x000e)
> [88690.498473] [ T593001] Memory failure: 0x40272d: unhandlable page.
> [88690.498619] [ T593001] Memory failure: 0x40272d: recovery action for get hwpoison page: Ignored
> [88757.847126] [ T640437] Internal error: synchronous external abort: 0000000096000410 [#1] SMP
> [88757.867131] [ T640437] Modules linked in: ghes_edac(E) act_gact(E) sch_fq(E) tcp_diag(E) inet_diag(E) cls_bpf(E) mlx5_ib(E) sm3_ce(E) sha3_ce(E) sha512_ce(E) ipmi_ssif(E) ipmi_devintf(E) nvidia_cspmu(E) ib_uverbs(E) cppc_cpufreq(E) coresight_etm4x(E) coresight_stm(E) ipmi_msghandler(E) coresight_trbe(E) arm_cspmu_module(E) arm_smmuv3_pmu(E) arm_spe_pmu(E) stm_core(E) coresight_tmc(E) coresight_funnel(E) coresight(E) bpf_preload(E) sch_fq_codel(E) ip_tables(E) ip6_tables(E) vhost_net(E) tun(E) vhost(E) vhost_iotlb(E) tap(E) tls(E) mpls_gso(E) mpls_iptunnel(E) mpls_router(E) fou(E) acpi_power_meter(E) loop(E) drm(E) backlight(E) drm_panel_orientation_quirks(E) autofs4(E) raid0(E) efivarfs(E) dm_crypt(E)
> [88757.991191] [ T640437] CPU: 70 UID: 34133 PID: 640437 Comm: Collection-20 Kdump: loaded Tainted: G M E 6.16.1-0_fbk2_0_gf40efc324cc8 #1 NONE
> [88758.017569] [ T640437] Tainted: [M]=MACHINE_CHECK, [E]=UNSIGNED_MODULE
> [88758.028860] [ T640437] Hardware name: ....
> [88758.046969] [ T640437] pstate: 23401009 (nzCv daif +PAN -UAO +TCO +DIT +SSBS BTYPE=--)
> [88758.061075] [ T640437] pc : d_lookup+0x5c/0x220
> [88758.068392] [ T640437] lr : try_lookup_noperm+0x30/0x50
> [88758.077088] [ T640437] sp : ffff800138cafc30
> [88758.083827] [ T640437] x29: ffff800138cafc40 x28: ffff0001dcfe8bc0 x27: 00000000bc0a11f7
> [88758.098321] [ T640437] x26: 00000000000ee00c x25: ffffffffffffffff x24: 0000000000000001
> [88758.112807] [ T640437] x23: ffff003fa14d0000 x22: ffff8000828d3740 x21: ffff800138cafde8
> [88758.127281] [ T640437] x20: ffff0000d0316fc0 x19: ffff800138cafce0 x18: 0001000000000000
> [88758.141753] [ T640437] x17: 0000000000000001 x16: 0000000001ffffff x15: dfc038a300003936
> [88758.156226] [ T640437] x14: 00000000fffffffa x13: ffffffffffffffff x12: ffff0000d0316fc0
> [88758.170695] [ T640437] x11: 61c8864680b583eb x10: 0000000000000039 x9 : ffff800080fcfd68
> [88758.185170] [ T640437] x8 : ffff003fa72d5088 x7 : 0000000000000000 x6 : ffff800138cafd58
> [88758.199645] [ T640437] x5 : ffff0001dcfe8bc0 x4 : ffff80008104a330 x3 : 0000000000000002
> [88758.214111] [ T640437] x2 : ffff800138cafd4d x1 : ffff800138cafce0 x0 : ffff0000d0316fc0
> [88758.228579] [ T640437] Call trace:
> [88758.233565] [ T640437] d_lookup+0x5c/0x220 (P)
> [88758.240864] [ T640437] try_lookup_noperm+0x30/0x50
> [88758.248868] [ T640437] proc_fill_cache+0x54/0x140
> [88758.256696] [ T640437] proc_readfd_common+0x138/0x1e8
> [88758.265222] [ T640437] proc_fd_iterate.llvm.7260857650841435759+0x1c/0x30
> [88758.277248] [ T640437] iterate_dir+0x84/0x228
> [88758.284354] [ T640437] __arm64_sys_getdents64+0x5c/0x110
> [88758.293383] [ T640437] invoke_syscall+0x4c/0xd0
> [88758.300843] [ T640437] do_el0_svc+0x80/0xb8
> [88758.307599] [ T640437] el0_svc+0x30/0xf0
> [88758.313820] [ T640437] el0t_64_sync_handler+0x70/0x100
> [88758.322497] [ T640437] el0t_64_sync+0x17c/0x180
> ...
>
> And my clear crash would look like the following:
>
> [ 1096.480523] Memory failure: 0x2: recovery action for reserved kernel page: Ignored
> [ 1096.480751] Kernel panic - not syncing: Memory failure: 0x2: unrecoverable page
> [ 1096.480760] CPU: 5 UID: 0 PID: 683 Comm: bash Tainted: G B 7.0.0-next-20260414-upstream-00004-gcbb3af7bfd3b #93 PREEMPTLAZY
> [ 1096.480768] Tainted: [B]=BAD_PAGE
> [ 1096.480774] Call Trace:
> [ 1096.480778] <TASK>
> [ 1096.480782] vpanic+0x399/0x700
> [ 1096.480821] panic+0xb4/0xc0
> [ 1096.480849] action_result+0x278/0x340
> [ 1096.480857] memory_failure+0x152b/0x1c80
> [ 1096.480925] hwpoison_inject+0x3a6/0x3f0 [hwpoison_inject]
> ....
>
>
> Isn't the clean approach way better than the random one?
I don't fully agree. In the past upstream has enhanced many kernel mm
services (e.g. khugepaged, page migration, dump_user_range()) to
recover from memory error in order to improve system availability,
given these service or tools can fail safe. Seeing many crashes
pointing to a certain in-kernel service at consumption time helped us
decide what services we should enhance, and which service we should
prioritize. Of course not all kernel code can be recovered from memory
error, but that doesn't mean knowing what kernel code often caused
crash isn't useful.
>
> For testing, I use this simple procedure, in case you want to play with
> it:
> # modprobe hwpoison-inject
> # sysctl -w vm.panic_on_unrecoverable_memory_failure=0
> # echo 1 > /sys/kernel/debug/hwpoison/corrupt-pfn
>
>
> Thanks for the review and good discussion,
Anyway, I only have a second opinion on the usefulness of a static
stack trace. This fail-fast option is good to have. Thanks!
> --breno
>
^ permalink raw reply
* Re: [PATCH V10 00/10] famfs: port into fuse
From: Joanne Koong @ 2026-04-16 15:56 UTC (permalink / raw)
To: John Groves
Cc: Darrick J. Wong, Miklos Szeredi, Bernd Schubert, John Groves,
Dan Williams, Bernd Schubert, Alison Schofield, John Groves,
Jonathan Corbet, Shuah Khan, Vishal Verma, Dave Jiang,
Matthew Wilcox, Jan Kara, Alexander Viro, David Hildenbrand,
Christian Brauner, Randy Dunlap, Jeff Layton, Amir Goldstein,
Jonathan Cameron, Stefan Hajnoczi, Josef Bacik, Bagas Sanjaya,
Chen Linxuan, James Morse, Fuad Tabba, Sean Christopherson,
Shivank Garg, Ackerley Tng, Gregory Price, Aravind Ramesh,
Ajay Joshi, venkataravis@micron.com, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org, nvdimm@lists.linux.dev,
linux-cxl@vger.kernel.org, linux-fsdevel@vger.kernel.org, djbw
In-Reply-To: <ad7Tps4tkNbndd9Z@groves.net>
On Tue, Apr 14, 2026 at 5:10 PM John Groves <John@groves.net> wrote:
>
> On 26/04/14 03:13PM, Joanne Koong wrote:
> > On Tue, Apr 14, 2026 at 11:57 AM Darrick J. Wong <djwong@kernel.org> wrote:
> > >
> > > On Tue, Apr 14, 2026 at 08:41:42AM -0500, John Groves wrote:
> > > > On 26/04/14 03:19PM, Miklos Szeredi wrote:
> > > > > On Fri, 10 Apr 2026 at 21:44, Joanne Koong <joannelkoong@gmail.com> wrote:
> > > > >
> > > > > > Overall, my intention with bringing this up is just to make sure we're
> > > > > > at least aware of this alternative before anything is merged and
> > > > > > permanent. If Miklos and you think we should land this series, then
> > > > > > I'm on board with that.
> > > > >
> > > > > TBH, I'd prefer not to add the famfs specific mapping interface if not
> > > > > absolutely necessary. This was the main sticking point originally,
> > > > > but there seemed to be no better alternative.
> > > > >
> > > > > However with the bpf approach this would be gone, which is great.
> > >
> > > Well... you can't get away with having *no* mapping interface at all.
> >
> > Yes but the mapping interface should be *generic*, not one that is so
> > specifically tailored to one server. fuse will have to support this
> > forever.
>
> Mapping interfaces being generic is a nice idea, but I'm no sure it's
> realistic in a generalized sense. But other mitigating comments below.
>
> >
> > > You still have to define a UABI that BPF programs can use to convey
> > > mapping data into fsdax/iomap. BTF is a nice piece of work that smooths
> > > over minor fluctuations in struct layout between a running kernel and
> > > a precompiled BPF program, but fundamentally we still need a fuse-native
> > > representation.
> > >
> > > That last sentence was an indirect way of saying: No, we're not going
> > > to export struct iomap to userspace. The fuse-iomap patchset provides
> > > all the UABI pieces we need for regular filesystems (ext4) and hardware
> > > adjacent filesystems (famfs) to exchange file mapping data with the
> > > kernel. This has been out for review since last October, but the lack
> > > of engagement with that patchset (or its February resubmission) doesn't
> > > leave me with confidence that any of it is going anywhere.
> > >
> > > Note: The reason for bolting BPF atop fuse-iomap is so that famfs can
> > > upload bpf programs to generate interleaved mappings. It's not so hard
> > > to convert famfs' iomapping paths to use fuse-iomap, but I haven't
> > > helped him do that because:
> > >
> > > a) I have no idea what Miklos' thoughts are about merging any of the
> > > famfs stuff.
> > >
> > > b) I also have no idea what his thoughts are about fuse-iomap. The
> > > sparse replies are not encouraging.
> > >
> > > c) It didn't seem fair to John to make him take on a whole new patchset
> > > dependency given (a) and (b).
> > >
> > > d) Nobody ever replied to my reply to the LSFMM thread about "can we do
> > > some code review of fuse iomap without waiting three months for LSFMM?"
> > > I've literally done nothing with fuse-iomap for two of the three months
> > > requested.
> > >
> > > > > So let us please at least have a try at this. I'm not into bpf yet,
> > > > > but willing to learn.
> > >
> > > I sent out the patches to enable exactly this sort of experimentation
> > > two months ago, and have not received any responses:
> > >
> > > https://lore.kernel.org/linux-fsdevel/177188736765.3938194.6770791688236041940.stgit@frogsfrogsfrogs/
> > >
> > > I would like to say this as gently as possible: I don't know what the
> > > problem here is, Miklos -- are you uninterested in the work? Do you
> > > have too many other things to do inside RH that you can't talk about?
> > > Is it too difficult to figure out how the iomap stuff fits into the rest
> > > of the fuse codebase? Do you need help from the rest of us to get
> > > reviews done? Is there something else with which I could help?
> > >
> > > Because ... over the past few years, many of my team's filesystem
> > > projects have endured monthslong review cycles and often fail to get
> > > merged. This has led to burnout and frustration among my teammates such
> > > that many of them chose to move on to other things. For the remaining
> > > people, it was very difficult to justify continuing headcount when
> > > progress on projects is so slow that individuals cannot achieve even one
> > > milestone per quarter on any project.
> > >
> > > There's now nobody left here but me.
> > >
> > > I'm not blaming you (Miklos) for any of this, but that is the current
> > > deplorable state of things.
> > >
> > > > > Thanks,
> > > > > Miklos
> > > >
> > > > Thanks for responding...
> > > >
> > > > My short response: Noooooooooo!!!!!!
> > > >
> > > > I very strongly object to making this a prerequisite to merging. This
> > > > is an untested idea that will certainly delay us by at least a couple
> > > > of merge windows when products are shipping now, and the existing approach
> > > > has been in circulation for a long time. It is TOO LATE!!!!!!
> > >
> > > /me notes that has "we're shipping so you have to merge it over peoples'
> > > concerns" rarely carries the day in LKML land, and has never ended well
> > > in the few cases that it happens. As Ted is fond of saying, this is a
> > > team sport, not an individual effort. Unfortunately, to abuse your
> > > sports metaphor, we all play for the ******* A's.
> > >
> > > That said, you're clearly pissed at the goalposts changing yet again,
> > > and that's really not fair that we collectively keep moving them.
> > >
> > > It's a rotten situation that I could have even helped you to solve both
> > > our problems via fuse-iomap, but I just couldn't motivate myself to
> > > entwine our two projects until the technical direction questions got
> > > answered.
> > >
> > > > Famfs is not a science project, it's enablement for actual products and
> > > > early versions are available now!!!
> > > >
> > > > That doesn't mean we couldn't convert later IF THERE ARE NO HIDDEN PROBLEMS.
> > >
> > > Heck, the fuse command field is a u32. There are plenty of numberspace
> > > left, and the kernel can just *stop issuing them*.
> >
> > I don't think the problem is the command field. As I understand it, if
> > this lands and is converted over later, none of the famfs code in this
> > series can be removed from fuse. If fuse has native non-bpf support
> > for famfs, then it will always need to have that. That's the part that
> > worries me.
>
> I believe this basic premise is completely wrong. Here is why:
>
> There is a FUSE_DAX_FMAP capability that the kernel may advertise or not
> at init time; this capability "is" the famfs GET_FMAP AND GET_DAXDEV
> commands. In the future, if we find a way to use BPF (or some other
> mechanism) to avoid needing those fuse messages, the kernel could be updated
> to NEVER advertise the FUSE_DAX_FMAP capability. All of the famfs-specific
> code could be taken out of kernels that never advertise that capability.
I’m not sure the capability bit can be used like that (though I am
hoping it can!). As I understand it, once the kernel advertises a
capability, it must continue supporting it in future kernels else
userspace programs that rely on it will break.
John, with what you have in this series, is there any way we can make
it generalizable so it can be used by any dax-based server? Would you
be open to that?
My main gripe is with the layout encoding for FUSE_GET_FMAP and how it
bakes in famfs-specific layout concepts (simple vs interleaved
extents, file type, strip definitions, etc) into the uapi and forces
the kernel to interpret/process this super-specific encoding in a
hardcoded way. Is it not possible to just move this all to the server
side? Could the famfs server just preprocess the mappings and take
care of the simple vs interleaved extents stuff and do all the logic
that's in [1] in userspace and simply just give the kernel a list of
generic extent mapping information? The kernel could then just cache
this generic mapping per inode and do the lookup on fault with no
famfs-specific logic needed in the kernel. Or is there a reason the
famfs processing logic needs to happen in the kernel? That's the part
I'm unclear about.
I think this is pretty much what Darrick is doing in his iomap
patchset [2] except in his case, the mappings are fetched lazily on
access, whereas I guess for your case that would be too slow so it
needs to be prepopulated at open time. imo that seems fine since
prepopulating mappings would be a useful feature in general. So could
we rename FUSE_GET_FMAP to something like FUSE_IOMAP_GETMAP and have
it just return super generic mapping info? Darrick already adds a
"struct fuse_iomap_io" [3] to the uapi, could we just use that and add
a uint32_t dev_id; to that?
Looking more at Darrick's patchset, his caching [4] uses the b+ tree
and is more performant but imo also more complex, but I don't think
that would be necessary for this series. imo we could just use a basic
interval tree for now for famfs's needs and optimize / converge it
later.
As well, are you open to renaming the FUSE_DAX_FMAP capability and
FUSE_FAMFS_DAX config to a generic FUSE_IOMAP_DAX naming scheme
instead?
Additionally, it would be really nice to replace FUSE_GET_DAXDEV and
add the daxdev config stuff to the generic FUSE_IOMAP_CONFIG Darrick
has for iomap [5], but this one might be more contentious in getting
the config uapi solidified. I think in general though for famfs, it
makes more sense for all the dax devices to be set up upfront all at
once at connection init time instead of discovered lazily on every
file open, which as I understand it, would make things less complex
server side for famfs too, so even if this doesn't go through
FUSE_IOMAP_CONFIG, maybe it makes sense to have this be on init where
the server sends all the daxdev device information all at once? That
seems less confusing (and better performance-wise) of a flow than
having it be lazily discovered on open, though if I'm misunderstanding
something here please let me know.
I'm not sure how Miklos feels about this, but I think this would get
rid of all the famfs-specific logic in fuse and would be adding
infrastrucutre that other dax-backed servers in the future would use.
On my end, this eases my concerns and would be just as good as what
the bpf approach is trying to accomplish.
There's some ties between your and Darrick's work as you guys are both
using iomap, and maybe/hopefully this aligns with Darrick's thoughts
combining the two, but in my opinion (and maybe Darrick disagrees with
this), to get famfs out the door, we wouldn't need all the code in
Darrick's patchset to land before famfs. In my view, we would only
need to borrow/steal the 'struct fuse_iomap_io' definition in uapi and
hopefully converge on a FUSE_IOMAP_CONFIG uapi format, for famfs to
land, if you agree this direction makes sense. I think we just need to
get the uapi stuff figured out and everything else could be optimized
in the future post-merge. Darrick, do you agree with this assessment?
John, is this a middle ground you think is reasonable? I know this
whole thing has kind of been a mess with the moving goalposts - if the
above would work for famfs but you're slammed with other work and
don't have the bandwidth to make these changes, I'm happy to help out
with the restructuring if that would be useful.
>
> Simple, really. Can't re-use the message opcodes, but as Darrick pointed out
> those are not a scarce resource.
>
> >
> > >
> > > > What are the risks of converting to BPF?
> >
> > I think maybe there is a misinterpretation of what the alternative
> > approach entails. From my point of view, the alternative approach is
> > not that different from what is already in this series. The only piece
> > of the famfs logic that would need to use bpf is the logic for
> > finding/computing the extent mappings (which is the famfs-specific
> > logic that would not be applicable to any other server). That famfs
> > bpf code is minimal and already written [1], as it is just the logic
> > that is in patch 6 [2] in this series copied over. No other part of
> > famfs touches bpf. The rest is renaming the functions in
> > fs/fuse/famfs.c to generic fuse_iomap_dax_XXX names (the logic is the
> > same logic in this series, eg invoking the lower-level calls to
> > dax_iomap_rw/fault/etc) and moving the daxdev setup/initialization to
> > connection initialization time where the server passes that daxdev
> > setup info/configs upfront. I don't think this would delay things by
> > several merge windows, as the code is already mostly written. If it
> > would be helpful, I can clean up what's in the prototype and send that
> > out.
> >
> > I think the part that is not clear yet and needs to be verified is
> > whether this approach runs into any technical limitations on famfs's
> > production workloads. For example, does the overhead of using bpf maps
> > lead to a noticeable performance drop on real workloads? In the
> > future, will there be too many extent mappings on high-scale systems
> > to make this feasible? etc. If there are technical reasons why the
> > famfs logic has to be in fuse, then imo we should figure that out and
> > ideally that's the discussion we should be having. I am not a cxl
> > expert so perhaps there is something missing in the approach that
> > makes it not sufficient on production systems. If we don't end up
> > going with the alternative approach, I still think this series should
> > try to make the famfs uapi additions to fuse as generic as possible
> > since that will be irreversible.
> >
> > If we expedited the alternative approach in terms of reviewing and
> > merging, would that suffice? Is the main pushback the timing of it, eg
> > that it would take too long to get reviewed, merged, and shipped?
> >
> > > >
> > > > - I don't know how to do it - so it'll be slow (kinda like my fuse learning
> > > > curve cost about a year because this is not that similar to anything
> > > > else that was already in fuse.
> > >
> > > ...and per above, BPF isn't some magic savior that avoids the expansion
> > > of the UABI.
> >
> > It doesn't avoid the expansion of the UABI but it makes the UABI
> > generic (eg plenty of future servers can/will use the generic iomap
> > layer).
>
> Um, advertised capabilities allow contraction of the UABI-handling code with
> only some small cruft. Code that is only reachable in the presence of dead
> capability can totally be removed.
>
> >
> > >
> > > > - Those of us who are involved don't fully understand either the security
> > > > or performance implications of this. It
> > >
> > > Correct. I sure think it's swell that people can inject IR programs
> > > that jit/link into the kernel. Don't ask which secondary connotation of
> > > "swell" I'm talking about.
> >
> > bpf is used elsewhere in the kernel (eg networking, scheduling). If it
> > is the case that it is unsafe (which maybe it is, I don't know), then
> > wouldn't those other areas have the same issues?
>
> See my long comment to Darrick's prior email.
>
> I suspect that this would be the only place BPF has been tried for a vma
> fault handler. That is a special, performance critical path - especially
> for famfs. In discussion with the right people we can probably reason
> through whether this is a non-starter or not.
Yes, I think the bpf overhead is the main uncertainty about whether
this suffices or not for famfs. I understand (from the all caps in
your previous messages :)) that performance is critical. The bpf
overhead could indeed be too much for the special famfs performance
critical path. I'll try to get some time next week to benchmark this.
Thanks,
Joanne
[1] https://lore.kernel.org/linux-fsdevel/0100019d43e79794-0eadcf5e-b659-43f7-8fdc-dec9f4ccce14-000000@email.amazonses.com/
[2] https://lore.kernel.org/linux-fsdevel/177188733084.3935219.10400570136529869673.stgit@frogsfrogsfrogs/T/#t
[3] https://lore.kernel.org/linux-fsdevel/176169810371.1424854.3010195280915622081.stgit@frogsfrogsfrogs/
[4] https://lore.kernel.org/linux-fsdevel/177188735954.3937557.841478048197856035.stgit@frogsfrogsfrogs/
[5] https://lore.kernel.org/linux-fsdevel/176169813786.1427432.414564085463311156.stgit@frogsfrogsfrogs/
>
> >
> > >
> > > > - Famfs is enabling access to memory and mapping fault handling must be
> > > > at "memory speed". We know that BPF walks some data structures when a
> > > > program executes. That exposes us to additional serialized L3 cache
> > > > misses each time we service a mapping fault (any TLB & page table miss).
> > > > This should be studied side-by-side with the existing approach under
> > > > multiple loads before being adopted for production.
> > >
> > > Yes, it should. AFAICT if one switched to a per-inode bpf program, then
> > > you could do per-inode bpf programs. Then you don't even need the bpf
> > > map, and the ->iomap_begin becomes an indirect call into JITted x86_64
> > > math code.
> > >
> > > (The downside is that dyn code can't be meaningfully signed, requires
> > > clang on the system, and you have to deal with inode eviction issues.)
> > >
> > > > - This has never been done in production, and we're throwing it in the way
> > > > of a project that has been soaking for years and needs to support early
> > > > shipments of products.
> > >
> > > Correct. I haven't even implemented BPF-iomap for fuse4fs. This BPF
> > > integration stuff is *highly* experimental code.
> >
> > I think what fuse4fs needs for bpf is significantly more complicated
> > and intensive than what famfs needs. For famfs, the extent mapping
> > logic is straightforward computation.
> >
> > >
> > > > If this is the only path, I'd like to revive famfs as a standalone file
> > > > system. I'm still maintaining that and it's still in use.
> > >
> > > Honestly, you should probably just ship that to your users. As long as
> > > the ondisk format doesn't change much, switching the implementation at a
> > > later date is at least still possible.
> >
> > I recognize this is an unfair situation John as you've already spent
> > years working on this and did what the community asked with rewriting
> > it. What I'm hoping to convey is that the approach where the extent
> > computing/finding logic gets moved to bpf is not radically different
> > from the famfs logic already in this patchset. In my view, moving this
> > logic to bpf is more advantageous for both fuse *and* famfs
> > (decoupling famfs releases from kernel releases) - it would be great
> > to consider this on technical merits if expediting the timeline of the
> > alternative approach would suffice.
> >
> > Thanks,
> > Joanne
> >
> > [1] https://github.com/joannekoong/libfuse/blob/444fa27fa9fd2118a0dc332933197faf9bbf25aa/example/famfs.bpf.c
> > [2] https://lore.kernel.org/linux-fsdevel/0100019d43e79794-0eadcf5e-b659-43f7-8fdc-dec9f4ccce14-000000@email.amazonses.com/
> >
> > >
> > > --D
>
> Regards,
> John
>
^ permalink raw reply
* Re: [PATCH v2 1/2] dt-bindings: hwmon: pmbus: add max20830
From: Conor Dooley @ 2026-04-16 15:51 UTC (permalink / raw)
To: Alexis Czezar Torreno
Cc: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Shuah Khan, linux-hwmon, devicetree,
linux-kernel, linux-doc
In-Reply-To: <20260416-dev_max20830-v2-1-2c7d676dc0bd@analog.com>
[-- Attachment #1: Type: text/plain, Size: 3739 bytes --]
On Thu, Apr 16, 2026 at 03:59:10PM +0800, Alexis Czezar Torreno wrote:
> Add device tree documentation for MAX20830 step-down DC-DC switching
> regulator with PMBus interface.
>
> Signed-off-by: Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
> ---
> .../bindings/hwmon/pmbus/adi,max20830.yaml | 61 ++++++++++++++++++++++
> MAINTAINERS | 7 +++
> 2 files changed, 68 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml
> new file mode 100644
> index 0000000000000000000000000000000000000000..8b3ec1ffa0c9460de2122f6606ce3dcbcdfbbcc7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml
> @@ -0,0 +1,61 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/hwmon/pmbus/adi,max20830.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices MAX20830 Step-Down Switching Regulator with PMBus
> +
> +maintainers:
> + - Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
> +
> +description: |
> + The MAX20830 is a fully integrated step-down DC-DC switching regulator with
> + PMBus interface. It provides 2.7V to 16V input, 0.4V to 5.8V adjustable
> + output, and up to 30A output current. It allows monitoring of input/output
> + voltage, output current and temperature through the PMBus serial interface.
> + Datasheet:
> + https://www.analog.com/en/products/max20830.html
> +
> +allOf:
> + - $ref: /schemas/regulator/regulator.yaml#
> +
> +properties:
> + compatible:
> + const: adi,max20830
> +
> + reg:
> + maxItems: 1
On the previous version, you got an LLM comment about not having the
interrupts property amongst other things.
I think the other things got implemented, but I didn't see any reply to
the bot about that?
I think the answer is that it shouldn't because the pin it referenced
doesn't exist, but when looking at the schematic I have to wonder if
there should be an interrupts property for dealing with "pgood"?
Cheers,
Conor.
> +
> + vddh-supply:
> + description:
> + Phandle to the regulator that provides the VDDH power supply.
> +
> + avdd-supply:
> + description:
> + Phandle to the regulator that provides the AVDD power supply.
> +
> + ldoin-supply:
> + description:
> + Optional 2.5V to 5.5V LDO input supply.
> +
> +required:
> + - compatible
> + - reg
> + - vddh-supply
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + i2c {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + regulator@30 {
> + compatible = "adi,max20830";
> + reg = <0x30>;
> + vddh-supply = <&vddh>;
> + };
> + };
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0a3991c10ade20dd79cc7d1bf2a1d307ba6bd19d..031c743e979521a92ed9ac67915c178ce31727bd 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15579,6 +15579,13 @@ F: Documentation/devicetree/bindings/hwmon/pmbus/adi,max17616.yaml
> F: Documentation/hwmon/max17616.rst
> F: drivers/hwmon/pmbus/max17616.c
>
> +MAX20830 HARDWARE MONITOR DRIVER
> +M: Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
> +L: linux-hwmon@vger.kernel.org
> +S: Supported
> +W: https://ez.analog.com/linux-software-drivers
> +F: Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml
> +
> MAX2175 SDR TUNER DRIVER
> M: Ramesh Shanmugasundaram <rashanmu@gmail.com>
> L: linux-media@vger.kernel.org
>
> --
> 2.34.1
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v4 0/3] mm/memory-failure: add panic option for unrecoverable pages
From: Breno Leitao @ 2026-04-16 15:32 UTC (permalink / raw)
To: Jiaqi Yan
Cc: Miaohe Lin, Naoya Horiguchi, Andrew Morton, Jonathan Corbet,
Shuah Khan, David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
linux-mm, linux-kernel, linux-doc, kernel-team
In-Reply-To: <CACw3F51PC0iB6mfbiceQ_Kh242FN8zdXOfTyE5Pa_5+gjTPPGg@mail.gmail.com>
Hi Jiaqi,
On Wed, Apr 15, 2026 at 01:56:35PM -0700, Jiaqi Yan wrote:
> On Wed, Apr 15, 2026 at 5:55 AM Breno Leitao <leitao@debian.org> wrote:
> >
> > When the memory failure handler encounters an in-use kernel page that it
> > cannot recover (slab, page tables, kernel stacks, vmalloc, etc.), it
> > currently logs the error as "Ignored" and continues operation.
> >
> > This leaves corrupted data accessible to the kernel, which will inevitably
> > cause either silent data corruption or a delayed crash when the poisoned memory
> > is next accessed.
> >
> > This is a common problem on large fleets. We frequently observe multi-bit ECC
> > errors hitting kernel slab pages, where memory_failure() fails to recover them
> > and the system crashes later at an unrelated code path, making root cause
> > analysis unnecessarily difficult.
> >
> > Here is one specific example from production on an arm64 server: a multi-bit
> > ECC error hit a dentry cache slab page, memory_failure() failed to recover it
> > (slab pages are not supported by the hwpoison recovery mechanism), and 67
> > seconds later d_lookup() accessed the poisoned cache line causing
> > a synchronous external abort:
> >
> > [88690.479680] [Hardware Error]: error_type: 3, multi-bit ECC
> > [88690.498473] Memory failure: 0x40272d: unhandlable page.
> > [88690.498619] Memory failure: 0x40272d: recovery action for
> > get hwpoison page: Ignored
> > ...
> > [88757.847126] Internal error: synchronous external abort:
> > 0000000096000410 [#1] SMP
> > [88758.061075] pc : d_lookup+0x5c/0x220
> >
> > This series adds a new sysctl vm.panic_on_unrecoverable_memory_failure
> > (default 0) that, when enabled, panics immediately on unrecoverable
> > memory failures. This provides a clean crash dump at the time of the
>
> I get the fail-fast part, but wonder will kernel really be able to
> provide clean crash dump useful for diagnosis?
Yes, the kernel does provide a useful crash dump. With the sysctl enabled,
here's what I observe:
Kernel panic - not syncing: Memory failure: 0x1: unrecoverable page
CPU: 40 UID: 0 PID: 682 Comm: bash Tainted: G B 7.0.0-next-20260414-upstream-00004-gcbb3af7bfd3b #93
Tainted: [B]=BAD_PAGE
Call Trace:
<TASK>
vpanic+0x399/0x700
panic+0xb4/0xc0
action_result+0x278/0x340 ← your new panic call site
memory_failure+0x152b/0x1c80
Without the patch (or with the sysctl disabled), you only get:
Memory failure: 0x1: unhandlable page.
Memory failure: 0x1: recovery action for reserved kernel page: Ignored
Then the host continues running until it eventually accesses that poisoned
memory, triggering a generic error similar to the d_lookup() case mentioned
above.
> In your example at 88757.847126, kernel was handling SEA and because
> we are under kernel context, eventually has to die(). Apparently not
> only your patch, but also memory-failure has no role to play there.
> But at least SEA handling tried its best to show the kernel code that
> consumed the memory error.
>
> So your code should apply to the memory failure handling at
> 88690.498473, which is likely triggered from APEI GHES for poison
> detection (I guess the example is from ARM64). Anything except SEA is
> considered not synchronous (by APEI is_hest_sync_notify()). If kernel
> panics there, I guess it will be in a random process context or a
> kworker thread? How useful is it for diagnosis? Just the exact time an
> error detected (which is already logged by kernel)?
The kernel panics with a clear stack trace and explicit reason, making it
straightforward to correlate and analyze the failure.
My objective is to have a clean, immediate crash rather than allowing the
system to continue running and potentially crash later (if at all).
Working at a hyperscaler, I regularly see thousands of these "unhandlable
page" messages, followed by later kernel crashes when the corrupted memory
is eventually accessed.
> On X86, for UCNA or SRAO type machine check exceptions, I think with
> your patch the panic would also happen in random process context or
> kworker thread,
>
> Can you share some clean crash dumps from your testing that show they
> are more useful than the crash at SEA? Thanks!
Certainly, here is the complete crash dump from the example above. This
happened on a real production hardware:
[88690.478913] [ T593001] {1}[Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 784
[88690.479097] [ T593001] {1}[Hardware Error]: event severity: recoverable
[88690.479184] [ T593001] {1}[Hardware Error]: imprecise tstamp: 2026-03-20 13:13:08
[88690.479282] [ T593001] {1}[Hardware Error]: Error 0, type: recoverable
[88690.479359] [ T593001] {1}[Hardware Error]: section_type: memory error
[88690.479424] [ T593001] {1}[Hardware Error]: physical_address: 0x00000040272d5080
[88690.479503] [ T593001] {1}[Hardware Error]: physical_address_mask: 0xfffffffffffff000
[88690.479606] [ T593001] {1}[Hardware Error]: node:0 card:0 module:1 rank:1 bank:13 device:6 row:64114 column:832 requestor_id:0x0000000000000027
[88690.479680] [ T593001] {1}[Hardware Error]: error_type: 3, multi-bit ECC
[88690.479754] [ T593001] {1}[Hardware Error]: DIMM location: not present. DMI handle: 0x000e
[88690.479882] [ T593001] EDAC MC0: 1 UE multi-bit ECC on unknown memory (node:0 card:0 module:1 rank:1 bank:13 device:6 row:64114 column:832 requestor_id:0x0000000000000027 DIMM location: not present. DMI handle: 0x000e page:0x40272d offset:0x5080 grain:4096 - APEI location: node:0 card:0 module:1 rank:1 bank:13 device:6 row:64114 column:832 requestor_id:0x0000000000000027 DIMM location: not present. DMI handle: 0x000e)
[88690.498473] [ T593001] Memory failure: 0x40272d: unhandlable page.
[88690.498619] [ T593001] Memory failure: 0x40272d: recovery action for get hwpoison page: Ignored
[88757.847126] [ T640437] Internal error: synchronous external abort: 0000000096000410 [#1] SMP
[88757.867131] [ T640437] Modules linked in: ghes_edac(E) act_gact(E) sch_fq(E) tcp_diag(E) inet_diag(E) cls_bpf(E) mlx5_ib(E) sm3_ce(E) sha3_ce(E) sha512_ce(E) ipmi_ssif(E) ipmi_devintf(E) nvidia_cspmu(E) ib_uverbs(E) cppc_cpufreq(E) coresight_etm4x(E) coresight_stm(E) ipmi_msghandler(E) coresight_trbe(E) arm_cspmu_module(E) arm_smmuv3_pmu(E) arm_spe_pmu(E) stm_core(E) coresight_tmc(E) coresight_funnel(E) coresight(E) bpf_preload(E) sch_fq_codel(E) ip_tables(E) ip6_tables(E) vhost_net(E) tun(E) vhost(E) vhost_iotlb(E) tap(E) tls(E) mpls_gso(E) mpls_iptunnel(E) mpls_router(E) fou(E) acpi_power_meter(E) loop(E) drm(E) backlight(E) drm_panel_orientation_quirks(E) autofs4(E) raid0(E) efivarfs(E) dm_crypt(E)
[88757.991191] [ T640437] CPU: 70 UID: 34133 PID: 640437 Comm: Collection-20 Kdump: loaded Tainted: G M E 6.16.1-0_fbk2_0_gf40efc324cc8 #1 NONE
[88758.017569] [ T640437] Tainted: [M]=MACHINE_CHECK, [E]=UNSIGNED_MODULE
[88758.028860] [ T640437] Hardware name: ....
[88758.046969] [ T640437] pstate: 23401009 (nzCv daif +PAN -UAO +TCO +DIT +SSBS BTYPE=--)
[88758.061075] [ T640437] pc : d_lookup+0x5c/0x220
[88758.068392] [ T640437] lr : try_lookup_noperm+0x30/0x50
[88758.077088] [ T640437] sp : ffff800138cafc30
[88758.083827] [ T640437] x29: ffff800138cafc40 x28: ffff0001dcfe8bc0 x27: 00000000bc0a11f7
[88758.098321] [ T640437] x26: 00000000000ee00c x25: ffffffffffffffff x24: 0000000000000001
[88758.112807] [ T640437] x23: ffff003fa14d0000 x22: ffff8000828d3740 x21: ffff800138cafde8
[88758.127281] [ T640437] x20: ffff0000d0316fc0 x19: ffff800138cafce0 x18: 0001000000000000
[88758.141753] [ T640437] x17: 0000000000000001 x16: 0000000001ffffff x15: dfc038a300003936
[88758.156226] [ T640437] x14: 00000000fffffffa x13: ffffffffffffffff x12: ffff0000d0316fc0
[88758.170695] [ T640437] x11: 61c8864680b583eb x10: 0000000000000039 x9 : ffff800080fcfd68
[88758.185170] [ T640437] x8 : ffff003fa72d5088 x7 : 0000000000000000 x6 : ffff800138cafd58
[88758.199645] [ T640437] x5 : ffff0001dcfe8bc0 x4 : ffff80008104a330 x3 : 0000000000000002
[88758.214111] [ T640437] x2 : ffff800138cafd4d x1 : ffff800138cafce0 x0 : ffff0000d0316fc0
[88758.228579] [ T640437] Call trace:
[88758.233565] [ T640437] d_lookup+0x5c/0x220 (P)
[88758.240864] [ T640437] try_lookup_noperm+0x30/0x50
[88758.248868] [ T640437] proc_fill_cache+0x54/0x140
[88758.256696] [ T640437] proc_readfd_common+0x138/0x1e8
[88758.265222] [ T640437] proc_fd_iterate.llvm.7260857650841435759+0x1c/0x30
[88758.277248] [ T640437] iterate_dir+0x84/0x228
[88758.284354] [ T640437] __arm64_sys_getdents64+0x5c/0x110
[88758.293383] [ T640437] invoke_syscall+0x4c/0xd0
[88758.300843] [ T640437] do_el0_svc+0x80/0xb8
[88758.307599] [ T640437] el0_svc+0x30/0xf0
[88758.313820] [ T640437] el0t_64_sync_handler+0x70/0x100
[88758.322497] [ T640437] el0t_64_sync+0x17c/0x180
...
And my clear crash would look like the following:
[ 1096.480523] Memory failure: 0x2: recovery action for reserved kernel page: Ignored
[ 1096.480751] Kernel panic - not syncing: Memory failure: 0x2: unrecoverable page
[ 1096.480760] CPU: 5 UID: 0 PID: 683 Comm: bash Tainted: G B 7.0.0-next-20260414-upstream-00004-gcbb3af7bfd3b #93 PREEMPTLAZY
[ 1096.480768] Tainted: [B]=BAD_PAGE
[ 1096.480774] Call Trace:
[ 1096.480778] <TASK>
[ 1096.480782] vpanic+0x399/0x700
[ 1096.480821] panic+0xb4/0xc0
[ 1096.480849] action_result+0x278/0x340
[ 1096.480857] memory_failure+0x152b/0x1c80
[ 1096.480925] hwpoison_inject+0x3a6/0x3f0 [hwpoison_inject]
....
Isn't the clean approach way better than the random one?
For testing, I use this simple procedure, in case you want to play with
it:
# modprobe hwpoison-inject
# sysctl -w vm.panic_on_unrecoverable_memory_failure=0
# echo 1 > /sys/kernel/debug/hwpoison/corrupt-pfn
Thanks for the review and good discussion,
--breno
^ permalink raw reply
* [PATCH] Documentation: update deferred_probe_timeout cmdline parameter documentation
From: Hans de Goede @ 2026-04-16 14:41 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan
Cc: Hans de Goede, linux-doc, linux-kernel, Danilo Krummrich
Extend the deferred_probe_timeout cmdline parameter documentation,
documenting that negative values are treated as an infinite timeout value.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
Documentation/admin-guide/kernel-parameters.txt | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 03a550630644..0f1a1332480f 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1249,8 +1249,9 @@ Kernel parameters
deferred probe to give up waiting on dependencies to
probe. Only specific dependencies (subsystems or
drivers) that have opted in will be ignored. A timeout
- of 0 will timeout at the end of initcalls. If the time
- out hasn't expired, it'll be restarted by each
+ of 0 will timeout at the end of initcalls; a negative
+ value is treated as an infinite timeout value. If the
+ timeout hasn't expired, it'll be restarted by each
successful driver registration. This option will also
dump out devices still on the deferred probe list after
retrying.
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v2 2/2] hwmon: (pmbus/max20830) add driver for max20830
From: Nuno Sá @ 2026-04-16 14:38 UTC (permalink / raw)
To: Alexis Czezar Torreno
Cc: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Shuah Khan, linux-hwmon, devicetree,
linux-kernel, linux-doc
In-Reply-To: <20260416-dev_max20830-v2-2-2c7d676dc0bd@analog.com>
On Thu, Apr 16, 2026 at 03:59:11PM +0800, Alexis Czezar Torreno wrote:
> Add support for MAX20830 step-down DC-DC switching regulator with
> PMBus interface. It allows monitoring of input/output voltage,
> output current and temperature through the PMBus serial interface.
>
> Signed-off-by: Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
> ---
> Documentation/hwmon/index.rst | 1 +
> Documentation/hwmon/max20830.rst | 49 +++++++++++++++++++++++
> MAINTAINERS | 2 +
> drivers/hwmon/pmbus/Kconfig | 9 +++++
> drivers/hwmon/pmbus/Makefile | 1 +
> drivers/hwmon/pmbus/max20830.c | 86 ++++++++++++++++++++++++++++++++++++++++
> 6 files changed, 148 insertions(+)
>
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 8b655e5d6b68b90c697a52c7bf526e81d370caf7..56f7eb761be76dd627a2f34135abad05203b0582 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -158,6 +158,7 @@ Hardware Monitoring Kernel Drivers
> max197
> max20730
> max20751
> + max20830
> max31722
> max31730
> max31760
> diff --git a/Documentation/hwmon/max20830.rst b/Documentation/hwmon/max20830.rst
> new file mode 100644
> index 0000000000000000000000000000000000000000..936e409dcc5c0898dde27d782308d4a7e1357e73
> --- /dev/null
> +++ b/Documentation/hwmon/max20830.rst
> @@ -0,0 +1,49 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +Kernel driver max20830
> +======================
> +
> +Supported chips:
> +
> + * Analog Devices MAX20830
> +
> + Prefix: 'max20830'
> +
> + Addresses scanned: -
> +
> + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max20830.pdf
> +
> +Author:
> +
> + - Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
> +
> +
> +Description
> +-----------
> +
> +This driver supports hardware monitoring for Analog Devices MAX20830
> +Step-Down Switching Regulator with PMBus Interface.
> +
> +The MAX20830 is a 2.7V to 16V, 30A fully integrated step-down DC-DC switching
> +regulator. Through the PMBus interface, the device can monitor input/output
> +voltages, output current and temperature.
> +
> +The driver is a client driver to the core PMBus driver. Please see
> +Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
> +
> +Sysfs entries
> +-------------
> +
> +================= ========================================
> +in1_label "vin"
> +in1_input Measured input voltage
> +in1_alarm Input voltage alarm
> +in2_label "vout1"
> +in2_input Measured output voltage
> +in2_alarm Output voltage alarm
> +curr1_label "iout1"
> +curr1_input Measured output current
> +curr1_alarm Output current alarm
> +temp1_input Measured temperature
> +temp1_alarm Chip temperature alarm
> +================= ========================================
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 031c743e979521a92ed9ac67915c178ce31727bd..d6a6745e2dae29c3b8f80bbe61c54a2f5ecd9f47 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15585,6 +15585,8 @@ L: linux-hwmon@vger.kernel.org
> S: Supported
> W: https://ez.analog.com/linux-software-drivers
> F: Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml
> +F: Documentation/hwmon/max20830.rst
> +F: drivers/hwmon/pmbus/max20830.c
>
> MAX2175 SDR TUNER DRIVER
> M: Ramesh Shanmugasundaram <rashanmu@gmail.com>
> diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
> index 8f4bff375ecbc355f5ed3400855c2852ec2aa5ef..987705bf45b75b7b91ccc469247909f3c3f53d77 100644
> --- a/drivers/hwmon/pmbus/Kconfig
> +++ b/drivers/hwmon/pmbus/Kconfig
> @@ -365,6 +365,15 @@ config SENSORS_MAX20751
> This driver can also be built as a module. If so, the module will
> be called max20751.
>
> +config SENSORS_MAX20830
> + tristate "Analog Devices MAX20830"
> + help
> + If you say yes here you get hardware monitoring support for Analog
> + Devices MAX20830.
> +
> + This driver can also be built as a module. If so, the module will
> + be called max20830.
> +
> config SENSORS_MAX31785
> tristate "Maxim MAX31785 and compatibles"
> help
> diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
> index 7129b62bc00f8a2e98de14004997752a856dfda2..bc52f930e0825a902a0dd1c9e2b44f2e8d577c35 100644
> --- a/drivers/hwmon/pmbus/Makefile
> +++ b/drivers/hwmon/pmbus/Makefile
> @@ -36,6 +36,7 @@ obj-$(CONFIG_SENSORS_MAX16601) += max16601.o
> obj-$(CONFIG_SENSORS_MAX17616) += max17616.o
> obj-$(CONFIG_SENSORS_MAX20730) += max20730.o
> obj-$(CONFIG_SENSORS_MAX20751) += max20751.o
> +obj-$(CONFIG_SENSORS_MAX20830) += max20830.o
> obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
> obj-$(CONFIG_SENSORS_MAX34440) += max34440.o
> obj-$(CONFIG_SENSORS_MAX8688) += max8688.o
> diff --git a/drivers/hwmon/pmbus/max20830.c b/drivers/hwmon/pmbus/max20830.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..21ea8b59150cb0564f1776ee08131bad7fdef003
> --- /dev/null
> +++ b/drivers/hwmon/pmbus/max20830.c
> @@ -0,0 +1,86 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Hardware monitoring driver for Analog Devices MAX20830
> + *
> + * Copyright (C) 2026 Analog Devices, Inc.
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include "pmbus.h"
> +
> +static struct pmbus_driver_info max20830_info = {
> + .pages = 1,
> + .format[PSC_VOLTAGE_IN] = linear,
> + .format[PSC_VOLTAGE_OUT] = linear,
> + .format[PSC_CURRENT_OUT] = linear,
> + .format[PSC_TEMPERATURE] = linear,
> + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
> + PMBUS_HAVE_TEMP |
> + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT |
> + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
> +};
> +
> +static int max20830_probe(struct i2c_client *client)
> +{
> + u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
AI seems a bit paranoid but maybe to be on the safe side just initialize
the above buffer.
> + u8 len;
> + int ret;
> +
> + if (!i2c_check_functionality(client->adapter,
> + I2C_FUNC_SMBUS_READ_I2C_BLOCK))
> + return -ENODEV;
> +
> + /*
> + * Use i2c_smbus_read_i2c_block_data() instead of
> + * i2c_smbus_read_block_data() to support I2C controllers
> + * which do not support SMBus block reads.
> + */
> + ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID,
> + I2C_SMBUS_BLOCK_MAX, buf);
> + if (ret < 0)
> + return dev_err_probe(&client->dev, ret,
> + "Failed to read IC_DEVICE_ID\n");
> +
> + /* First byte is the block length. */
> + len = buf[0];
> + if (len != 9)
> + return dev_err_probe(&client->dev, -ENODEV,
> + "Unexpected IC_DEVICE_ID response\n");
> +
> + buf[len] = '\0';
It looks like it has a point in the above though.
- Nuno Sá
^ permalink raw reply
* [PATCH v5 7/7] bus: mhi: Expose DDR training data via controller sysfs
From: Kishore Batta @ 2026-04-16 14:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Jeff Hugo, Carl Vanderlip,
Oded Gabbay, Manivannan Sadhasivam
Cc: linux-doc, linux-kernel, linux-arm-msm, dri-devel, mhi,
Kishore Batta
In-Reply-To: <20260416-sahara_protocol_new_v2-v5-0-6aebf005e4ba@oss.qualcomm.com>
DDR training data captured during Sahara command mode needs to be
accessible to userspace so it can be persisted and reused on subsequent
boots. Currently, the training data is stored internally in the driver
but has no external visibility once the Sahara channel is torn down.
Expose the captured DDR training data via a read-only binary sysfs
attribute on the MHI controller device:
/sys/bus/mhi/devices/<mhi_cntrl>/ddr_training_data
The sysfs read callback serves data directly from controller scoped storage
and protects access with the controller training data lock. The attribute
lifetime is tied to the controller device via devres, allowing the data to
remain readable after Sahara channel teardown and ensuring automatic
cleanup when controller device is removed.
Userspace flow:
1. For each controller device, userspace reads the ddr_training_data sysfs
attribute.
2. If the read returns non-zero data, userspace persists it using a
serial specific filename (for example, mdmddr_0x<serial_no>.mbn).
3. On subsequent boots, the Sahara driver attempts to load this serial
specific DDR training image before falling back to the default
training image, restoring DDR calibration data and avoiding retraining.
Add ABI documentation for the DDR training data sysfs attribute exposed by
Sahara MHI driver.
Signed-off-by: Kishore Batta <kishore.batta@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-mhi-ddr_training_data | 19 ++++++
drivers/bus/mhi/host/clients/sahara/sahara.c | 69 ++++++++++++++++++++++
2 files changed, 88 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-bus-mhi-ddr_training_data b/Documentation/ABI/testing/sysfs-bus-mhi-ddr_training_data
new file mode 100644
index 0000000000000000000000000000000000000000..810b487b5a5fdba133d81255f9879844e3938a10
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-mhi-ddr_training_data
@@ -0,0 +1,19 @@
+What: /sys/bus/mhi/devices/<mhi-cntrl>/ddr_training_data
+
+Date: March 2026
+
+Contact: Kishore Batta <kishore.batta@oss.qualcomm.com>
+
+Description: Contains the DDR training data for the Qualcomm device
+ connected. MHI driver populates different controller
+ nodes for each device. The DDR training data is exposed
+ to userspace to read and save the training data file to
+ the filesystem. In the subsequent boot up of the device,
+ the training data is restored from host to device
+ optimizing the boot up time of the device.
+
+Usage: Example for reading DDR training data:
+ cat /sys/bus/mhi/devices/mhi0/ddr_training_data
+
+Permissions: The file permissions are set to 0444 allowing read
+ access.
diff --git a/drivers/bus/mhi/host/clients/sahara/sahara.c b/drivers/bus/mhi/host/clients/sahara/sahara.c
index 07bc743aa061dd2fa85638067d494562152474e3..fef5dc1d8884133397d204f23361584fd1d9b075 100644
--- a/drivers/bus/mhi/host/clients/sahara/sahara.c
+++ b/drivers/bus/mhi/host/clients/sahara/sahara.c
@@ -273,6 +273,73 @@ static struct sahara_cntrl_training_data *sahara_cntrl_training_get(struct devic
return ct;
}
+static ssize_t ddr_training_data_read(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *attr, char *buf,
+ loff_t offset, size_t count)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct sahara_cntrl_training_data *ct;
+ size_t available;
+
+ ct = sahara_cntrl_training_get(dev);
+ if (!ct)
+ return -ENODEV;
+
+ mutex_lock(&ct->lock);
+
+ /* No data yet or offset past end */
+ if (!ct->data || offset >= ct->size) {
+ mutex_unlock(&ct->lock);
+ return 0;
+ }
+
+ available = ct->size - offset;
+ count = min(count, available);
+ memcpy(buf, (u8 *)ct->data + offset, count);
+
+ mutex_unlock(&ct->lock);
+
+ return count;
+}
+
+static const struct bin_attribute ddr_training_data_attr = {
+ .attr = {
+ .name = "ddr_training_data",
+ .mode = 0444,
+ },
+ .read = ddr_training_data_read,
+};
+
+static void sahara_sysfs_devres_release(struct device *dev, void *res)
+{
+ device_remove_bin_file(dev, &ddr_training_data_attr);
+}
+
+static void sahara_sysfs_create(struct mhi_device *mhi_dev)
+{
+ struct device *dev = &mhi_dev->mhi_cntrl->mhi_dev->dev;
+ void *cookie;
+ int ret;
+
+ if (devres_find(dev, sahara_sysfs_devres_release, NULL, NULL))
+ return;
+
+ ret = device_create_bin_file(dev, &ddr_training_data_attr);
+ if (ret) {
+ dev_warn(&mhi_dev->dev,
+ "Failed to create DDR training sysfs node (%d)\n", ret);
+ return;
+ }
+
+ cookie = devres_alloc(sahara_sysfs_devres_release, 1, GFP_KERNEL);
+ if (!cookie) {
+ device_remove_bin_file(dev, &ddr_training_data_attr);
+ return;
+ }
+
+ devres_add(dev, cookie);
+}
+
static int sahara_find_image(struct sahara_context *context, u32 image_id)
{
char *fw_path;
@@ -1131,6 +1198,8 @@ static int sahara_mhi_probe(struct mhi_device *mhi_dev, const struct mhi_device_
return ret;
}
+ sahara_sysfs_create(mhi_dev);
+
return 0;
}
--
2.34.1
^ permalink raw reply related
* [PATCH v5 6/7] bus: mhi: Capture DDR training data via command mode
From: Kishore Batta @ 2026-04-16 14:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Jeff Hugo, Carl Vanderlip,
Oded Gabbay, Manivannan Sadhasivam
Cc: linux-doc, linux-kernel, linux-arm-msm, dri-devel, mhi,
Kishore Batta
In-Reply-To: <20260416-sahara_protocol_new_v2-v5-0-6aebf005e4ba@oss.qualcomm.com>
During early boot, devices may perform DDR training and produce training
data that can be reused on subsequent boots to reduce initialization
time. The Sahara protocol provides a command mode flow to transfer this
training data to the host, but the driver currently does not handle
command mode and drops the training payload.
Add Sahara command mode support to retrieve DDR training data from the
device. When the device enters command mode and sends CMD_READY, query
the support command list(ID 8) and request DDR training data(ID 9) using
EXECUTE and EXECUTE_DATA as defined by protocol. Allocate receive buffers
based on the reported response size and copy the raw payload directly from
the MHI DL completion callback.
Store the captured training data in controller-scoped memory using devres,
so it remains available after Sahara channel teardown. Also distinguish
raw payload completion from control packets in the DL callback, avoiding
misinterpretation of training data as protocol messages, and requeue
the RX buffer after switching back to IMAGE_TX_PENDING to allow the
boot flow to continue.
Signed-off-by: Kishore Batta <kishore.batta@oss.qualcomm.com>
---
drivers/bus/mhi/host/clients/sahara/sahara.c | 326 ++++++++++++++++++++++++++-
1 file changed, 319 insertions(+), 7 deletions(-)
diff --git a/drivers/bus/mhi/host/clients/sahara/sahara.c b/drivers/bus/mhi/host/clients/sahara/sahara.c
index b5ca6353540dc3815db6539e7424afdb749fd3f6..07bc743aa061dd2fa85638067d494562152474e3 100644
--- a/drivers/bus/mhi/host/clients/sahara/sahara.c
+++ b/drivers/bus/mhi/host/clients/sahara/sahara.c
@@ -5,11 +5,14 @@
*/
#include <linux/devcoredump.h>
+#include <linux/device.h>
+#include <linux/device/devres.h>
#include <linux/firmware.h>
#include <linux/limits.h>
#include <linux/mhi.h>
#include <linux/minmax.h>
#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
#include <linux/overflow.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
@@ -59,7 +62,15 @@
#define SAHARA_RESET_LENGTH 0x8
#define SAHARA_MEM_DEBUG64_LENGTH 0x18
#define SAHARA_MEM_READ64_LENGTH 0x18
+#define SAHARA_COMMAND_READY_LENGTH 0x8
+#define SAHARA_COMMAND_EXEC_RESP_LENGTH 0x10
+#define SAHARA_COMMAND_EXECUTE_LENGTH 0xc
+#define SAHARA_COMMAND_EXEC_DATA_LENGTH 0xc
+#define SAHARA_SWITCH_MODE_LENGTH 0xc
+#define SAHARA_EXEC_CMD_GET_COMMAND_ID_LIST 0x8
+#define SAHARA_EXEC_CMD_GET_TRAINING_DATA 0x9
#define SAHARA_DDR_TRAINING_IMG_ID 34
+#define SAHARA_NUM_CMD_BUF SAHARA_NUM_TX_BUF
struct sahara_packet {
__le32 cmd;
@@ -95,6 +106,19 @@ struct sahara_packet {
__le64 memory_address;
__le64 memory_length;
} memory_read64;
+ struct {
+ __le32 client_command;
+ } command_execute;
+ struct {
+ __le32 client_command;
+ __le32 response_length;
+ } command_execute_resp;
+ struct {
+ __le32 client_command;
+ } command_exec_data;
+ struct {
+ __le32 mode;
+ } mode_switch;
};
};
@@ -161,6 +185,7 @@ struct sahara_context {
struct work_struct fw_work;
struct work_struct dump_work;
struct work_struct read_data_work;
+ struct work_struct cmd_work;
struct mhi_device *mhi_dev;
const char * const *image_table;
u32 table_size;
@@ -181,6 +206,24 @@ struct sahara_context {
bool is_mem_dump_mode;
bool non_streaming;
const char *fw_folder;
+ bool is_cmd_mode;
+ bool receiving_training_data;
+ size_t training_size;
+ size_t training_rcvd;
+ u32 training_nbuf;
+ char *cmd_buff[SAHARA_NUM_CMD_BUF];
+};
+
+/*
+ * Controller-scoped training data store (per MHI controller device).
+ * Stored as devres resource on mhi_dev->mhi_cntrl->mhi_dev->dev.
+ */
+struct sahara_cntrl_training_data {
+ struct mutex lock; /* Protects data, size, copied and receiving */
+ void *data;
+ size_t size;
+ size_t copied;
+ bool receiving;
};
static bool is_streaming(struct sahara_context *context)
@@ -188,6 +231,48 @@ static bool is_streaming(struct sahara_context *context)
return !context->non_streaming;
}
+static void sahara_cntrl_training_release(struct device *dev, void *res)
+{
+ struct sahara_cntrl_training_data *ct = res;
+
+ mutex_lock(&ct->lock);
+ kfree(ct->data);
+ ct->data = NULL;
+ ct->size = 0;
+ ct->copied = 0;
+ ct->receiving = false;
+ mutex_unlock(&ct->lock);
+}
+
+static int sahara_cntrl_training_match(struct device *dev, void *res, void *match_data)
+{
+ /* Exactly one instance per controller */
+ return 1;
+}
+
+static struct sahara_cntrl_training_data *sahara_cntrl_training_get(struct device *dev)
+{
+ struct sahara_cntrl_training_data *ct;
+
+ ct = devres_find(dev, sahara_cntrl_training_release,
+ sahara_cntrl_training_match, NULL);
+ if (ct)
+ return ct;
+
+ ct = devres_alloc(sahara_cntrl_training_release, sizeof(*ct), GFP_KERNEL);
+ if (!ct)
+ return NULL;
+
+ mutex_init(&ct->lock);
+ ct->data = NULL;
+ ct->size = 0;
+ ct->copied = 0;
+ ct->receiving = false;
+
+ devres_add(dev, ct);
+ return ct;
+}
+
static int sahara_find_image(struct sahara_context *context, u32 image_id)
{
char *fw_path;
@@ -282,6 +367,11 @@ static void sahara_send_reset(struct sahara_context *context)
context->is_mem_dump_mode = false;
context->read_data_offset = 0;
context->read_data_length = 0;
+ context->is_cmd_mode = false;
+ context->receiving_training_data = false;
+ context->training_size = 0;
+ context->training_rcvd = 0;
+ context->training_nbuf = 0;
context->tx[0]->cmd = cpu_to_le32(SAHARA_RESET_CMD);
context->tx[0]->length = cpu_to_le32(SAHARA_RESET_LENGTH);
@@ -317,7 +407,8 @@ static void sahara_hello(struct sahara_context *context)
if (le32_to_cpu(context->rx->hello.mode) != SAHARA_MODE_IMAGE_TX_PENDING &&
le32_to_cpu(context->rx->hello.mode) != SAHARA_MODE_IMAGE_TX_COMPLETE &&
- le32_to_cpu(context->rx->hello.mode) != SAHARA_MODE_MEMORY_DEBUG) {
+ le32_to_cpu(context->rx->hello.mode) != SAHARA_MODE_MEMORY_DEBUG &&
+ le32_to_cpu(context->rx->hello.mode) != SAHARA_MODE_COMMAND) {
dev_err(&context->mhi_dev->dev, "Unsupported hello packet - mode %d\n",
le32_to_cpu(context->rx->hello.mode));
return;
@@ -336,6 +427,153 @@ static void sahara_hello(struct sahara_context *context)
dev_err(&context->mhi_dev->dev, "Unable to send hello response %d\n", ret);
}
+static void sahara_switch_mode_to_img_tx(struct sahara_context *context)
+{
+ int ret;
+
+ context->tx[0]->cmd = cpu_to_le32(SAHARA_SWITCH_MODE_CMD);
+ context->tx[0]->length = cpu_to_le32(SAHARA_SWITCH_MODE_LENGTH);
+ context->tx[0]->mode_switch.mode = cpu_to_le32(SAHARA_MODE_IMAGE_TX_PENDING);
+
+ ret = mhi_queue_buf(context->mhi_dev, DMA_TO_DEVICE, context->tx[0],
+ SAHARA_SWITCH_MODE_LENGTH, MHI_EOT);
+
+ if (ret)
+ dev_err(&context->mhi_dev->dev, "Unable to send mode switch %d\n", ret);
+}
+
+static void sahara_command_execute(struct sahara_context *context, u32 client_command)
+{
+ int ret;
+
+ context->tx[0]->cmd = cpu_to_le32(SAHARA_EXECUTE_CMD);
+ context->tx[0]->length = cpu_to_le32(SAHARA_COMMAND_EXECUTE_LENGTH);
+ context->tx[0]->command_execute.client_command = cpu_to_le32(client_command);
+
+ ret = mhi_queue_buf(context->mhi_dev, DMA_TO_DEVICE, context->tx[0],
+ SAHARA_COMMAND_EXECUTE_LENGTH, MHI_EOT);
+ if (ret)
+ dev_err(&context->mhi_dev->dev, "Unable to send command execute %d\n", ret);
+}
+
+static void sahara_command_execute_data(struct sahara_context *context, u32 client_command)
+{
+ int ret;
+
+ context->tx[0]->cmd = cpu_to_le32(SAHARA_EXECUTE_DATA_CMD);
+ context->tx[0]->length = cpu_to_le32(SAHARA_COMMAND_EXEC_DATA_LENGTH);
+ context->tx[0]->command_exec_data.client_command = cpu_to_le32(client_command);
+
+ ret = mhi_queue_buf(context->mhi_dev, DMA_TO_DEVICE, context->tx[0],
+ SAHARA_COMMAND_EXEC_DATA_LENGTH, MHI_EOT);
+ if (ret)
+ dev_err(&context->mhi_dev->dev, "Unable to send execute data %d\n", ret);
+}
+
+static void sahara_command_ready(struct sahara_context *context)
+{
+ if (le32_to_cpu(context->rx->length) != SAHARA_COMMAND_READY_LENGTH) {
+ dev_err(&context->mhi_dev->dev,
+ "Malformed command ready packet - length %u\n",
+ le32_to_cpu(context->rx->length));
+ return;
+ }
+
+ context->is_cmd_mode = true;
+ context->receiving_training_data = false;
+
+ sahara_command_execute(context, SAHARA_EXEC_CMD_GET_COMMAND_ID_LIST);
+}
+
+static void sahara_command_execute_resp(struct sahara_context *context)
+{
+ struct device *dev = &context->mhi_dev->mhi_cntrl->mhi_dev->dev;
+ struct sahara_cntrl_training_data *ct;
+ u32 client_cmd, resp_len;
+ int ret;
+ u64 remaining;
+ u32 i;
+
+ if (le32_to_cpu(context->rx->length) != SAHARA_COMMAND_EXEC_RESP_LENGTH ||
+ le32_to_cpu(context->rx->command_execute_resp.response_length) < 0) {
+ dev_err(&context->mhi_dev->dev,
+ "Malformed command execute resp packet - length %d\n",
+ le32_to_cpu(context->rx->length));
+ return;
+ }
+
+ client_cmd = le32_to_cpu(context->rx->command_execute_resp.client_command);
+ resp_len = le32_to_cpu(context->rx->command_execute_resp.response_length);
+
+ sahara_command_execute_data(context, client_cmd);
+
+ if (client_cmd == SAHARA_EXEC_CMD_GET_COMMAND_ID_LIST) {
+ sahara_command_execute(context, SAHARA_EXEC_CMD_GET_TRAINING_DATA);
+ return;
+ }
+
+ if (client_cmd != SAHARA_EXEC_CMD_GET_TRAINING_DATA)
+ return;
+
+ ct = sahara_cntrl_training_get(dev);
+ if (!ct) {
+ context->is_cmd_mode = false;
+ sahara_switch_mode_to_img_tx(context);
+ return;
+ }
+
+ mutex_lock(&ct->lock);
+ kfree(ct->data);
+ ct->data = kzalloc(resp_len, GFP_KERNEL);
+ ct->size = resp_len;
+ ct->copied = 0;
+ ct->receiving = true;
+ mutex_unlock(&ct->lock);
+
+ if (!ct->data) {
+ context->is_cmd_mode = false;
+ sahara_switch_mode_to_img_tx(context);
+ return;
+ }
+
+ context->training_size = resp_len;
+ context->training_rcvd = 0;
+ context->receiving_training_data = true;
+
+ remaining = resp_len;
+ for (i = 0; i < SAHARA_NUM_CMD_BUF && remaining; i++) {
+ size_t pkt = min_t(size_t, remaining, SAHARA_PACKET_MAX_SIZE);
+
+ ret = mhi_queue_buf(context->mhi_dev, DMA_FROM_DEVICE,
+ context->cmd_buff[i], pkt,
+ (remaining <= pkt) ? MHI_EOT : MHI_CHAIN);
+ if (ret)
+ break;
+
+ remaining -= pkt;
+ }
+
+ context->training_nbuf = i;
+}
+
+static void sahara_command_processing(struct work_struct *work)
+{
+ struct sahara_context *context = container_of(work, struct sahara_context, cmd_work);
+ int ret;
+
+ if (le32_to_cpu(context->rx->cmd) == SAHARA_EXECUTE_RESP_CMD)
+ sahara_command_execute_resp(context);
+
+ if (!context->receiving_training_data) {
+ ret = mhi_queue_buf(context->mhi_dev, DMA_FROM_DEVICE,
+ context->rx, SAHARA_PACKET_MAX_SIZE, MHI_EOT);
+
+ if (ret)
+ dev_err(&context->mhi_dev->dev,
+ "Unable to requeue rx buf %d\n", ret);
+ }
+}
+
static int read_data_helper(struct sahara_context *context, int buf_index)
{
enum mhi_flags mhi_flag;
@@ -562,6 +800,9 @@ static void sahara_processing(struct work_struct *work)
case SAHARA_MEM_DEBUG64_CMD:
sahara_memory_debug64(context);
break;
+ case SAHARA_CMD_READY_CMD:
+ sahara_command_ready(context);
+ break;
default:
dev_err(&context->mhi_dev->dev, "Unknown command %d\n",
le32_to_cpu(context->rx->cmd));
@@ -862,6 +1103,20 @@ static int sahara_mhi_probe(struct mhi_device *mhi_dev, const struct mhi_device_
INIT_WORK(&context->fw_work, sahara_processing);
INIT_WORK(&context->dump_work, sahara_dump_processing);
INIT_WORK(&context->read_data_work, sahara_read_data_processing);
+ INIT_WORK(&context->cmd_work, sahara_command_processing);
+
+ for (i = 0; i < SAHARA_NUM_CMD_BUF; i++) {
+ context->cmd_buff[i] = devm_kzalloc(&mhi_dev->dev,
+ SAHARA_PACKET_MAX_SIZE, GFP_KERNEL);
+ if (!context->cmd_buff[i])
+ return -ENOMEM;
+ }
+
+ context->is_cmd_mode = false;
+ context->receiving_training_data = false;
+ context->training_size = 0;
+ context->training_rcvd = 0;
+ context->training_nbuf = 0;
context->active_image_id = SAHARA_IMAGE_ID_NONE;
dev_set_drvdata(&mhi_dev->dev, context);
@@ -885,6 +1140,7 @@ static void sahara_mhi_remove(struct mhi_device *mhi_dev)
cancel_work_sync(&context->fw_work);
cancel_work_sync(&context->dump_work);
+ cancel_work_sync(&context->cmd_work);
vfree(context->mem_dump);
sahara_release_image(context);
mhi_unprepare_from_transfer(mhi_dev);
@@ -901,15 +1157,71 @@ static void sahara_mhi_ul_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result
static void sahara_mhi_dl_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
struct sahara_context *context = dev_get_drvdata(&mhi_dev->dev);
+ struct sahara_cntrl_training_data *ct;
+ struct device *dev;
+ size_t copy;
+ int ret;
+ u32 i;
+
+ if (mhi_result->transaction_status)
+ return;
+
+ /*
+ * Raw training payload completions arrive for cmd_buff[] buffers.
+ * Do not schedule cmd_work for those.
+ */
+ if (context->is_cmd_mode && context->receiving_training_data &&
+ mhi_result->buf_addr != context->rx) {
+ dev = &context->mhi_dev->mhi_cntrl->mhi_dev->dev;
+ ct = sahara_cntrl_training_get(dev);
+ if (!ct)
+ return;
- if (!mhi_result->transaction_status) {
- context->rx_size = mhi_result->bytes_xferd;
- if (context->is_mem_dump_mode)
- schedule_work(&context->dump_work);
- else
- schedule_work(&context->fw_work);
+ for (i = 0; i < context->training_nbuf; i++) {
+ if (mhi_result->buf_addr == context->cmd_buff[i]) {
+ mutex_lock(&ct->lock);
+ copy = min_t(size_t, mhi_result->bytes_xferd,
+ ct->size - ct->copied);
+ memcpy((u8 *)ct->data + ct->copied,
+ mhi_result->buf_addr, copy);
+ ct->copied += copy;
+ mutex_unlock(&ct->lock);
+
+ context->training_rcvd += copy;
+
+ if (context->training_rcvd >= context->training_size) {
+ mutex_lock(&ct->lock);
+ ct->receiving = false;
+ mutex_unlock(&ct->lock);
+
+ context->receiving_training_data = false;
+ context->is_cmd_mode = false;
+
+ sahara_switch_mode_to_img_tx(context);
+ ret = mhi_queue_buf(context->mhi_dev,
+ DMA_FROM_DEVICE,
+ context->rx,
+ SAHARA_PACKET_MAX_SIZE,
+ MHI_EOT);
+ if (ret)
+ dev_err(&context->mhi_dev->dev,
+ "Unable to requeue rx buf %d\n", ret);
+ }
+ return;
+ }
+ }
+ return;
}
+ /* Normal Rx completion */
+ context->rx_size = mhi_result->bytes_xferd;
+ if (context->is_mem_dump_mode)
+ schedule_work(&context->dump_work);
+ else if (context->is_cmd_mode)
+ schedule_work(&context->cmd_work);
+ else
+ schedule_work(&context->fw_work);
+
}
static const struct mhi_device_id sahara_mhi_match_table[] = {
--
2.34.1
^ permalink raw reply related
* [PATCH v5 5/7] bus: mhi: Load DDR training data using device serial number
From: Kishore Batta @ 2026-04-16 14:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Jeff Hugo, Carl Vanderlip,
Oded Gabbay, Manivannan Sadhasivam
Cc: linux-doc, linux-kernel, linux-arm-msm, dri-devel, mhi,
Kishore Batta
In-Reply-To: <20260416-sahara_protocol_new_v2-v5-0-6aebf005e4ba@oss.qualcomm.com>
Devices may provide device specific DDR training data that can be reused
across boot to avoid retraining and reduce boot time. The Sahara driver
currently always falls back to the default DDR training image, even when
serial specific training data is available.
Extend the firmware loading logic for the DDR training image to first
attempt loading a per-device image dervied from the device serial number.
If the serial-specific image is not present, fall back to the existing
default image, preserving current behavior.
This allows reuse of previously generated DDR training data when available,
while keeping the existing training flow unchanged for devices without
saved data or for all other firmware images.
Signed-off-by: Kishore Batta <kishore.batta@oss.qualcomm.com>
---
drivers/bus/mhi/host/clients/sahara/sahara.c | 25 ++++++++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/drivers/bus/mhi/host/clients/sahara/sahara.c b/drivers/bus/mhi/host/clients/sahara/sahara.c
index 9adbd84859073d8024ba2a5fcfa33897439d6759..b5ca6353540dc3815db6539e7424afdb749fd3f6 100644
--- a/drivers/bus/mhi/host/clients/sahara/sahara.c
+++ b/drivers/bus/mhi/host/clients/sahara/sahara.c
@@ -59,6 +59,7 @@
#define SAHARA_RESET_LENGTH 0x8
#define SAHARA_MEM_DEBUG64_LENGTH 0x18
#define SAHARA_MEM_READ64_LENGTH 0x18
+#define SAHARA_DDR_TRAINING_IMG_ID 34
struct sahara_packet {
__le32 cmd;
@@ -226,6 +227,27 @@ static int sahara_find_image(struct sahara_context *context, u32 image_id)
return 0;
}
+ /* DDR training special case: Try per-serial number file first */
+ if (image_id == SAHARA_DDR_TRAINING_IMG_ID && context->fw_folder) {
+ u32 serial_num = context->mhi_dev->mhi_cntrl->serial_number;
+
+ fw_path = kasprintf(GFP_KERNEL,
+ "qcom/%s/mdmddr_0x%x.mbn",
+ context->fw_folder, serial_num);
+ if (!fw_path)
+ return -ENOMEM;
+
+ ret = firmware_request_nowarn(&context->firmware,
+ fw_path,
+ &context->mhi_dev->dev);
+ kfree(fw_path);
+
+ if (!ret) {
+ context->active_image_id = image_id;
+ return 0;
+ }
+ }
+
/*
* This image might be optional. The device may continue without it.
* Only the device knows. Suppress error messages that could suggest an
@@ -235,7 +257,8 @@ static int sahara_find_image(struct sahara_context *context, u32 image_id)
context->image_table[image_id],
&context->mhi_dev->dev);
if (ret) {
- dev_dbg(&context->mhi_dev->dev, "request for image id %d / file %s failed %d\n",
+ dev_dbg(&context->mhi_dev->dev,
+ "request for image id %d / file %s failed %d\n",
image_id, context->image_table[image_id], ret);
return ret;
}
--
2.34.1
^ permalink raw reply related
* [PATCH v5 4/7] bus: mhi: Add QDU100 Sahara variant and firmware fallback
From: Kishore Batta @ 2026-04-16 14:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Jeff Hugo, Carl Vanderlip,
Oded Gabbay, Manivannan Sadhasivam
Cc: linux-doc, linux-kernel, linux-arm-msm, dri-devel, mhi,
Kishore Batta
In-Reply-To: <20260416-sahara_protocol_new_v2-v5-0-6aebf005e4ba@oss.qualcomm.com>
The Sahara driver currently selects a firmware image table based on the
attached device, but it does not recognize QDU100 devices that expose the
protocol on the SAHARA MHI channel. As a result, the host cannot associate
QDU100 devices with the correct firmware namespace during image transfer.
Extend the probe time variant selection to match the SAHARA MHI channel and
associate it with the QDU100 firmware folder. Add a firmware lookup
fallback for cases where an image does not have an explicit entry in the
device's firmware table. This allows required images to be provisioned by
the platform.
This change only affects devices matched on the SAHARA MHI channel and
does not change behavior for existing AIC100 and AIC200 devices.
Signed-off-by: Kishore Batta <kishore.batta@oss.qualcomm.com>
---
drivers/bus/mhi/host/clients/sahara/sahara.c | 27 +++++++++++++++--
drivers/bus/mhi/host/pci_generic.c | 45 ++++++++++++++++++++++++++++
2 files changed, 70 insertions(+), 2 deletions(-)
diff --git a/drivers/bus/mhi/host/clients/sahara/sahara.c b/drivers/bus/mhi/host/clients/sahara/sahara.c
index e339c67e236af271645ca81cc517efd9eead87e4..9adbd84859073d8024ba2a5fcfa33897439d6759 100644
--- a/drivers/bus/mhi/host/clients/sahara/sahara.c
+++ b/drivers/bus/mhi/host/clients/sahara/sahara.c
@@ -189,6 +189,7 @@ static bool is_streaming(struct sahara_context *context)
static int sahara_find_image(struct sahara_context *context, u32 image_id)
{
+ char *fw_path;
int ret;
if (image_id == context->active_image_id)
@@ -201,8 +202,28 @@ static int sahara_find_image(struct sahara_context *context, u32 image_id)
}
if (image_id >= context->table_size || !context->image_table[image_id]) {
- dev_err(&context->mhi_dev->dev, "request for unknown image: %d\n", image_id);
- return -EINVAL;
+ if (!context->fw_folder) {
+ dev_err(&context->mhi_dev->dev,
+ "Request for unknown image: %u (no fw folder)\n", image_id);
+ return -EINVAL;
+ }
+
+ fw_path = kasprintf(GFP_KERNEL, "qcom/%s/%u",
+ context->fw_folder, image_id);
+ if (!fw_path)
+ return -ENOMEM;
+
+ ret = firmware_request_nowarn(&context->firmware,
+ fw_path,
+ &context->mhi_dev->dev);
+ kfree(fw_path);
+ if (ret) {
+ dev_err(&context->mhi_dev->dev,
+ "request for unknown image: %d\n", image_id);
+ return -EINVAL;
+ }
+ context->active_image_id = image_id;
+ return 0;
}
/*
@@ -870,8 +891,10 @@ static void sahara_mhi_dl_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result
static const struct mhi_device_id sahara_mhi_match_table[] = {
{ .chan = "QAIC_SAHARA", },
+ { .chan = "SAHARA"},
{},
};
+MODULE_DEVICE_TABLE(mhi, sahara_mhi_match_table);
static struct mhi_driver sahara_mhi_driver = {
.id_table = sahara_mhi_match_table,
diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c
index 391ab146f501c6ce1c81f6138f7c491a49c2f264..82e41632afc555a53dec3d8395558ae039b33bbd 100644
--- a/drivers/bus/mhi/host/pci_generic.c
+++ b/drivers/bus/mhi/host/pci_generic.c
@@ -300,6 +300,43 @@ static const struct mhi_pci_dev_info mhi_qcom_qdu100_info = {
.reset_on_remove = true,
};
+static const char * const qdu100_image_table[] = {
+ [5] = "qcom/qdu100/uefi.elf",
+ [8] = "qcom/qdu100/qdsp6sw.mbn",
+ [16] = "qcom/qdu100/efs1.bin",
+ [17] = "qcom/qdu100/efs2.bin",
+ [20] = "qcom/qdu100/efs3.bin",
+ [23] = "qcom/qdu100/aop.mbn",
+ [25] = "qcom/qdu100/tz.mbn",
+ [29] = "qcom/qdu100/zeros_1sector.bin",
+ [33] = "qcom/qdu100/hypvm.mbn",
+ [34] = "qcom/qdu100/mdmddr.mbn",
+ [36] = "qcom/qdu100/multi_image_qti.mbn",
+ [37] = "qcom/qdu100/multi_image.mbn",
+ [38] = "qcom/qdu100/xbl_config.elf",
+ [39] = "qcom/qdu100/abl_userdebug.elf",
+ [40] = "qcom/qdu100/zeros_1sector.bin",
+ [41] = "qcom/qdu100/devcfg.mbn",
+ [42] = "qcom/qdu100/zeros_1sector.bin",
+ [45] = "qcom/qdu100/tools_l.elf",
+ [46] = "qcom/qdu100/Quantum.elf",
+ [47] = "qcom/qdu100/quest.elf",
+ [48] = "qcom/qdu100/xbl_ramdump.elf",
+ [49] = "qcom/qdu100/shrm.elf",
+ [50] = "qcom/qdu100/cpucp.elf",
+ [51] = "qcom/qdu100/aop_devcfg.mbn",
+ [52] = "qcom/qdu100/fw_csm_gsi_3.0.elf",
+ [53] = "qcom/qdu100/qdsp6sw_dtbs.elf",
+ [54] = "qcom/qdu100/qupv3fw.elf",
+};
+
+static const struct mhi_sahara_fw_table qdu100_sahara_fw = {
+ .image_table = qdu100_image_table,
+ .table_size = ARRAY_SIZE(qdu100_image_table),
+ .fw_folder = "qdu100",
+ .non_streaming = false,
+};
+
static const struct mhi_channel_config mhi_qcom_sa8775p_channels[] = {
MHI_CHANNEL_CONFIG_UL(46, "IP_SW0", 2048, 1),
MHI_CHANNEL_CONFIG_DL(47, "IP_SW0", 2048, 2),
@@ -1399,6 +1436,14 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
pci_set_drvdata(pdev, mhi_pdev);
+ /*
+ * Provide Sahara firmware mapping. Sahara consumes it via
+ * mhi_dev->mhi_cntrl->sahara_fw at probe time.
+ */
+ if (info == &mhi_qcom_qdu100_info ||
+ (info->name && !strcmp(info->name, "qcom-qdu100")))
+ mhi_cntrl->sahara_fw = &qdu100_sahara_fw;
+
/* Have stored pci confspace at hand for restore in sudden PCI error.
* cache the state locally and discard the PCI core one.
*/
--
2.34.1
^ permalink raw reply related
* [PATCH v5 3/7] bus: mhi: Centralize Sahara firmware image table selection at probe time
From: Kishore Batta @ 2026-04-16 14:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Jeff Hugo, Carl Vanderlip,
Oded Gabbay, Manivannan Sadhasivam
Cc: linux-doc, linux-kernel, linux-arm-msm, dri-devel, mhi,
Kishore Batta
In-Reply-To: <20260416-sahara_protocol_new_v2-v5-0-6aebf005e4ba@oss.qualcomm.com>
The Sahara driver currently selects firmware image tables using scattered,
device specific conditionals in the probe path. This makes the logic harder
to follow, harder to extend for new devices, and spreads device knowledge
across multiple code paths.
Refactor firmware image table selection into a single, explicit probe time
mechanism by introducing a controller provided firmware mapping table that
captures device matching, Sahara image tables, firmware folder names, and
streaming behaviour in one place.
This centralizes device specific decisions in the controller driver,
simplifies the Sahara probe logic, and removes ad-hoc conditionals while
preserving existing behavior for all supported AIC devices. This is in
preparation for adding QDU100 support.
Signed-off-by: Kishore Batta <kishore.batta@oss.qualcomm.com>
---
drivers/accel/qaic/mhi_controller.c | 61 ++++++++++++++++++++++++++++
drivers/bus/mhi/host/clients/sahara/sahara.c | 60 +++++----------------------
include/linux/mhi.h | 17 ++++++++
3 files changed, 88 insertions(+), 50 deletions(-)
diff --git a/drivers/accel/qaic/mhi_controller.c b/drivers/accel/qaic/mhi_controller.c
index 4d787f77ce419fcd2b250f9cabaec9c26f2da8dc..1f9ef871421b976c35cfad59aed715da96c1813b 100644
--- a/drivers/accel/qaic/mhi_controller.c
+++ b/drivers/accel/qaic/mhi_controller.c
@@ -20,6 +20,62 @@ static unsigned int mhi_timeout_ms = 2000; /* 2 sec default */
module_param(mhi_timeout_ms, uint, 0600);
MODULE_PARM_DESC(mhi_timeout_ms, "MHI controller timeout value");
+static const char * const aic100_image_table[] = {
+ [1] = "qcom/aic100/fw1.bin",
+ [2] = "qcom/aic100/fw2.bin",
+ [4] = "qcom/aic100/fw4.bin",
+ [5] = "qcom/aic100/fw5.bin",
+ [6] = "qcom/aic100/fw6.bin",
+ [8] = "qcom/aic100/fw8.bin",
+ [9] = "qcom/aic100/fw9.bin",
+ [10] = "qcom/aic100/fw10.bin",
+};
+
+static const char * const aic200_image_table[] = {
+ [5] = "qcom/aic200/uefi.elf",
+ [12] = "qcom/aic200/aic200-nsp.bin",
+ [23] = "qcom/aic200/aop.mbn",
+ [32] = "qcom/aic200/tz.mbn",
+ [33] = "qcom/aic200/hypvm.mbn",
+ [38] = "qcom/aic200/xbl_config.elf",
+ [39] = "qcom/aic200/aic200_abl.elf",
+ [40] = "qcom/aic200/apdp.mbn",
+ [41] = "qcom/aic200/devcfg.mbn",
+ [42] = "qcom/aic200/sec.elf",
+ [43] = "qcom/aic200/aic200-hlos.elf",
+ [49] = "qcom/aic200/shrm.elf",
+ [50] = "qcom/aic200/cpucp.elf",
+ [51] = "qcom/aic200/aop_devcfg.mbn",
+ [54] = "qcom/aic200/qupv3fw.elf",
+ [57] = "qcom/aic200/cpucp_dtbs.elf",
+ [62] = "qcom/aic200/uefi_dtbs.elf",
+ [63] = "qcom/aic200/xbl_ac_config.mbn",
+ [64] = "qcom/aic200/tz_ac_config.mbn",
+ [65] = "qcom/aic200/hyp_ac_config.mbn",
+ [66] = "qcom/aic200/pdp.elf",
+ [67] = "qcom/aic200/pdp_cdb.elf",
+ [68] = "qcom/aic200/sdi.mbn",
+ [69] = "qcom/aic200/dcd.mbn",
+ [73] = "qcom/aic200/gearvm.mbn",
+ [74] = "qcom/aic200/sti.bin",
+ [76] = "qcom/aic200/tz_qti_config.mbn",
+ [78] = "qcom/aic200/pvs.bin",
+};
+
+static const struct mhi_sahara_fw_table aic100_sahara_fw = {
+ .image_table = aic100_image_table,
+ .table_size = ARRAY_SIZE(aic100_image_table),
+ .fw_folder = "aic100",
+ .non_streaming = true,
+};
+
+static const struct mhi_sahara_fw_table aic200_sahara_fw = {
+ .image_table = aic200_image_table,
+ .table_size = ARRAY_SIZE(aic200_image_table),
+ .fw_folder = "aic200",
+ .non_streaming = false,
+};
+
static const char *fw_image_paths[FAMILY_MAX] = {
[FAMILY_AIC100] = "qcom/aic100/sbl.bin",
[FAMILY_AIC200] = "qcom/aic200/sbl.bin",
@@ -871,6 +927,11 @@ struct mhi_controller *qaic_mhi_register_controller(struct pci_dev *pci_dev, voi
mhi_cntrl->name = "AIC100";
}
+ if (mhi_cntrl->name && !strcmp(mhi_cntrl->name, "AIC100"))
+ mhi_cntrl->sahara_fw = &aic100_sahara_fw;
+ else if (mhi_cntrl->name && !strcmp(mhi_cntrl->name, "AIC200"))
+ mhi_cntrl->sahara_fw = &aic200_sahara_fw;
+
/* use latest configured timeout */
mhi_config.timeout_ms = mhi_timeout_ms;
ret = mhi_register_controller(mhi_cntrl, &mhi_config);
diff --git a/drivers/bus/mhi/host/clients/sahara/sahara.c b/drivers/bus/mhi/host/clients/sahara/sahara.c
index 858dc5bc39c1ad42922cabef3b1abcd43bc4f0f4..e339c67e236af271645ca81cc517efd9eead87e4 100644
--- a/drivers/bus/mhi/host/clients/sahara/sahara.c
+++ b/drivers/bus/mhi/host/clients/sahara/sahara.c
@@ -179,48 +179,7 @@ struct sahara_context {
u32 read_data_length;
bool is_mem_dump_mode;
bool non_streaming;
-};
-
-static const char * const aic100_image_table[] = {
- [1] = "qcom/aic100/fw1.bin",
- [2] = "qcom/aic100/fw2.bin",
- [4] = "qcom/aic100/fw4.bin",
- [5] = "qcom/aic100/fw5.bin",
- [6] = "qcom/aic100/fw6.bin",
- [8] = "qcom/aic100/fw8.bin",
- [9] = "qcom/aic100/fw9.bin",
- [10] = "qcom/aic100/fw10.bin",
-};
-
-static const char * const aic200_image_table[] = {
- [5] = "qcom/aic200/uefi.elf",
- [12] = "qcom/aic200/aic200-nsp.bin",
- [23] = "qcom/aic200/aop.mbn",
- [32] = "qcom/aic200/tz.mbn",
- [33] = "qcom/aic200/hypvm.mbn",
- [38] = "qcom/aic200/xbl_config.elf",
- [39] = "qcom/aic200/aic200_abl.elf",
- [40] = "qcom/aic200/apdp.mbn",
- [41] = "qcom/aic200/devcfg.mbn",
- [42] = "qcom/aic200/sec.elf",
- [43] = "qcom/aic200/aic200-hlos.elf",
- [49] = "qcom/aic200/shrm.elf",
- [50] = "qcom/aic200/cpucp.elf",
- [51] = "qcom/aic200/aop_devcfg.mbn",
- [54] = "qcom/aic200/qupv3fw.elf",
- [57] = "qcom/aic200/cpucp_dtbs.elf",
- [62] = "qcom/aic200/uefi_dtbs.elf",
- [63] = "qcom/aic200/xbl_ac_config.mbn",
- [64] = "qcom/aic200/tz_ac_config.mbn",
- [65] = "qcom/aic200/hyp_ac_config.mbn",
- [66] = "qcom/aic200/pdp.elf",
- [67] = "qcom/aic200/pdp_cdb.elf",
- [68] = "qcom/aic200/sdi.mbn",
- [69] = "qcom/aic200/dcd.mbn",
- [73] = "qcom/aic200/gearvm.mbn",
- [74] = "qcom/aic200/sti.bin",
- [76] = "qcom/aic200/tz_qti_config.mbn",
- [78] = "qcom/aic200/pvs.bin",
+ const char *fw_folder;
};
static bool is_streaming(struct sahara_context *context)
@@ -796,6 +755,7 @@ static void sahara_read_data_processing(struct work_struct *work)
static int sahara_mhi_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id)
{
+ const struct mhi_sahara_fw_table *sahara_fw;
struct sahara_context *context;
int ret;
int i;
@@ -808,14 +768,14 @@ static int sahara_mhi_probe(struct mhi_device *mhi_dev, const struct mhi_device_
if (!context->rx)
return -ENOMEM;
- if (!strcmp(mhi_dev->mhi_cntrl->name, "AIC200")) {
- context->image_table = aic200_image_table;
- context->table_size = ARRAY_SIZE(aic200_image_table);
- } else {
- context->image_table = aic100_image_table;
- context->table_size = ARRAY_SIZE(aic100_image_table);
- context->non_streaming = true;
- }
+ sahara_fw = mhi_dev->mhi_cntrl->sahara_fw;
+ if (!sahara_fw || !sahara_fw->image_table || !sahara_fw->table_size)
+ return -ENODEV;
+
+ context->image_table = sahara_fw->image_table;
+ context->table_size = sahara_fw->table_size;
+ context->non_streaming = sahara_fw->non_streaming;
+ context->fw_folder = sahara_fw->fw_folder;
/*
* There are two firmware implementations for READ_DATA handling.
diff --git a/include/linux/mhi.h b/include/linux/mhi.h
index 88ccb3e14f481d6b85c2a314eb74ba960c2d4c81..060dafffac67c5c920adc1562a61a7233e8d583f 100644
--- a/include/linux/mhi.h
+++ b/include/linux/mhi.h
@@ -234,6 +234,21 @@ struct mhi_channel_config {
bool wake_capable;
};
+/**
+ * struct mhi_sahara_fw_table - Controller provided sahara firmware mapping
+ * @image_table: Sparse array indexed by Sahara image ID
+ * @table_size: Size of @image_table
+ * @fw_folder: Firmware folder name.
+ * @non_streaming: Streaming feature support (optional)
+ *
+ */
+struct mhi_sahara_fw_table {
+ const char *const *image_table;
+ u32 table_size;
+ const char *fw_folder;
+ bool non_streaming;
+};
+
/**
* struct mhi_event_config - Event ring configuration structure for controller
* @num_elements: The number of elements that can be queued to this ring
@@ -360,6 +375,7 @@ struct mhi_controller_config {
* @wake_set: Device wakeup set flag
* @irq_flags: irq flags passed to request_irq (optional)
* @mru: the default MRU for the MHI device
+ * @sahara_fw: Sahara firmware mapping
*
* Fields marked as (required) need to be populated by the controller driver
* before calling mhi_register_controller(). For the fields marked as (optional)
@@ -445,6 +461,7 @@ struct mhi_controller {
bool wake_set;
unsigned long irq_flags;
u32 mru;
+ const struct mhi_sahara_fw_table *sahara_fw;
};
/**
--
2.34.1
^ permalink raw reply related
* [PATCH v5 1/7] Add documentation for Sahara protocol
From: Kishore Batta @ 2026-04-16 14:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Jeff Hugo, Carl Vanderlip,
Oded Gabbay, Manivannan Sadhasivam
Cc: linux-doc, linux-kernel, linux-arm-msm, dri-devel, mhi,
Kishore Batta
In-Reply-To: <20260416-sahara_protocol_new_v2-v5-0-6aebf005e4ba@oss.qualcomm.com>
Introduce documentation for the Sahara protocol, describing its
operational modes and their respective functions. The image transfer mode
enables firmware transfer from host to device. The memory debug mode
allows extraction of device memory contents to host. The command mode
facilitates retrieval of DDR training data from the device and also
to restore the training data back to device in subsequent boot of device
to save boot time.
Signed-off-by: Kishore Batta <kishore.batta@oss.qualcomm.com>
---
Documentation/mhi/index.rst | 1 +
Documentation/mhi/sahara_protocol.rst | 1241 +++++++++++++++++++++++++++++++++
2 files changed, 1242 insertions(+)
diff --git a/Documentation/mhi/index.rst b/Documentation/mhi/index.rst
index 0aa00482aa2e2d7ec4941154a8c6947dc0a0ac40..39a38978398b81727514ec95dee4e060a1063b34 100644
--- a/Documentation/mhi/index.rst
+++ b/Documentation/mhi/index.rst
@@ -9,3 +9,4 @@ MHI
mhi
topology
+ sahara_protocol
diff --git a/Documentation/mhi/sahara_protocol.rst b/Documentation/mhi/sahara_protocol.rst
new file mode 100644
index 0000000000000000000000000000000000000000..bea72a98b9529ee7d5ce875b00dda5665237830a
--- /dev/null
+++ b/Documentation/mhi/sahara_protocol.rst
@@ -0,0 +1,1241 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+
+=============================
+Sahara protocol Specification
+=============================
+
+The Qualcomm Sahara protocol driver is primarily designed for transferring
+software images from a host device to a target device using a simplified data
+transfer mechanism over a link. However, the Sahara protocol does not support
+any authentication/validation of the data sent between devices. Such a mechanism
+is beyond the scope of the protocol.
+
+The Sahara protocol defines two types of packets - Command packet and Data
+packet.
+
+Command packet
+--------------
+ These packets are sent between the host and the target to setup transfers of
+ data packets. The command packets contain a command ID and packet length.
+ Depending on the command, the packet may contain additional command specific
+ field.
+
++-------------+---------------+----------------+----------------+
+| Command ID | Packet length | Optional field | Optional field |
++-------------+---------------+----------------+----------------+
+
+Data packet
+-----------
+ The data packets contain RAW data as shown below.
+
++---------------------------------------------------------+
+| RAW Data (arbitrary number of bytes) |
++---------------------------------------------------------+
+
+Command packet optional fields
+------------------------------
+
++---------+---------------+---------+-----------------------------------------+
+| ID val | Field | Sent by | Description |
++---------+---------------+---------+-----------------------------------------+
+| 0x0 | - | - | Invalid |
++---------+---------------+---------+-----------------------------------------+
+| 0x1 | Hello packet | Target | Initializes connection and protocol |
++---------+---------------+---------+-----------------------------------------+
+| 0x2 | Hello response| Host | Acknowledges connection and protocol |
+| | | | sent by target. Also used to set mode of|
+| | | | operation for target to execute. |
++---------+---------------+---------+-----------------------------------------+
+| 0x3 | Read data | Target | Reads specified number of bytes from |
+| | | | host for a given image. |
++---------+---------------+---------+-----------------------------------------+
+| 0x4 | End of image | Target | Indicates host that the single image tx |
+| | transfer | | is complete. Also used to indicate a |
+| | | | target failure during an image transfer |
++---------+---------------+---------+-----------------------------------------+
+| 0x5 | Done packet | Host | Sends acknowledgment from host that a |
+| | | | single image transfer is complete. |
++---------+---------------+---------+-----------------------------------------+
+| 0x6 | Done response | Target | Provides the following information to |
+| | | | host. |
+| | | | 1. Target is exiting protocol |
+| | | | 2. Whether the target expects to |
+| | | | re-enter protocol to transfer another |
+| | | | image. |
++---------+---------------+---------+-----------------------------------------+
+| 0x7 | Reset packet | Host | Instructs target to perform a reset. |
++---------+---------------+---------+-----------------------------------------+
+| 0x8 | Reset response| Target | Indicates host that target is about to |
+| | | | reset. |
++---------+---------------+---------+-----------------------------------------+
+| 0x9 | Memory debug | Target | Indicates host that target has entered |
+| | packet | | a debug mode where it is ready to |
+| | | | transfer its system memory contents |
++---------+---------------+---------+-----------------------------------------+
+| 0xA | Memory read | Host | Reads specified number of bytes from |
+| | packet | | target's system memory, starting from a |
+| | | | specified address. |
++---------+---------------+---------+-----------------------------------------+
+| 0xB | Command ready | Target | Indicates host that target is ready to |
+| | packet | | receive client commands. |
++---------+---------------+---------+-----------------------------------------+
+| 0xC | Command switch| Host | Indicates target to switch modes. |
+| | mode packet | | 1. Image transfer pending mode. |
+| | | | 2. Image transfer complete mode. |
+| | | | 3. Memory debug mode. |
+| | | | 4. Command mode. |
++---------+---------------+---------+-----------------------------------------+
+| 0xD | Command | Host | Indicates target to execute a given |
+| | execute packet| | client command. |
++---------+---------------+---------+-----------------------------------------+
+| 0xE | Command | Target | Indicates host that target has executed |
+| | execute | | client command. Also used to indicate |
+| | response | | status of executed command. |
+| | packet | | |
++---------+---------------+---------+-----------------------------------------+
+| 0xF | Command | Host | Indicates target that host is ready to |
+| | execute | | receive data resulting from executing |
+| | data | | previous client command. |
+| | packet | | |
++---------+---------------+---------+-----------------------------------------+
+| 0x10 | 64 bit Memory | Target | Indicates host that target has entered |
+| | debug packet | | a debug mode where it is ready to |
+| | | | transfer its 64 bit system memory |
+| | | | contents. |
++---------+---------------+---------+-----------------------------------------+
+| 0x11 | 64 bit Memory | Host | Reads specified number of bytes from |
+| | read packet | | target's system memory, starting from a |
+| | | | 64 bit specified address. |
++---------+---------------+---------+-----------------------------------------+
+| 0x12 | 64 bit Read | Target | Reads specified number of bytes from |
+| | data | | host for a given 64 bit image. |
++---------+---------------+---------+-----------------------------------------+
+| 0x13 | Reset Sahara | Host | Resets Sahara state machine and enters |
+| | sate machine | | Sahara entry without target reset |
+| | packet | | |
++---------+---------------+---------+-----------------------------------------+
+| 0x14 | Write data | Target | Writes specified number of bytes to host|
+| | packet | | for a given image |
++---------+---------------+---------+-----------------------------------------+
+| Others | - | - | Invalid |
++---------+---------------+---------+-----------------------------------------+
+
+
+Hello Packet
+------------
+
+The hello packet is the first packet that the target sends to the host. If the
+host receives any other packet, it sends a reset command to the target. When the
+host receives a valid hello packet, it first verifies that the protocol running
+on the target is compatible with the protocol running on the host. If the
+protocol mismatch, the host sends a reset command to the target. The target uses
+the following format while sending a hello packet.
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Version | 4 | Version number of this protocol |
++-----------+-------------+--------------------------------------+
+| Version | 4 | Lowest Compatible version |
+| Compatible| | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Maximum command packet length |
+| packet | | (in bytes) the protocol supports. |
+| length | | |
++-----------+-------------+--------------------------------------+
+| Mode | 4 | Expected mode of target operation |
++-----------+-------------+--------------------------------------+
+| Reserved | 4 | Reserved for future use. |
++-----------+-------------+--------------------------------------+
+| Reserved | 4 | Reserved for future use. |
++-----------+-------------+--------------------------------------+
+| Reserved | 4 | Reserved for future use. |
++-----------+-------------+--------------------------------------+
+| Reserved | 4 | Reserved for future use. |
++-----------+-------------+--------------------------------------+
+| Reserved | 4 | Reserved for future use. |
++-----------+-------------+--------------------------------------+
+| Reserved | 4 | Reserved for future use. |
++-----------+-------------+--------------------------------------+
+
+The target also sends the following information:
+ 1. Maximum length of the command packet that it supports. The host uses this
+ information to avoid sending more bytes than the target can support in the
+ receiving command buffer.
+ 2. Mode of operation it expects to enter, based on the boot up sequence. The
+ supported modes of operation for the target are as follows:
+
++-----------------------------+---------+------------------------------------+
+| Mode | Mode ID | Description |
++-----------------------------+---------+------------------------------------+
+| SAHARA_MODE_IMAGE_TX_PENDING| 0x0 | Image transfer is in the pending |
+| | | mode. Transfer image from the host.|
+| | | After completion, the host should |
+| | | expect another image transfer |
+| | | request. |
++-----------------------------+---------+------------------------------------+
+|SAHARA_MODE_IMAGE_TX_COMPLETE| 0x1 | Image transfer is in the complete |
+| | | mode. Transfer image from the host.|
+| | | After completion, the host should |
+| | | not expect another image transfer |
+| | | request. |
++-----------------------------+---------+------------------------------------+
+| SAHARA_MODE_MEMORY_DBEUG | 0x2 | Memory debug mode. The host should |
+| | | prepare to receive a memory dump |
+| | | from the target. |
++-----------------------------+---------+------------------------------------+
+| SAHARA_MODE_COMMAND | 0x3 | Command mode. The host executes |
+| | | operations on the target by sending|
+| | | the appropriate client command to |
+| | | the Sahara client running on the |
+| | | target. The Sahar client interprets|
+| | | the client command and the response|
+| | | is sent after execution of the |
+| | | given command. |
++-----------------------------+---------+------------------------------------+
+
+Hello response packet
+---------------------
+
+After the host validates the protocol running on the target, it sends a response
+to the target. The response contains the following information.
+1. The protocol version that is running.
+2. The minimum protocol version that it supports.
+3. The mode of operation.
+
+The host sets the packet status field to success if no errors occur on the host
+side. After the target receives this packet, it can proceed with data transfer
+requests or memory debug. The host uses the following format while sending a
+hello response packet.
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++===========+=============+======================================+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet (in bytes) |
++-----------+-------------+--------------------------------------+
+| Version | 4 | Version number of this protocol |
++-----------+-------------+--------------------------------------+
+| Compatible| 4 | Lowest Compatible version |
++-----------+-------------+--------------------------------------+
+| Status | 4 | Success or error code |
++-----------+-------------+--------------------------------------+
+| Mode | 4 | Mode of operation for target to |
+| | | execute |
++-----------+-------------+--------------------------------------+
+| Reserved | 4 | Reserved for future use |
++-----------+-------------+--------------------------------------+
+| Reserved | 4 | Reserved for future use |
++-----------+-------------+--------------------------------------+
+| Reserved | 4 | Reserved for future use |
++-----------+-------------+--------------------------------------+
+| Reserved | 4 | Reserved for future use |
++-----------+-------------+--------------------------------------+
+| Reserved | 4 | Reserved for future use |
++-----------+-------------+--------------------------------------+
+| Reserved | 4 | Reserved for future use |
++-----------+-------------+--------------------------------------+
+
+
+Read data packet / 64 bit read data packet
+------------------------------------------
+
+The read data packet serves as a generic data transfer packet when any image
+data is to be transferred from the host to the target. This packet allows
+flexibility in the way that the image is transferred from the host to the
+target. As the target controls which data gets transferred, the target can
+determine what parts of the image get transferred and in what order. The host
+need not be familiar about the structure of the image. It must open the file and
+start transferring the data to the target based on the parameters specified in
+the packet.
+
+This gives the target complete control over how the images are transferred and
+processed. To initiate an image transfer, the target fills the read data packet
+with the image ID corresponding to the image that it wants to receive. The
+target also sends the offset into the image file and the length of the data(in
+bytes) it wants to read from the image. After the host receives this packet, the
+host responds with a data packet, which contains image data with the length
+specified in the read data packet. The host uses the following format while
+transferring the read data packet and 64-bit read data packet.
+
+
+Read data packet format
+=======================
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Image ID | 4 | ID of the image to be transferred. |
++-----------+-------------+--------------------------------------+
+| Data | 4 | Offset into the image file to start |
+| offset | | transferring data. |
++-----------+-------------+--------------------------------------+
+| Data | 4 | Number of bytes target wants to |
+| Length | | transfer from the image. |
++-----------+-------------+--------------------------------------+
+
+
+64-bit read data packet format
+==============================
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Image ID | 8 | ID of the image to be transferred. |
++-----------+-------------+--------------------------------------+
+| Data | 8 | Offset into the image file to start |
+| offset | | transferring data. |
++-----------+-------------+--------------------------------------+
+| Data | 8 | Number of bytes target wants to |
+| Length | | transfer from the image. |
++-----------+-------------+--------------------------------------+
+
+If any of the preceding fields are invalid, or if any other error occurs on the
+host, the host sends a data packet with length that does not match with what the
+target was expecting. The resulting error forces the target to send an end of
+image transfer packet with an error code in the status field and enables both
+the target and the host to enter an error handling state.
+
+End of Image transfer packet
+----------------------------
+
+If an image transfer is successfully completed, the target sends the host an end
+of image transfer packet with a success status. The target then waits for the
+host to send a done packet. If any error occurs during the transfer or
+processing of the image data, the status is set to the corresponding error code,
+and the target waits for a different command packet.
+
+The host uses the following format while transferring end of image transfer
+packet:
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Image ID | 4 | ID of the image that was being |
+| | | transferred. |
++-----------+-------------+--------------------------------------+
+| Status | 4 | Success or error code |
++-----------+-------------+--------------------------------------+
+
+Done packet
+-----------
+
+If the host receives an end of image transfer packet with a success status, the
+host sends a done packet to indicate the target that it can exit the protocol
+and continue execution of code. The host uses the following format while sending
+the done packet:
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+
+To transfer another image from the host, the target must re-initiate the
+protocol by starting with another hello packet.
+
+Done Response packet
+--------------------
+
+If the target receives a done packet, it responds with a done response packet
+containing the image transfer status. The target uses the following format while
+sending the done response packet:
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Image Tx | 4 | Indicates whether target is |
+| Status | | expecting to receive another image |
+| | | or not. |
++-----------+-------------+--------------------------------------+
+
+If all the images are transferred, the target sends a complete status to enable
+the host to exit the protocol. If all the images are not transferred, the target
+sends a pending status and waits for another hello packet to arrive.
+
+Reset Packet
+------------
+
+The host sends a reset packet to reset the target. The target services a reset
+request only if its in a state where reset requests are valid. If the target
+receives an invalid reset request, the target sends an error in an end of image
+transfer packet. The format of reset packet is as follows:
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+
+
+Reset response packet
+---------------------
+
+If the target receives a valid reset request, it sends a reset response packet
+just before it resets. The purpose of this response is to acknowledge the host
+that the target received the reset request. The format of reset response packet
+is as follows:
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+
+
+Memory debug packet
+-------------------
+
+The target initiates a memory dump by sending the host a memory debug packet.
+This packet contains the address and length of the memory debug table. The
+memory debug table is a listing of memory locations that can be accessed and
+dumped to the host. The target uses the following format while sending the
+memory debug packet:
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Memory | 4 | Target sets this field to the address|
+| table | | in memory that stores the memory |
+| Address | | debug table. |
++-----------+-------------+--------------------------------------+
+| Memory | 4 | Length in bytes of memory debug |
+| table | | table. |
+| Length | | |
++-----------+-------------+--------------------------------------+
+
+Given the memory table address and length, the host issues a memory read to
+retrieve the table. After the host receives the memory table information, it can
+decode each entry and issue memory read requests to dump each memory location.
+
+Memory read packet / 64-bit memory read packet
+----------------------------------------------
+
+The host issues memory read commands for each section of memory that it dumps.
+The host uses the following format while sending the memory read packet and 64
+bit memory read packet:
+
+Memory read packet format
+=========================
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Memory | 4 | Memory location to read. |
+| Address | | |
++-----------+-------------+--------------------------------------+
+| Memory | 4 | Length in bytes of memory to read |
+| Length | | |
++-----------+-------------+--------------------------------------+
+
+64 bit memory read packet format
+================================
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Memory | 8 | Memory location to read. |
+| Address | | |
++-----------+-------------+--------------------------------------+
+| Memory | 8 | Length in bytes of memory to read |
+| Length | | |
++-----------+-------------+--------------------------------------+
+
+The accessible regions are defined in the memory debug table. For each memory
+read command received, the target verifies that the specified memory(address and
+length) is accessible and responds with a raw data packet. The content and
+length of the raw data packet is the memory dump starting from the memory
+address and length specified in the memory read packet. The memory debug table
+can also be read using a memory read command by setting the address and length
+to the values specified in the memory debug packet.
+
+If any error occurs on the target, an end of image transfer packet is sent with
+the corresponding error code and the host recognizes whether it is actual memory
+data or an end of image transfer packet. The host issues a reset command on
+completion of a successful memory dump. However, the protocol does not force
+this implementation.
+
+Command ready packet
+--------------------
+
+The target sends this packet to the host to indicate that the target is ready to
+execute client commands. The target uses the following format while sending the
+command ready packet:
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+
+
+Command switch mode packet
+--------------------------
+
+The host sends the command switch mode packet to the target so that the target
+can switch to another mode. The host uses the following format while sending the
+command switch mode packet:
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Mode | 4 | Mode of operation for target |
+| | | to execute. |
++-----------+-------------+--------------------------------------+
+
+Command execute packet
+----------------------
+
+The host sends this packet to execute the given client command on the target. If
+the client command successfully executes, the target sends a command execute
+response packet. If an error occurs, the target sends an end of image transfer
+packet with the corresponding error code. The host uses the following format
+while sending command execute packet:
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Client | 4 | Client Command to be executed. |
+| Command | | |
++-----------+-------------+--------------------------------------+
+
+
+Client commands
+===============
+
++------------+-------------+--------------------------------------+
+| Client ID | Length | Description |
++------------+-------------+--------------------------------------+
+| 0x8 | 4 | Get Command ID list. |
++------------+-------------+--------------------------------------+
+| 0x9 | 4 | Get DDR training data. |
++------------+-------------+--------------------------------------+
+
+Command execute Response packet
+-------------------------------
+
+The target sends this packet if it successfully executes the client command. The
+target uses the following format while sending the command execute response
+packet.
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Client | 4 | Client Command to be executed. |
+| Command | | |
++-----------+-------------+--------------------------------------+
+| Response | 4 | Number of bytes for response data. |
+| Length | | |
++-----------+-------------+--------------------------------------+
+
+Command execute data packet
+---------------------------
+
+The host sends this packet if the response length received in the command
+execute response packet is greater than 0. The host uses the following format
+while sending command execute data packet:
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Client | 4 | Client Command executed. |
+| Command | | |
++-----------+-------------+--------------------------------------+
+
+The packet indicates the target to send the response data in a raw data packet.
+The target sends the response data upon receiving this packet.
+
+64-bit memory debug packet
+--------------------------
+
+The target sends this packet to the host to initiate a memory dump. The packet
+contains 64-bit address and length of the memory table. The target uses the
+following format while sending 64-bit memory debug packet:
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Memory | 8 | Target sets this field to the 64-bit |
+| table | | address in memory that stores the |
+| Address | | memory debug table. |
++-----------+-------------+--------------------------------------+
+| Memory | 8 | Length in bytes of memory debug |
+| table | | table. |
+| Length | | |
++-----------+-------------+--------------------------------------+
+
+Reset Sahara state machine packet
+---------------------------------
+
+The host sends a reset Sahara state machine packet whenever it wants to reset
+Sahara state machine. When the target receives a reset Sahara state machine
+request, it reinitializes Sahara protocol and sends the hello packet to the
+host. The Sahara protocol is restarted without a target reset. The host uses the
+following format while sending the reset Sahara state machine packet:
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+
+Write data packet
+-----------------
+
+Write data packet serves as a generic data transfer packet when any data is
+transferred from the target to the host. This packet allows flexible data
+transfer from the target to the host.
+
+As the target controls what data gets transferred, target can determine what
+parts of the data get transferred and in what order. The host does not need to
+know anything about the structure of the data. It only needs to open the file
+and start accepting the data to the host based on the parameters specified in
+the packet.
+
+To initiate a write data transfer, the target fills the write data packet with
+the image ID corresponding to the image data that it wants to send. The target
+also sends the offset into the output file and the length of the data(in bytes)
+it wants to write from the target. As soon as the host receives the packet, the
+host opens an output file and waits to receive the data packets. After the
+packet is received, the content from the data pcket is written to the output
+file, The format of the write data packet is as follows:
+
++-----------+-------------+--------------------------------------+
+| Field | Length | Description |
+| | (bytes) | |
++-----------+-------------+--------------------------------------+
+| Command | 4 | Command identifier code |
++-----------+-------------+--------------------------------------+
+| Length | 4 | Length of the packet(in bytes) |
++-----------+-------------+--------------------------------------+
+| Data | 8 | Offset into the image file to start |
+| offset | | writing the data to host. |
++-----------+-------------+--------------------------------------+
+| Image ID | 4 | ID of the image to be transferred. |
++-----------+-------------+--------------------------------------+
+| Data | 4 | Number of bytes target wants to |
+| Length | | transfer the data to the host. |
++-----------+-------------+--------------------------------------+
+
+
+Command packet flow between host and target
+-------------------------------------------
+
+Packet flow is a process of exchange of information as packets between the host
+and the target in a specific way using command packets. The Sahara protocol
+allows packet processing for the following scenarios:
+
+1. Transferring an image from the host to the target.
+2. Dumping memory from the target to the host.
+3. Loading DDR calibration data on flashless target.
+
+Packet flow for Image transfer
+------------------------------
+
+The packet flow is performed between the host and target for a successful image
+transfer.
+
+.. code-block:: text
+
+ Host Target
+ | HELLO |
+ | (mode = image transfer) |
+ |<--------------------------|
+ | |
+ | HELLO RESP |
+ | (mode = image transfer) |
+ |-------------------------->|
+ | |
+ | READ_DATA |
+ | (img ID, 0, offset, |
+ | size of image header) |
+ |<--------------------------|
+ | |
+ | RAW_DATA |
+ | (size of image header) |
+ |-------------------------->|
+ | |
+ | READ_DATA |
+ | (img ID, segment 0 offset,|
+ | size of segment 0) |
+ |<--------------------------|
+ | RAW_DATA |
+ | (size of segment 0) |
+ |-------------------------->|
+ | |
+ | READ_DATA |
+ | (img ID, segment 1 offset,|
+ | size of segment 1) |
+ |<--------------------------|
+ | |
+ | |
+ | RAW_DATA |
+ | (size of segment 1) |
+ |-------------------------->|
+ | ... |
+ | ... |
+ | ... |
+ | ... |
+ | |
+ | |
+ | READ_DATA |
+ | (img ID, segment N offset,|
+ | size of segment N) |
+ |<--------------------------|
+ | |
+ | |
+ | |
+ | RAW_DATA |
+ | (size of segment N) |
+ |-------------------------->|
+ | |
+ | |
+ | END_IMAGE_TX |
+ |<--------------------------|
+ | |
+ | |
+ | DONE |
+ |-------------------------->|
+ | |
+ | |
+ | DONE_RESP |
+ |<--------------------------|
+ | |
+
+The packet flow sequence for image transfer is as follows:
+
+1. A hello packet is sent from the target to the host to initiate the protocol
+ with the mode set to either image transfer pending or image transfer
+ complete (depending on the target's boot sequence).
+
+2. The host sends a hello response packet with a success status and sets the
+ mode to the mode received in the hello packet. After it receives the hello
+ packet and validates the protocol version running on the target.
+
+3. After the target receives the hello response, the target initiates the
+ image transfer request by sending read data packets. Each read data packet
+ specifies the image that the target wishes to receive and what part of the
+ image is to be transferred.
+
+4. During normal operation, the target first requests image header information.
+
+ a. The image header information specifies image size and location of the
+ image data that is to be transferred.
+
+ b. The image header information (which is sent as a read data request)
+ allows the target to know the format of the image to be transferred.
+ The protocol does not require the host to know anything about the
+ image formats and allows the host to read and transfer data from the
+ image as requested by the target.
+
+ c. If the image is a standalone binary image without any data segmentation
+ (which means the data is entirely contiguous when stored as well as
+ transferred to the target system memory), then the target requests for
+ entire image data to be sent in one transfer.
+
+ d. If the image data is segmented and requires scattering of the data
+ segments to noncontiguous system memory locations, the target issues
+ multiple read data requests to enable each data segment to be
+ transferred directly to the respective destination address. This
+ scattered information resides in the image header and is parsed by the
+ target before issuing the read data requests.
+
+5. After receiving a read data request, the host parses the image ID, data
+ offset, and data length to transfer data from the corresponding image file.
+ The host sends the requested data without any packet header.
+
+6. The target directly transfers the data to the destination address without
+ any software processing or temporarily buffering of the data in system
+ memory by transferring the image header to the targert and setting the
+ receive buffer for the data as the destination address in system memory.
+
+7. After the target successfully receives all segments for an image, the
+ target sends an end of image transfer packet with the image ID of the
+ corresponding image, and a success status. The host stops reading and
+ closes the image file after receiving the success status.
+
+8. The host sends a done packet to allow the target to exit the protocol after
+ it receives a successgul end of image transfer packet.
+
+9. After the target receives the done packet, the target sends a done response
+ packet to the host. This packet indicates if the target expects another
+ image to be transferred and if the host can continue to run the protocol.
+
+Packet flow for memory debug
+----------------------------
+
+The packet flow is performed between the host and the target for the successful
+memory debug.
+
+.. code-block:: text
+
+ Host Target
+ | HELLO |
+ | (mode = memory debug) |
+ |<--------------------------|
+ | |
+ | HELLO RESP |
+ | (mode = memory debug) |
+ |-------------------------->|
+ | |
+ | MEMORY_DEBUG |
+ | (location of mem table, |
+ | size of memory table) |
+ |<--------------------------|
+ | |
+ | MEMORY_READ |
+ | (Address from region 0 ,|
+ | size of region 0) |
+ |-------------------------->|
+ | RAW_DATA |
+ | (size of region 0) |
+ |<--------------------------|
+ | |
+ | MEMORY_READ |
+ | (Address from region 1 ,|
+ | size of region 1) |
+ |-------------------------->|
+ | RAW_DATA |
+ | (size of region 1) |
+ |<--------------------------|
+ | MEMORY_READ |
+ | (Address from region 2 ,|
+ | size of region 0) |
+ |-------------------------->|
+ | RAW_DATA |
+ | (size of region 2) |
+ |<--------------------------|
+ | ... |
+ | ... |
+ | ... |
+ | ... |
+ | |
+ | MEMORY_READ |
+ | (Address from region N ,|
+ | size of region N) |
+ |-------------------------->|
+ | RAW_DATA |
+ | (size of region N) |
+ |<--------------------------|
+ | |
+ | RESET |
+ |-------------------------->|
+ | |
+ | |
+ | RESET_RESP |
+ |<--------------------------|
+ | |
+
+The packet flow sequence for image transfer is as follows:
+
+1. A hello packet is sent from the target to the host to initiate the protocol
+ with mode set to memory debug.
+
+2. The host sends a hello response packet with a success status and sets the
+ mode to memory debug after it receives the hello packet and validates the
+ protocol version running on the target.
+
+3. After the target receives the hello response, the target initiates the
+ memory dump by sending a memory debug packet with the location and size of
+ the memory debug table. The memory debug table specifies accessible memory
+ regions.
+
+4. The host then initiates a memory read packet to read the memory debug
+ table and receives the table in a raw data packet after it receives the
+ memory debug packet.
+
+5. The host then decodes the table and issues memory reads for each accessible
+ region. The data for each region is sent in a raw data packet.
+
+6. Upon completion, the host issues a reset to the target. The target sends a
+ reset response and resets the target.
+
+7. The host can alternatively send a command switch mode packet to allow the
+ target to switch modes and avoid a reset.
+
+
+Packet flow to load DDR calibration data on target
+--------------------------------------------------
+
+The packet flow is performed between the host and the target to load DDR
+calibration data on flashless target. This packet flow is initiated when the
+device boots up for the first time and needs DDR calibration. This packet flow
+is also initiated in other scenarios, such as build update or any reason for
+which DDR calibration data gets corrupted.
+
+First boot scenario or invalid calibration data in filesystem.
+--------------------------------------------------------------
+
+.. code-block:: text
+
+ Host Target
+ | HELLO |
+ | (mode = image transfer) |
+ |<--------------------------|
+ | |
+ | HELLO RESP |
+ | (mode = image transfer) |
+ |-------------------------->|
+ | |
+ | READ_DATA |
+ | (img ID:34, 0, offset, |
+ | size of DDR training data)|
+ |<--------------------------|
+ | |
+ | RAW_DATA |
+ |(size of DDR training data)|
+ |-------------------------->|
+ | |
+ | |
+ | END_IMAGE_TX |
+ |<--------------------------|
+ | |
+ | |
+ | DONE |
+ |-------------------------->|
+ | |
+ | |
+ | DONE_RESP |
+ | (mode = IMAGE_TX_PENDING) |
+ |<--------------------------|
+ |1. First boot scenario. |
+ | DDR driver performs |
+ | calibration and returns |
+ | to SBL. |
+ |2. Next: Push DDR |
+ | Calibration data to host |
+ | |
+ | |
+ | HELLO |
+ | (mode = COMMAND mode) |
+ |<--------------------------|
+ | |
+ | HELLO RESP |
+ | (mode = COMMAND mode ) |
+ |-------------------------->|
+ | |
+ | CMD_READY |
+ |<--------------------------|
+ | |
+ | CMD_EXEC |
+ |(cmd id = 8, Get command |
+ | ID to be executed) |
+ |-------------------------->|
+ | |
+ | CMD_EXEC_RESP |
+ |(cmd id: 8, resp len = 4) |
+ |<--------------------------|
+ | |
+ | CMD_EXEC_GET_DATA |
+ | (ID = 0x8) |
+ |-------------------------->|
+ | |
+ | RAW_DATA |
+ | (0x00000009) |
+ |<--------------------------|
+ | |
+ | CMD_EXEC |
+ | (cmd id: 9, resp len > 0) |
+ |-------------------------->|
+ | |
+ | |
+ | CMD_EXEC_RESP |
+ |(cmd id: 9, resp len > 0) |
+ |<--------------------------|
+ | |
+ | CMD_EXEC_GET_DATA |
+ | (ID = 0x9) |
+ |-------------------------->|
+ | |
+ | RAW_DATA |
+ | (valid training data) |
+ |<--------------------------|
+ | |
+ |3. Host sends switch to |
+ |image tx mode to continue |
+ |booting. |
+ | |
+ | |
+ | CMD_SWITCH_MODE |
+ | (mode = IMAGE_TX_PENDING) |
+ |-------------------------->|
+ | |
+ | |
+ | HELLO |
+ | (mode = IMAGE_TX_PENDING) |
+ |<--------------------------|
+ | |
+ | HELLO RESP |
+ | (mode = IMAGE_TX_PENDING) |
+ |-------------------------->|
+ | |
+ |4. Boot/Load rest of the |
+ | images.... |
+ | |
+ | END_IMAGE_TX |
+ |<--------------------------|
+ | |
+ | |
+ | DONE |
+ |-------------------------->|
+ | |
+ | |
+ | DONE_RESP |
+ |(mode = IMAGE_TX_COMPLETE) |
+ |<--------------------------|
+ | |
+
+The packet flow sequence is as follows :
+
+1. The target sends the hello packet to the host to initiate the protocol
+ with the mode set to image transfer pending.
+
+2. The host sends a hello response packet with a success status and sets the
+ mode to image transfer pending after it receives the hello packet and
+ validates the protocol version running on the target.
+
+3. After the target receives the hello response, it initiates the data
+ transfer by requesting the size of DDR training/calibration data.
+
+4. The host sends back the DDR training/calibration data to the target.
+
+5. The target decodes the training data and does not find valid DDR
+ calibration data, target sends END_IMAGE_TX to interrupt the transfer.
+
+6. The host sends DONE after receives END_IMAGE_TX.
+
+7. The target sends DONE_RESP with mode = IMAGE_TX_PENDING because it has
+ not received all images.
+
+8. The target executes DDR training process to generate valid DDR calibration
+ data and prepares to push back to host.
+
+9. The target initiates protocol by sending a hello packet with COMMAND_MODE
+ to the host.
+
+10. The host sends a hello response packet with a success status and sets the
+ mode to COMMAND_MODE.
+
+11. The target sends CMD_READY to the host.
+
+12. The host receives CMD_READY and starts to get command IDs to be executed.
+
+13. The target sends CMD_ID = 9 to push DDR calibration data to host.
+
+14. The host executes CMD_ID = 9 to get DDR calibration data from the target.
+
+15. The target sends RAW_DATA with the payload which contains DDR calibration
+ data to host.
+
+16. The host saves training data in the kernel buffer and exposes to userspace
+ via the sysfs entry. The host sends CMD_SWITCH_MODE with the mode set to
+ IMAGE_TX_PENDING to continue booting.
+
+17. After the target receives the CMD_SWITCH_MODE command, it sends HELLO to
+ the host with the mode set to IMAGE_TX_PENDING. The target and the host
+ repeat the packet flow for image transfer to get all booting-required
+ images.
+
+18. Upon successful transfer of all images, the target sends an END_IMAGE_TX
+ packet with a success status to the host.
+
+19. The host sends DONE after it receives END_IMAGE_TX.
+
+20. The target sends DONE_RESP with the mode set to IMAGE_TX_COMPLETE because
+ it has received all images. The process has been completed after the host
+ receives DONE_RESP with the mode set to IMAGE_TX_COMPLETE.
+
+Subsequent boot scenario with valid DDR calibration data
+--------------------------------------------------------
+
+The below firgure shows the subsequent boot scenario with valid DDR calibration
+data process being loaded from host to target.
+
+.. code-block:: text
+
+ Host Target
+ | HELLO |
+ | (mode = image transfer) |
+ |<--------------------------|
+ | |
+ | HELLO RESP |
+ | (mode = image transfer) |
+ |-------------------------->|
+ | |
+ | READ_DATA |
+ | (img ID:34, 0, offset, |
+ | size of DDR training data)|
+ |<--------------------------|
+ | |
+ | RAW_DATA |
+ |(size of DDR training data)|
+ |-------------------------->|
+ | |
+ | |
+ | END_IMAGE_TX |
+ |<--------------------------|
+ | |
+ | |
+ | DONE |
+ |-------------------------->|
+ | |
+ | |
+ | DONE_RESP |
+ | (mode = IMAGE_TX_PENDING) |
+ |<--------------------------|
+ | |
+ | Subsequent boot scenario |
+ | (valid calibration data) |
+ | DDR driver configures DDR |
+ | using valid calibration |
+ | data |
+ | |
+ | |
+ | HELLO |
+ | (mode = IMAGE_TX_PENDING) |
+ |<--------------------------|
+ | |
+ | HELLO RESP |
+ | (mode = IMAGE_TX_PENDING) |
+ |-------------------------->|
+ | |
+ | Boot/Load rest of the |
+ | images.... |
+ | |
+ | END_IMAGE_TX |
+ |<--------------------------|
+ | |
+ | |
+ | DONE |
+ |-------------------------->|
+ | |
+ | |
+ | DONE_RESP |
+ |(mode = IMAGE_TX_COMPLETE) |
+ |<--------------------------|
+ | |
+
+The packet flow is as follows :
+
+1. The target sends the hello packet to the host to initiate the protocol
+ with the mode set to image transfer pending.
+
+2. The host sends a hello response packet with a success status and sets the
+ mode to image transfer pending after it receives the hello packet and
+ validates the protocol version running on the target.
+
+3. After the target receives the hello response, it initiates the images
+ transfer by requesting the training/calibration data from the host.
+
+4. The host sends back the DDR training/calibration data to the target.
+
+5. The target decodes the DDR training/calibration data and finds valid DDR
+ calibration data.
+
+6. The host sends RAW_DATA with the size of the DDR calibration data to the
+ target.
+
+7. Upon successful transfer of DDR calibration data, the target sends an
+ END_IMAGE_TX packet with a success status.
+
+8. The host sends DONE after it receives END_IMAGE_TX.
+
+9. The target sends DONE_RESP with mode = IMAGE_TX_PENDING because it has not
+ received all images.
+
+10. The target continues booting with valid DDR calibration data.
+
+11. The target and the host repeat the packet flow for image transfer to get
+ all booting-required images.
+
+12. After successful transfer of all images, the target sends an END_IMAGE_TX
+ packet with a success status to the host.
+
+13. The host sends DONE after it receives END_IMAGE_TX.
+
+14. The target sends DONE_RESP with the mode set to IMAGE_TX_COMPLETE because
+ it has received all images. The process has been completed after the host
+ receives DONE_RESP with the mode set to IMAGE_TX_COMPLETE.
--
2.34.1
^ permalink raw reply related
* [PATCH v5 2/7] bus: mhi: Move Sahara protocol driver under MHI host client drivers
From: Kishore Batta @ 2026-04-16 14:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Jeff Hugo, Carl Vanderlip,
Oded Gabbay, Manivannan Sadhasivam
Cc: linux-doc, linux-kernel, linux-arm-msm, dri-devel, mhi,
Kishore Batta
In-Reply-To: <20260416-sahara_protocol_new_v2-v5-0-6aebf005e4ba@oss.qualcomm.com>
The Sahara protocol driver currently lives under the QAIC accelerator
subsystem even though the protocol is transported over MHI and is used by
multiple Qualcomm flashless devices. This makes Sahara appear QAIC specific
and complicates reuse by other MHI based devices.
Move the Sahara protocol driver under drivers/bus/mhi as a host client
driver and build it as an independent MHI protocol driver. This keeps the
QAIC driver focused on the accelerator device while allowing other MHI
users to enable Sahara without depending on QAIC.
As part of the move, add a dedicated Kconfig/Makefile hierarchy under the
MHI host client drivers and convert the driver to use module_mhi_driver()
instead of register/unregister hooks.
Signed-off-by: Kishore Batta <kishore.batta@oss.qualcomm.com>
---
drivers/accel/qaic/Kconfig | 1 +
drivers/accel/qaic/Makefile | 3 +--
drivers/accel/qaic/qaic_drv.c | 9 ---------
drivers/accel/qaic/sahara.h | 10 ----------
drivers/bus/mhi/Kconfig | 1 +
drivers/bus/mhi/host/Makefile | 1 +
drivers/bus/mhi/host/clients/Kconfig | 5 +++++
drivers/bus/mhi/host/clients/Makefile | 1 +
drivers/bus/mhi/host/clients/sahara/Kconfig | 15 +++++++++++++++
drivers/bus/mhi/host/clients/sahara/Makefile | 2 ++
.../qaic => bus/mhi/host/clients/sahara}/sahara.c | 20 +++++++-------------
11 files changed, 34 insertions(+), 34 deletions(-)
diff --git a/drivers/accel/qaic/Kconfig b/drivers/accel/qaic/Kconfig
index 116e42d152ca885b8c59e33c7a87519a0abc6bb3..af90fdfcf77eeb6dd5ad309b33d793d4fdc91b1e 100644
--- a/drivers/accel/qaic/Kconfig
+++ b/drivers/accel/qaic/Kconfig
@@ -8,6 +8,7 @@ config DRM_ACCEL_QAIC
depends on DRM_ACCEL
depends on PCI && HAS_IOMEM
depends on MHI_BUS
+ depends on MHI_SAHARA
select CRC32
select WANT_DEV_COREDUMP
help
diff --git a/drivers/accel/qaic/Makefile b/drivers/accel/qaic/Makefile
index 71f727b74da3bb4478324689f02a7cea24a05c2d..e7b8458800072aa627f7f36c3257883aa56f4ce4 100644
--- a/drivers/accel/qaic/Makefile
+++ b/drivers/accel/qaic/Makefile
@@ -13,7 +13,6 @@ qaic-y := \
qaic_ras.o \
qaic_ssr.o \
qaic_sysfs.o \
- qaic_timesync.o \
- sahara.o
+ qaic_timesync.o
qaic-$(CONFIG_DEBUG_FS) += qaic_debugfs.o
diff --git a/drivers/accel/qaic/qaic_drv.c b/drivers/accel/qaic/qaic_drv.c
index 63fb8c7b4abcbe4f1b76c32106f4e8b9ea5e2c8e..3907b13e426064f4fa069e803cc44462feea4063 100644
--- a/drivers/accel/qaic/qaic_drv.c
+++ b/drivers/accel/qaic/qaic_drv.c
@@ -32,7 +32,6 @@
#include "qaic_ras.h"
#include "qaic_ssr.h"
#include "qaic_timesync.h"
-#include "sahara.h"
MODULE_IMPORT_NS("DMA_BUF");
@@ -791,12 +790,6 @@ static int __init qaic_init(void)
goto free_pci;
}
- ret = sahara_register();
- if (ret) {
- pr_debug("qaic: sahara_register failed %d\n", ret);
- goto free_mhi;
- }
-
ret = qaic_timesync_init();
if (ret)
pr_debug("qaic: qaic_timesync_init failed %d\n", ret);
@@ -818,7 +811,6 @@ static int __init qaic_init(void)
free_bootlog:
qaic_bootlog_unregister();
-free_mhi:
mhi_driver_unregister(&qaic_mhi_driver);
free_pci:
pci_unregister_driver(&qaic_pci_driver);
@@ -847,7 +839,6 @@ static void __exit qaic_exit(void)
qaic_ras_unregister();
qaic_bootlog_unregister();
qaic_timesync_deinit();
- sahara_unregister();
mhi_driver_unregister(&qaic_mhi_driver);
pci_unregister_driver(&qaic_pci_driver);
}
diff --git a/drivers/accel/qaic/sahara.h b/drivers/accel/qaic/sahara.h
deleted file mode 100644
index 640208acc0d13d423bd9220e6861b7c141af74ff..0000000000000000000000000000000000000000
--- a/drivers/accel/qaic/sahara.h
+++ /dev/null
@@ -1,10 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-
-/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. */
-
-#ifndef __SAHARA_H__
-#define __SAHARA_H__
-
-int sahara_register(void);
-void sahara_unregister(void);
-#endif /* __SAHARA_H__ */
diff --git a/drivers/bus/mhi/Kconfig b/drivers/bus/mhi/Kconfig
index b39a11e6c624ba00349cca22d74bd876020590ab..720115218c2401c99b29f79bbd4113cd877503ac 100644
--- a/drivers/bus/mhi/Kconfig
+++ b/drivers/bus/mhi/Kconfig
@@ -7,3 +7,4 @@
source "drivers/bus/mhi/host/Kconfig"
source "drivers/bus/mhi/ep/Kconfig"
+source "drivers/bus/mhi/host/clients/Kconfig"
diff --git a/drivers/bus/mhi/host/Makefile b/drivers/bus/mhi/host/Makefile
index 859c2f38451c669b3d3014c374b2b957c99a1cfe..2e8949f1a2fe6f3f3b2e1dc541f97d2c393d6a0f 100644
--- a/drivers/bus/mhi/host/Makefile
+++ b/drivers/bus/mhi/host/Makefile
@@ -4,3 +4,4 @@ mhi-$(CONFIG_MHI_BUS_DEBUG) += debugfs.o
obj-$(CONFIG_MHI_BUS_PCI_GENERIC) += mhi_pci_generic.o
mhi_pci_generic-y += pci_generic.o
+obj-$(CONFIG_MHI_BUS) += clients/
diff --git a/drivers/bus/mhi/host/clients/Kconfig b/drivers/bus/mhi/host/clients/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..a4f2a3c1d20c887cc474646ea91532d775a13f57
--- /dev/null
+++ b/drivers/bus/mhi/host/clients/Kconfig
@@ -0,0 +1,5 @@
+menu "MHI host client drivers"
+
+source "drivers/bus/mhi/host/clients/sahara/Kconfig"
+
+endmenu
diff --git a/drivers/bus/mhi/host/clients/Makefile b/drivers/bus/mhi/host/clients/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..62e2fa161565225be7f6a23d3cdf4f2f169cb7ce
--- /dev/null
+++ b/drivers/bus/mhi/host/clients/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_MHI_SAHARA) += sahara/
diff --git a/drivers/bus/mhi/host/clients/sahara/Kconfig b/drivers/bus/mhi/host/clients/sahara/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..f1fc7013a2dee0be645c50f32305659e591de7e7
--- /dev/null
+++ b/drivers/bus/mhi/host/clients/sahara/Kconfig
@@ -0,0 +1,15 @@
+config MHI_SAHARA
+ tristate "Sahara protocol driver"
+ depends on MHI_BUS
+ help
+ Enable support for the Sahara protocol transported over the MHI bus.
+
+ The Sahara protocol is used to transfer firmware images, retrieve
+ memory dumps and exchange command mode DDR calibration data between
+ host and device. This driver is not tied to a specific SoC and may be
+ used by multiple MHI based devices.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the module will be
+ called mhi_sahara.
diff --git a/drivers/bus/mhi/host/clients/sahara/Makefile b/drivers/bus/mhi/host/clients/sahara/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..fc02a25935011cbd7138ea8f24b88cf5b032a4ce
--- /dev/null
+++ b/drivers/bus/mhi/host/clients/sahara/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_MHI_SAHARA) += mhi_sahara.o
+mhi_sahara-y := sahara.o
diff --git a/drivers/accel/qaic/sahara.c b/drivers/bus/mhi/host/clients/sahara/sahara.c
similarity index 99%
rename from drivers/accel/qaic/sahara.c
rename to drivers/bus/mhi/host/clients/sahara/sahara.c
index fd3c3b2d1fd3bb698809e6ca669128e2dce06613..858dc5bc39c1ad42922cabef3b1abcd43bc4f0f4 100644
--- a/drivers/accel/qaic/sahara.c
+++ b/drivers/bus/mhi/host/clients/sahara/sahara.c
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-only
-
-/* Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. */
+/*
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ *
+ */
#include <linux/devcoredump.h>
#include <linux/firmware.h>
@@ -13,8 +15,6 @@
#include <linux/vmalloc.h>
#include <linux/workqueue.h>
-#include "sahara.h"
-
#define SAHARA_HELLO_CMD 0x1 /* Min protocol version 1.0 */
#define SAHARA_HELLO_RESP_CMD 0x2 /* Min protocol version 1.0 */
#define SAHARA_READ_DATA_CMD 0x3 /* Min protocol version 1.0 */
@@ -923,13 +923,7 @@ static struct mhi_driver sahara_mhi_driver = {
.name = "sahara",
},
};
+module_mhi_driver(sahara_mhi_driver);
-int sahara_register(void)
-{
- return mhi_driver_register(&sahara_mhi_driver);
-}
-
-void sahara_unregister(void)
-{
- mhi_driver_unregister(&sahara_mhi_driver);
-}
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Qualcomm Sahara MHI protocol driver");
--
2.34.1
^ permalink raw reply related
* [PATCH v5 0/7] Qualcomm Sahara protocol enhancements
From: Kishore Batta @ 2026-04-16 14:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Jeff Hugo, Carl Vanderlip,
Oded Gabbay, Manivannan Sadhasivam
Cc: linux-doc, linux-kernel, linux-arm-msm, dri-devel, mhi,
Kishore Batta
Hi All,
This series reworks the Sahara protocol driver to make it reusable for
multiple MHI based devices and adds support for capturing, restoring and
exposing DDR training data using the Sahara command mode.
The Sahara protocol is transported over the MHI bus and is used by multiple
flashless devices to transfer firmware images, retrieve memory dumps and
exchange command mode data during early boot. However, the current
implementation lives under the QAIC accelerator driver and contains
device-specific assumptions that limit reuse.
Some MHI devices (for example, QDU100) expose the sahara protocol directly
on a "SAHARA" MHI channel and rely on command mode to exchange DDR training
data with the host. The existing driver does not bind to such devices and
ignores Sahara command mode packets, causing training data to be dropped.
This series addresses these issues by relocating the Sahara driver to the
MHI subsystem, centralizing device specific configuration and adding command
mode handling for DDR training data.
Overview of the changes in this series -
1. Move Sahara under the MHI subsystem:
a. Relocate the sahara protocol driver from QAIC accelerator tree to
drivers/bus/mhi/host/clients directory.
b. Register Sahara as an independent MHI protocol driver.
2. Generalize device matching and configuration
a. Allow the driver to bind to devices exposing the protocol on a
SAHARA MHI channel.
b. Centralize firmware image table selection at probe time using a variant
table, instead of scattered conditionals.
c. Preserve existing behavior on AIC devices.
3. Add QDU100 firmware image table support
a. Add a QDU100 image table and select it based on the matched MHI channel.
b. No separate client driver or registration mechanism is required.
4. Add Sahara command mode support for DDR training.
a. Handle command mode packets(CMD_READY, EXECUTE, EXECUTE_DATA).
b. Query supported commands and retrieve DDR training data from the device.
c. Allocate receive buffers based on the reported payload size and copy
raw data from the MHI DL.
d. Store training data in controller-scoped memory using devres so it
survives sahara channel teardown.
5. Expose DDR training data to userspace
a. Add a read-only binary sysfs attribute under the MHI controller device.
b. The attribute reads directly from controller-scoped storage and remains
available after the Sahara channel device is removed.
c. Cleanup is handled automatically via device-managed resources.
6. Document the sysfs ABI
a. Add ABI documentation describing the DDR training data sysfs node.
Signed-off-by: Kishore Batta <kishore.batta@oss.qualcomm.com>
---
Changes in v5:
- Sahara protocol documentation is moved to Documentation/mhi/ directory.
- Sahara driver is now moved to drivers/bus/mhi/host/clients/ directory.
- Squashed v4 patch 3 with v4 patch 5.
- Squashed v4 patch 9 with v4 patch 8.
- Firmware image tables are now moved to respective MHI controller files.
- Removed redundant if-else conditions when loading DDR training data image.
- Corrected commit subjects and messages.
- Removed Sahara variant structure and introduced mhi_sahara_fw_table structure.
- Link to v4: https://lore.kernel.org/r/20260319-sahara_protocol_new_v2-v4-0-47ad79308762@oss.qualcomm.com
Changes in v4:
- EDITME: describe what is new in this series revision.
- EDITME: use bulletpoints and terse descriptions.
- Link to v3: https://lore.kernel.org/r/20260310-sahara_protocol_new_v2-v3-0-994ea4b0d5ad@oss.qualcomm.com
Changes in v3:
- Dropped the explicit image table entry for the boot critical image ID and
added a generic image ID based firmware lookup fallback.
- Link to v2: https://lore.kernel.org/r/20260307-sahara_protocol_new_v2-v2-0-29dc748b5e9c@oss.qualcomm.com
Changes in v2:
- Rebased onto latest linux-next tip.
- Reworked commit messages to clearly start with the problem being solved and
end with a technical description of the change.
- Moved the Sahara driver to drivers/bus/mhi instead of drivers/soc/qcom,
reflecting that its an MHI protocol driver rather than a SoC specific driver.
- Removed client side image table registration and consolidated firmware
selection directly in the sahara driver using a probe-time variant
mechanism.
- Ensured each patch is self-contained and does not break the build or runtime
behavior at any intermediate point.
- Simplified state handling and lifetime management to avoid duplicated state
tracking and ad-hoc cleanup.
- Updated sysfs handling to use controller-scoped devres and avoid one-shot
reads or manual teardown.
- Link to v1: https://lore.kernel.org/r/20250825101926.2160554-1-kishore.batta@oss.qualcomm.com
---
Kishore Batta (7):
Add documentation for Sahara protocol
bus: mhi: Move Sahara protocol driver under MHI host client drivers
bus: mhi: Centralize Sahara firmware image table selection at probe time
bus: mhi: Add QDU100 Sahara variant and firmware fallback
bus: mhi: Load DDR training data using device serial number
bus: mhi: Capture DDR training data via command mode
bus: mhi: Expose DDR training data via controller sysfs
.../ABI/testing/sysfs-bus-mhi-ddr_training_data | 19 +
Documentation/mhi/index.rst | 1 +
Documentation/mhi/sahara_protocol.rst | 1241 ++++++++++++++++++++
drivers/accel/qaic/Kconfig | 1 +
drivers/accel/qaic/Makefile | 3 +-
drivers/accel/qaic/mhi_controller.c | 61 +
drivers/accel/qaic/qaic_drv.c | 9 -
drivers/accel/qaic/sahara.h | 10 -
drivers/bus/mhi/Kconfig | 1 +
drivers/bus/mhi/host/Makefile | 1 +
drivers/bus/mhi/host/clients/Kconfig | 5 +
drivers/bus/mhi/host/clients/Makefile | 1 +
drivers/bus/mhi/host/clients/sahara/Kconfig | 15 +
drivers/bus/mhi/host/clients/sahara/Makefile | 2 +
.../qaic => bus/mhi/host/clients/sahara}/sahara.c | 523 +++++++--
drivers/bus/mhi/host/pci_generic.c | 45 +
include/linux/mhi.h | 17 +
17 files changed, 1863 insertions(+), 92 deletions(-)
---
base-commit: a0ae2a256046c0c5d3778d1a194ff2e171f16e5f
change-id: 20260307-sahara_protocol_new_v2-662854773cf7
Best regards,
--
Kishore Batta <kishore.batta@oss.qualcomm.com>
^ permalink raw reply
* Re: [RFC, PATCH 00/12] userfaultfd: working set tracking for VM guest memory
From: Kiryl Shutsemau @ 2026-04-16 13:49 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Andrew Morton, Peter Xu, Lorenzo Stoakes, Mike Rapoport,
Suren Baghdasaryan, Vlastimil Babka, Liam R . Howlett, Zi Yan,
Jonathan Corbet, Shuah Khan, Sean Christopherson, Paolo Bonzini,
linux-mm, linux-kernel, linux-doc, linux-kselftest, kvm
In-Reply-To: <ad50rPOseVa-SP_s@thinkstation>
On Tue, Apr 14, 2026 at 06:10:44PM +0100, Kiryl Shutsemau wrote:
> On Tue, Apr 14, 2026 at 05:37:50PM +0200, David Hildenbrand (Arm) wrote:
> > On 4/14/26 16:23, Kiryl Shutsemau (Meta) wrote:
> > > This series adds userfaultfd support for tracking the working set of
> > > VM guest memory, enabling VMMs to identify cold pages and evict them
> > > to tiered or remote storage.
> > >
> > > == Problem ==
> > >
> > > VMMs managing guest memory need to:
> > > 1. Track which pages are actively used (working set detection)
> > > 2. Safely evict cold pages to slower storage
> > > 3. Fetch pages back on demand when accessed again
> > >
> > > For shmem-backed guest memory, working set tracking partially works
> > > today: MADV_DONTNEED zaps PTEs while pages stay in page cache, and
> > > re-access auto-resolves from cache. But safe eviction still requires
> > > synchronous fault interception to prevent data loss races.
> > >
> > > For anonymous guest memory (needed for KSM cross-VM deduplication),
> > > there is no mechanism at all — clearing a PTE loses the page.
> > >
> > > == Solution ==
> > >
> > > The series introduces a unified userfaultfd interface that works
> > > across both anonymous and shmem-backed memory:
> > >
> > > UFFD_FEATURE_MINOR_ANON: extends MODE_MINOR registration to anonymous
> > > private memory. Uses the PROT_NONE hinting mechanism (same as NUMA
> > > balancing) to make pages inaccessible without freeing them.
> >
> > I would rather tackle this from the other direction: it's another form
> > of protection (like WP), not really a "minor" mode.
> >
> > Could we add a UFFDIO_REGISTER_MODE_RWP (or however we would call it)
> > and support it for anon+shmem, avoiding the zapping for shmem completely?
>
> I like this idea.
>
> It should be functionally equivalent, but your interface idea fits
> better with the rest.
>
> Thanks! Will give it a try.
Here is an updated version:
https://git.kernel.org/pub/scm/linux/kernel/git/kas/linux.git/log/?h=uffd/rfc-v2
will post after -rc1 is tagged.
I like it more. It got substantially cleaner.
--
Kiryl Shutsemau / Kirill A. Shutemov
^ permalink raw reply
* Re: [RFC, PATCH 10/12] userfaultfd: add UFFDIO_SET_MODE for runtime sync/async toggle
From: Kiryl Shutsemau @ 2026-04-16 13:27 UTC (permalink / raw)
To: Usama Arif
Cc: Andrew Morton, Peter Xu, David Hildenbrand, Lorenzo Stoakes,
Mike Rapoport, Suren Baghdasaryan, Vlastimil Babka,
Liam R . Howlett, Zi Yan, Jonathan Corbet, Shuah Khan,
Sean Christopherson, Paolo Bonzini, linux-mm, linux-kernel,
linux-doc, linux-kselftest, kvm
In-Reply-To: <20260415150900.3660575-1-usama.arif@linux.dev>
On Wed, Apr 15, 2026 at 08:08:59AM -0700, Usama Arif wrote:
> > + /* only toggleable features are allowed */
> > + if ((mode.enable | mode.disable) & ~UFFD_FEATURE_TOGGLEABLE)
> > + return -EINVAL;
>
> The commit message states "Only async features that were enabled at
> UFFDIO_API time may be toggled." However, the code only checks that
> the requested feature is in UFFD_FEATURE_TOGGLEABLE.
>
> Is it intentional that a user who opened a uffd without
> UFFD_FEATURE_MINOR_ASYNC can still enable it later via
> UFFDIO_SET_MODE?
The comment is stale. Will fix.
--
Kiryl Shutsemau / Kirill A. Shutemov
^ permalink raw reply
* Re: [PATCH v4 02/13] dt-bindings: leds: document Samsung S2M series PMIC RGB LED device
From: Kaustabh Chakraborty @ 2026-04-16 12:15 UTC (permalink / raw)
To: Krzysztof Kozlowski, Kaustabh Chakraborty
Cc: Lee Jones, Pavel Machek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, MyungJoo Ham, Chanwoo Choi, Sebastian Reichel,
André Draszik, Alexandre Belloni, Jonathan Corbet,
Shuah Khan, Nam Tran, Łukasz Lebiedziński, linux-leds,
devicetree, linux-kernel, linux-pm, linux-samsung-soc, linux-rtc,
linux-doc
In-Reply-To: <20260416-upbeat-archetypal-mantis-1ede48@quoll>
On 2026-04-16 10:23 +02:00, Krzysztof Kozlowski wrote:
> On Wed, Apr 15, 2026 at 11:00:16PM +0530, Kaustabh Chakraborty wrote:
>> On 2026-04-15 09:03 +02:00, Krzysztof Kozlowski wrote:
>> > On Tue, Apr 14, 2026 at 12:02:54PM +0530, Kaustabh Chakraborty wrote:
>> >> +description: |
>> >> + The Samsung S2M series PMIC RGB LED is a three-channel LED device with
>> >> + 8-bit brightness control for each channel, typically used as status
>> >> + indicators in mobile phones.
>> >> +
>> >> + This is a part of device tree bindings for S2M and S5M family of Power
>> >> + Management IC (PMIC).
>> >> +
>> >> + See also Documentation/devicetree/bindings/mfd/samsung,s2mps11.yaml for
>> >> + additional information and example.
>> >> +
>> >> +allOf:
>> >> + - $ref: common.yaml#
>> >
>> > Rob's comment is still valid:
>> > 1. How do you address one of three LEDs in non-RGB case?
>> > 2. Where is multi-color?
>>
>> Yes, multi-color should have been added here.
>>
>> >
>> > And based on this alone without other properties, I say this should be
>> > part of top-level schema. Separate node is fine, but no need for
>> > separate binding.
>>
>> BTW, for loading the sub-device driver via platform (as it won't be a
>> separate binding) the driver *must* be built-in. Although not related to
>> bindings, this seems counter-intuitive. I see the same problem with the
>
> I don't understand that comment. If it has nothing to do with the
> binding, what is the problem?
It was an unrelated user-space issue, so ignore.
>
> Best regards,
> Krzysztof
^ permalink raw reply
* [PATCH] docs: staging: fix various typos and grammar issues
From: Zhang Xiaolei @ 2026-04-16 10:58 UTC (permalink / raw)
To: corbet, ebiggers, andersson, mathieu.poirier
Cc: ardb, skhan, linux-crypto, linux-doc, linux-remoteproc,
linux-kernel, Zhang Xiaolei
Fix a few typographical and grammatical issues across several
staging documentation files to improve readability:
- crc32.rst: replace "decide in" with "decide on"
- lzo.rst: replace "independent on" with "independent of"
- remoteproc.rst: fix word order in dependent clause
- static-keys.rst: add hyphen to "low-level"
Signed-off-by: Zhang Xiaolei <zxl434815272@gmail.com>
---
Documentation/staging/crc32.rst | 2 +-
Documentation/staging/lzo.rst | 2 +-
Documentation/staging/remoteproc.rst | 2 +-
Documentation/staging/static-keys.rst | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Documentation/staging/crc32.rst b/Documentation/staging/crc32.rst
index 64f3dd430a6c..fc0d9564b99c 100644
--- a/Documentation/staging/crc32.rst
+++ b/Documentation/staging/crc32.rst
@@ -119,7 +119,7 @@ the byte-at-a-time table method, popularized by Dilip V. Sarwate,
v.31 no.8 (August 1988) p. 1008-1013.
Here, rather than just shifting one bit of the remainder to decide
-in the correct multiple to subtract, we can shift a byte at a time.
+on the correct multiple to subtract, we can shift a byte at a time.
This produces a 40-bit (rather than a 33-bit) intermediate remainder,
and the correct multiple of the polynomial to subtract is found using
a 256-entry lookup table indexed by the high 8 bits.
diff --git a/Documentation/staging/lzo.rst b/Documentation/staging/lzo.rst
index f65b51523014..2d48b2667dd2 100644
--- a/Documentation/staging/lzo.rst
+++ b/Documentation/staging/lzo.rst
@@ -75,7 +75,7 @@ Description
are called under the assumption that a certain number of bytes follow
because it has already been guaranteed before parsing the instructions.
They just have to "refill" this credit if they consume extra bytes. This
- is an implementation design choice independent on the algorithm or
+ is an implementation design choice independent of the algorithm or
encoding.
Versions
diff --git a/Documentation/staging/remoteproc.rst b/Documentation/staging/remoteproc.rst
index 5c226fa076d6..c117b060e76c 100644
--- a/Documentation/staging/remoteproc.rst
+++ b/Documentation/staging/remoteproc.rst
@@ -24,7 +24,7 @@ handlers, and then all rpmsg drivers will then just work
(for more information about the virtio-based rpmsg bus and its drivers,
please read Documentation/staging/rpmsg.rst).
Registration of other types of virtio devices is now also possible. Firmwares
-just need to publish what kind of virtio devices do they support, and then
+just need to publish what kind of virtio devices they support, and then
remoteproc will add those devices. This makes it possible to reuse the
existing virtio drivers with remote processor backends at a minimal development
cost.
diff --git a/Documentation/staging/static-keys.rst b/Documentation/staging/static-keys.rst
index b0a519f456cf..e8dc3a87c381 100644
--- a/Documentation/staging/static-keys.rst
+++ b/Documentation/staging/static-keys.rst
@@ -90,7 +90,7 @@ out-of-line true branch. Thus, changing branch direction is expensive but
branch selection is basically 'free'. That is the basic tradeoff of this
optimization.
-This lowlevel patching mechanism is called 'jump label patching', and it gives
+This low-level patching mechanism is called 'jump label patching', and it gives
the basis for the static keys facility.
Static key label API, usage and examples
--
2.53.0.windows.2
^ permalink raw reply related
* [PATCH v8 6/6] docs: iio: adc: ad4691: add driver documentation
From: Radu Sabau via B4 Relay @ 2026-04-16 9:18 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel, Jonathan Corbet, Shuah Khan
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
linux-doc, Radu Sabau
In-Reply-To: <20260416-ad4692-multichannel-sar-adc-driver-v8-0-c415bd048fa3@analog.com>
From: Radu Sabau <radu.sabau@analog.com>
Add RST documentation for the AD4691 family ADC driver covering
supported devices, IIO channels, operating modes, oversampling,
reference voltage, LDO supply, reset, GP pins, SPI offload support,
and buffer data format.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
Documentation/iio/ad4691.rst | 205 +++++++++++++++++++++++++++++++++++++++++++
Documentation/iio/index.rst | 1 +
MAINTAINERS | 1 +
3 files changed, 207 insertions(+)
diff --git a/Documentation/iio/ad4691.rst b/Documentation/iio/ad4691.rst
new file mode 100644
index 000000000000..38e2ad28a713
--- /dev/null
+++ b/Documentation/iio/ad4691.rst
@@ -0,0 +1,205 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+=============
+AD4691 driver
+=============
+
+ADC driver for Analog Devices Inc. AD4691 family of multichannel SAR ADCs.
+The module name is ``ad4691``.
+
+
+Supported devices
+=================
+
+The following chips are supported by this driver:
+
+* `AD4691 <https://www.analog.com/en/products/ad4691.html>`_ — 16-channel, 500 kSPS
+* `AD4692 <https://www.analog.com/en/products/ad4692.html>`_ — 16-channel, 1 MSPS
+* `AD4693 <https://www.analog.com/en/products/ad4693.html>`_ — 8-channel, 500 kSPS
+* `AD4694 <https://www.analog.com/en/products/ad4694.html>`_ — 8-channel, 1 MSPS
+
+
+IIO channels
+============
+
+Each physical ADC input maps to one IIO voltage channel. The AD4691 and AD4692
+expose 16 channels (``voltage0`` through ``voltage15``); the AD4693 and AD4694
+expose 8 channels (``voltage0`` through ``voltage7``).
+
+All channels share a common scale (``in_voltage_scale``), derived from the
+reference voltage. Each channel independently exposes:
+
+* ``in_voltageN_raw`` — single-shot ADC result
+* ``in_voltageN_sampling_frequency`` — per-channel effective output rate,
+ defined as the internal oscillator frequency divided by the channel's
+ oversampling ratio. Writing this attribute selects the nearest achievable
+ rate for the current OSR; the value read back reflects the actual rate after
+ snapping to the closest valid oscillator entry.
+* ``in_voltageN_sampling_frequency_available`` — list of achievable effective
+ rates for the channel's current oversampling ratio. The list updates
+ dynamically when the oversampling ratio changes.
+
+The following attributes are only available in CNV Burst Mode:
+
+* ``in_voltageN_oversampling_ratio`` — per-channel hardware oversampling depth;
+ see `Oversampling`_ below.
+* ``in_voltageN_oversampling_ratio_available`` — valid ratios: 1, 2, 4, 8, 16,
+ 32.
+
+
+Operating modes
+===============
+
+The driver supports two operating modes, selected automatically from the
+device tree at probe time.
+
+Manual Mode
+-----------
+
+Selected when no ``pwms`` property is present in the device tree. The CNV pin
+is tied to the SPI chip-select: every CS assertion triggers a conversion and
+returns the previous result. A user-defined IIO trigger (e.g. hrtimer trigger)
+drives the buffer.
+
+Oversampling is not supported in Manual Mode.
+
+CNV Burst Mode
+--------------
+
+Selected when a ``pwms`` property is present in the device tree. A PWM drives
+the CNV pin at the configured conversion rate. A GP pin wired to the SoC and
+declared in the device tree signals DATA_READY at the end of each burst,
+triggering a readout of all active channel results into the IIO buffer.
+
+The buffer output rate is controlled by the ``sampling_frequency`` attribute
+on the IIO buffer. In practice the PWM rate should be set low enough to allow
+the SPI readout to complete before the next conversion burst begins.
+
+Autonomous Mode (idle / single-shot)
+-------------------------------------
+
+When the IIO buffer is disabled, ``in_voltageN_raw`` reads perform a single
+conversion on the requested channel using the internal oscillator. The
+oscillator is started and stopped around each read to save power.
+
+
+Oversampling
+============
+
+In CNV Burst Mode each channel has an independent hardware accumulator that
+averages a configurable number of successive conversions. The result is always
+returned as a 16-bit mean, so ``realbits`` and ``storagebits`` are unaffected
+by the oversampling ratio. Valid ratios are 1, 2, 4, 8, 16 and 32; the default
+is 1 (no averaging). Oversampling is not supported in Manual Mode.
+
+.. code-block:: bash
+
+ # Set oversampling ratio to 16 on channel 0
+ echo 16 > /sys/bus/iio/devices/iio:device0/in_voltage0_oversampling_ratio
+
+ # Read the resulting effective sampling frequency
+ cat /sys/bus/iio/devices/iio:device0/in_voltage0_sampling_frequency
+
+Writing ``oversampling_ratio`` stores the new depth for that channel;
+the internal oscillator is unaffected. The effective rate read back via
+``in_voltageN_sampling_frequency`` becomes ``osc_freq / new_osr``
+automatically. ``oversampling_ratio`` and ``sampling_frequency`` are
+orthogonal: one controls averaging depth, the other controls the oscillator.
+
+All channels share one internal oscillator. Writing ``sampling_frequency`` for
+any channel updates the oscillator and therefore affects the effective rate
+read back from all other channels.
+
+
+Reference voltage
+=================
+
+The driver supports two reference configurations, mutually exclusive:
+
+* **External reference** (``ref-supply``): a voltage between 2.4 V and 5.25 V
+ supplied externally.
+* **Buffered internal reference** (``refin-supply``): an internal reference
+ buffer is enabled by the driver.
+
+Exactly one of ``ref-supply`` or ``refin-supply`` must be present in the
+device tree. The reference voltage determines the full-scale range reported
+via ``in_voltage_scale``.
+
+
+LDO supply
+==========
+
+The chip contains an internal LDO that powers part of the analog front-end.
+The supply configuration is mutually exclusive:
+
+* **External VDD** (``vdd-supply``): an external 1.8 V supply is used directly;
+ the internal LDO is disabled.
+* **Internal LDO** (``ldo-in-supply``): the internal LDO is enabled and fed
+ from the ``ldo-in`` regulator. Use this when no external 1.8 V VDD is present.
+
+Exactly one of ``vdd-supply`` or ``ldo-in-supply`` must be provided.
+
+
+Reset
+=====
+
+The driver supports two reset mechanisms:
+
+* **Hardware reset** (``reset-gpios`` in device tree): asserted at probe by
+ the reset controller framework.
+* **Software reset** (fallback when ``reset-gpios`` is absent): written
+ automatically at probe.
+
+
+GP pins and interrupts
+======================
+
+The chip exposes up to four general-purpose (GP) pins. In CNV Burst Mode
+(non-offload), one GP pin must be wired to an interrupt-capable SoC input and
+declared in the device tree using the ``interrupts`` and ``interrupt-names``
+properties. The ``interrupt-names`` value identifies which GP pin is used
+(``"gp0"`` through ``"gp3"``).
+
+Example device tree fragment::
+
+ adc@0 {
+ compatible = "adi,ad4692";
+ ...
+ interrupts = <17 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&gpio0>;
+ interrupt-names = "gp0";
+ };
+
+
+SPI offload support
+===================
+
+When a SPI offload engine (e.g. the AXI SPI Engine) is present, the driver
+uses DMA-backed transfers for CPU-independent, high-throughput data capture.
+SPI offload is detected automatically at probe; if no offload hardware is
+available the driver falls back to the software triggered-buffer path.
+
+Two SPI offload sub-modes exist:
+
+CNV Burst offload
+-----------------
+
+Used when a ``pwms`` property is present and SPI offload is available. The PWM
+drives CNV at the configured rate; on DATA_READY the offload engine reads all
+active channel results and streams them directly to the IIO DMA buffer with no
+CPU involvement. The GP pin used as DATA_READY trigger is supplied by the
+trigger-source consumer at buffer enable time; no ``interrupt-names`` entry is
+required.
+
+Manual offload
+--------------
+
+Used when no ``pwms`` property is present and SPI offload is available. A
+periodic SPI offload trigger controls the conversion rate and the offload engine
+streams results directly to the IIO DMA buffer.
+
+The ``sampling_frequency`` attribute on the IIO buffer controls the trigger
+rate (in Hz). The initial rate is 100 kHz.
+
+Oversampling is not supported in Manual Mode.
+
diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst
index ba3e609c6a13..007e0a1fcc5a 100644
--- a/Documentation/iio/index.rst
+++ b/Documentation/iio/index.rst
@@ -23,6 +23,7 @@ Industrial I/O Kernel Drivers
ad4000
ad4030
ad4062
+ ad4691
ad4695
ad7191
ad7380
diff --git a/MAINTAINERS b/MAINTAINERS
index 24e4502b8292..819d8b6eb6bb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1491,6 +1491,7 @@ S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
F: drivers/iio/adc/ad4691.c
+F: drivers/iio/adc/ad4691.rst
ANALOG DEVICES INC AD4695 DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
--
2.43.0
^ permalink raw reply related
* [PATCH v8 5/6] iio: adc: ad4691: add oversampling support
From: Radu Sabau via B4 Relay @ 2026-04-16 9:18 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel, Jonathan Corbet, Shuah Khan
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
linux-doc, Radu Sabau
In-Reply-To: <20260416-ad4692-multichannel-sar-adc-driver-v8-0-c415bd048fa3@analog.com>
From: Radu Sabau <radu.sabau@analog.com>
Add per-channel oversampling ratio (OSR) support for CNV burst mode.
The accumulator depth register (ACC_DEPTH_IN) is programmed with the
selected OSR at buffer enable time and before each single-shot read.
Supported OSR values: 1, 2, 4, 8, 16, 32.
Introduce AD4691_MANUAL_CHANNEL() for manual mode channels, which do
not expose the oversampling ratio attribute since OSR is not applicable
in that mode. A separate manual_channels array is added to
struct ad4691_channel_info and selected at probe time; offload paths
reuse the same arrays with num_channels capping access before the soft
timestamp entry.
in_voltageN_sampling_frequency represents the effective output rate for
channel N, defined as osc_freq / osr[N]. The chip has one internal
oscillator shared by all channels; each channel independently
accumulates osr[N] oscillator cycles before producing a result.
Writing sampling_frequency computes needed_osc = freq * osr[N] and
snaps down to the largest oscillator table entry that satisfies both
osc <= needed_osc and osc % osr[N] == 0, guaranteeing an exact integer
read-back. The result is stored in target_osc_freq_Hz and written to
OSC_FREQ_REG at buffer enable and single-shot time, so sampling_frequency
and oversampling_ratio can be set in any order.
in_voltageN_sampling_frequency_available is computed dynamically from
the channel's current OSR, listing only oscillator table entries that
divide evenly by osr[N], expressed as effective rates. The list becomes
sparser as OSR increases, capping at max_rate / osr[N].
Writing oversampling_ratio stores the new OSR for that channel;
target_osc_freq_Hz is left unchanged. The effective rate read back via
in_voltageN_sampling_frequency becomes target_osc_freq_Hz / new_osr
automatically. The two attributes are orthogonal: sampling_frequency
controls the oscillator, oversampling_ratio controls the averaging depth.
OSR defaults to 1 (no accumulation) for all channels.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/adc/ad4691.c | 251 +++++++++++++++++++++++++++++++++++++++++------
1 file changed, 221 insertions(+), 30 deletions(-)
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
index fbd44f595cbe..f000e4cde339 100644
--- a/drivers/iio/adc/ad4691.c
+++ b/drivers/iio/adc/ad4691.c
@@ -115,6 +115,7 @@ enum ad4691_ref_ctrl {
struct ad4691_channel_info {
const struct iio_chan_spec *channels;
+ const struct iio_chan_spec *manual_channels;
unsigned int num_channels;
};
@@ -125,7 +126,34 @@ struct ad4691_chip_info {
const struct ad4691_channel_info *offload_info;
};
+/* CNV burst mode channel — exposes oversampling ratio. */
#define AD4691_CHANNEL(ch) \
+ { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_separate_available = \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \
+ .channel = ch, \
+ .scan_index = ch, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_BE, \
+ }, \
+ }
+
+/*
+ * Manual mode channel — no oversampling ratio attribute. OSR is not
+ * supported in manual mode; ACC_DEPTH_IN is not configured during manual
+ * buffer enable.
+ */
+#define AD4691_MANUAL_CHANNEL(ch) \
{ \
.type = IIO_VOLTAGE, \
.indexed = 1, \
@@ -176,25 +204,65 @@ static const struct iio_chan_spec ad4693_channels[] = {
IIO_CHAN_SOFT_TIMESTAMP(8),
};
+static const struct iio_chan_spec ad4691_manual_channels[] = {
+ AD4691_MANUAL_CHANNEL(0),
+ AD4691_MANUAL_CHANNEL(1),
+ AD4691_MANUAL_CHANNEL(2),
+ AD4691_MANUAL_CHANNEL(3),
+ AD4691_MANUAL_CHANNEL(4),
+ AD4691_MANUAL_CHANNEL(5),
+ AD4691_MANUAL_CHANNEL(6),
+ AD4691_MANUAL_CHANNEL(7),
+ AD4691_MANUAL_CHANNEL(8),
+ AD4691_MANUAL_CHANNEL(9),
+ AD4691_MANUAL_CHANNEL(10),
+ AD4691_MANUAL_CHANNEL(11),
+ AD4691_MANUAL_CHANNEL(12),
+ AD4691_MANUAL_CHANNEL(13),
+ AD4691_MANUAL_CHANNEL(14),
+ AD4691_MANUAL_CHANNEL(15),
+ IIO_CHAN_SOFT_TIMESTAMP(16),
+};
+
+static const struct iio_chan_spec ad4693_manual_channels[] = {
+ AD4691_MANUAL_CHANNEL(0),
+ AD4691_MANUAL_CHANNEL(1),
+ AD4691_MANUAL_CHANNEL(2),
+ AD4691_MANUAL_CHANNEL(3),
+ AD4691_MANUAL_CHANNEL(4),
+ AD4691_MANUAL_CHANNEL(5),
+ AD4691_MANUAL_CHANNEL(6),
+ AD4691_MANUAL_CHANNEL(7),
+ IIO_CHAN_SOFT_TIMESTAMP(8),
+};
+
+static const int ad4691_oversampling_ratios[] = { 1, 2, 4, 8, 16, 32 };
+
static const struct ad4691_channel_info ad4691_sw_info = {
.channels = ad4691_channels,
+ .manual_channels = ad4691_manual_channels,
.num_channels = ARRAY_SIZE(ad4691_channels),
};
static const struct ad4691_channel_info ad4693_sw_info = {
.channels = ad4693_channels,
+ .manual_channels = ad4693_manual_channels,
.num_channels = ARRAY_SIZE(ad4693_channels),
};
static const struct ad4691_channel_info ad4691_offload_info = {
.channels = ad4691_channels,
- /* Exclude the soft timestamp entry; num_channels caps access. */
+ /*
+ * Offload paths share the SW channel arrays. num_channels caps access
+ * before the soft timestamp entry, so no separate array is needed.
+ */
+ .manual_channels = ad4691_manual_channels,
.num_channels = ARRAY_SIZE(ad4691_channels) - 1,
};
static const struct ad4691_channel_info ad4693_offload_info = {
.channels = ad4693_channels,
- /* Exclude the soft timestamp entry; num_channels caps access. */
+ .manual_channels = ad4693_manual_channels,
.num_channels = ARRAY_SIZE(ad4693_channels) - 1,
};
@@ -269,6 +337,19 @@ struct ad4691_state {
int irq;
int vref_uV;
u32 cnv_period_ns;
+ /*
+ * Snapped oscillator frequency (Hz) shared by all channels. Set when
+ * sampling_frequency or oversampling_ratio is written; written to
+ * OSC_FREQ_REG at buffer enable and single-shot time so both attributes
+ * can be set in any order. Reading in_voltageN_sampling_frequency
+ * returns target_osc_freq_Hz / osr[N] — the effective rate for that
+ * channel given its oversampling ratio.
+ */
+ u32 target_osc_freq_Hz;
+ /* Per-channel oversampling ratio; always 1 in manual mode. */
+ u8 osr[16];
+ /* Scratch buffer for read_avail SAMP_FREQ; content is OSR-dependent. */
+ int samp_freq_avail[ARRAY_SIZE(ad4691_osc_freqs_Hz)];
bool manual_mode;
bool refbuf_en;
@@ -489,6 +570,18 @@ static const struct regmap_config ad4691_regmap_config = {
.cache_type = REGCACHE_MAPLE,
};
+/* Write target_osc_freq_Hz to OSC_FREQ_REG. Called at use time. */
+static int ad4691_write_osc_freq(struct ad4691_state *st)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
+ if (ad4691_osc_freqs_Hz[i] == st->target_osc_freq_Hz)
+ return regmap_write(st->regmap, AD4691_OSC_FREQ_REG, i);
+ }
+ return -EINVAL;
+}
+
/*
* Index 0 in ad4691_osc_freqs_Hz is 1 MHz — valid only for AD4692/AD4694
* (max_rate == 1 MHz). AD4691/AD4693 cap at 500 kHz so their valid range
@@ -499,36 +592,58 @@ static unsigned int ad4691_samp_freq_start(const struct ad4691_chip_info *info)
return (info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
}
-static int ad4691_get_sampling_freq(struct ad4691_state *st, int *val)
+/*
+ * Find the largest oscillator table entry that is both <= needed_osc and
+ * evenly divisible by osr (guaranteeing an integer effective rate on
+ * read-back). Returns 0 if no such entry exists in the chip's valid range.
+ */
+static unsigned int ad4691_find_osc_freq(struct ad4691_state *st,
+ unsigned int needed_osc,
+ unsigned int osr)
{
- unsigned int reg_val;
- int ret;
+ unsigned int start = ad4691_samp_freq_start(st->info);
- ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val);
- if (ret)
- return ret;
+ for (unsigned int i = start; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
+ if ((unsigned int)ad4691_osc_freqs_Hz[i] > needed_osc)
+ continue;
+ if (ad4691_osc_freqs_Hz[i] % osr != 0)
+ continue;
+ return ad4691_osc_freqs_Hz[i];
+ }
+ return 0;
+}
- *val = ad4691_osc_freqs_Hz[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)];
+static int ad4691_get_sampling_freq(struct ad4691_state *st, u8 osr, int *val)
+{
+ *val = st->target_osc_freq_Hz / osr;
return IIO_VAL_INT;
}
-static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq)
+static int ad4691_set_sampling_freq(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int freq)
{
struct ad4691_state *st = iio_priv(indio_dev);
- unsigned int start = ad4691_samp_freq_start(st->info);
+ unsigned int osr = st->osr[chan->channel];
+ unsigned int found;
IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
if (IIO_DEV_ACQUIRE_FAILED(claim))
return -EBUSY;
- for (unsigned int i = start; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
- if (ad4691_osc_freqs_Hz[i] != freq)
- continue;
- return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG,
- AD4691_OSC_FREQ_MASK, i);
- }
+ if (freq <= 0 || (unsigned int)freq > st->info->max_rate / osr)
+ return -EINVAL;
- return -EINVAL;
+ found = ad4691_find_osc_freq(st, (unsigned int)freq * osr, osr);
+ if (!found)
+ return -EINVAL;
+
+ /*
+ * Store the snapped oscillator frequency; OSC_FREQ_REG is written at
+ * buffer enable and single-shot time so that sampling_frequency and
+ * oversampling_ratio can be set in any order.
+ */
+ st->target_osc_freq_Hz = found;
+ return 0;
}
static int ad4691_read_avail(struct iio_dev *indio_dev,
@@ -540,10 +655,30 @@ static int ad4691_read_avail(struct iio_dev *indio_dev,
unsigned int start = ad4691_samp_freq_start(st->info);
switch (mask) {
- case IIO_CHAN_INFO_SAMP_FREQ:
- *vals = &ad4691_osc_freqs_Hz[start];
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ unsigned int osr = st->osr[chan->channel];
+ int n = 0;
+
+ /*
+ * Only oscillator frequencies evenly divisible by the channel's
+ * OSR yield an integer effective rate; expose those as effective
+ * rates (osc / osr) so the user works entirely in output-sample
+ * space.
+ */
+ for (unsigned int i = start; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
+ if (ad4691_osc_freqs_Hz[i] % osr != 0)
+ continue;
+ st->samp_freq_avail[n++] = ad4691_osc_freqs_Hz[i] / osr;
+ }
+ *vals = st->samp_freq_avail;
*type = IIO_VAL_INT;
- *length = ARRAY_SIZE(ad4691_osc_freqs_Hz) - start;
+ *length = n;
+ return IIO_AVAIL_LIST;
+ }
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ *vals = ad4691_oversampling_ratios;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(ad4691_oversampling_ratios);
return IIO_AVAIL_LIST;
default:
return -EINVAL;
@@ -554,7 +689,7 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val)
{
struct ad4691_state *st = iio_priv(indio_dev);
- unsigned int reg_val, osc_idx, period_us;
+ unsigned int reg_val, period_us;
int ret;
guard(mutex)(&st->lock);
@@ -575,7 +710,12 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
if (ret)
return ret;
- ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val);
+ ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(chan->channel),
+ st->osr[chan->channel]);
+ if (ret)
+ return ret;
+
+ ret = ad4691_write_osc_freq(st);
if (ret)
return ret;
@@ -583,9 +723,12 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
if (ret)
return ret;
- osc_idx = FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val);
- /* Wait 2 oscillator periods for the conversion to complete. */
- period_us = DIV_ROUND_UP(2UL * USEC_PER_SEC, ad4691_osc_freqs_Hz[osc_idx]);
+ /*
+ * Wait osr + 1 oscillator periods: osr for accumulation, +1 for the
+ * pipeline margin (one extra period ensures the final result is ready).
+ */
+ period_us = DIV_ROUND_UP((unsigned long)(st->osr[chan->channel] + 1) * USEC_PER_SEC,
+ st->target_osc_freq_Hz);
fsleep(period_us);
ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
@@ -620,7 +763,10 @@ static int ad4691_read_raw(struct iio_dev *indio_dev,
return ad4691_single_shot_read(indio_dev, chan, val);
}
case IIO_CHAN_INFO_SAMP_FREQ:
- return ad4691_get_sampling_freq(st, val);
+ return ad4691_get_sampling_freq(st, st->osr[chan->channel], val);
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ *val = st->osr[chan->channel];
+ return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = st->vref_uV / (MICRO / MILLI);
*val2 = chan->scan_type.realbits;
@@ -634,9 +780,29 @@ static int ad4691_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
+ struct ad4691_state *st = iio_priv(indio_dev);
+
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
- return ad4691_set_sampling_freq(indio_dev, val);
+ return ad4691_set_sampling_freq(indio_dev, chan, val);
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(ad4691_oversampling_ratios); i++) {
+ if (ad4691_oversampling_ratios[i] != val)
+ continue;
+ /*
+ * Store the new OSR; target_osc_freq_Hz is unchanged.
+ * The effective rate read back via in_voltageN_sampling_frequency
+ * becomes target_osc_freq_Hz / new_osr automatically.
+ */
+ st->osr[chan->channel] = val;
+ return 0;
+ }
+ return -EINVAL;
+ }
default:
return -EINVAL;
}
@@ -691,6 +857,10 @@ static int ad4691_enter_conversion_mode(struct ad4691_state *st)
return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
AD4691_MANUAL_MODE, AD4691_MANUAL_MODE);
+ ret = ad4691_write_osc_freq(st);
+ if (ret)
+ return ret;
+
ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE);
if (ret)
@@ -844,6 +1014,12 @@ static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev)
if (ret)
goto err_unoptimize;
+ iio_for_each_active_channel(indio_dev, i) {
+ ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(i), st->osr[i]);
+ if (ret)
+ goto err_unoptimize;
+ }
+
ret = ad4691_enter_conversion_mode(st);
if (ret)
goto err_unoptimize;
@@ -995,6 +1171,12 @@ static int ad4691_cnv_burst_offload_buffer_postenable(struct iio_dev *indio_dev)
if (ret)
return ret;
+ iio_for_each_active_channel(indio_dev, bit) {
+ ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(bit), st->osr[bit]);
+ if (ret)
+ return ret;
+ }
+
ret = ad4691_enter_conversion_mode(st);
if (ret)
return ret;
@@ -1361,6 +1543,8 @@ static int ad4691_config(struct ad4691_state *st)
if (ret)
return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n");
+ st->target_osc_freq_Hz = ad4691_osc_freqs_Hz[ad4691_samp_freq_start(st->info)];
+
ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
if (ret)
@@ -1518,6 +1702,7 @@ static int ad4691_probe(struct spi_device *spi)
st = iio_priv(indio_dev);
st->spi = spi;
st->info = spi_get_device_match_data(spi);
+ memset(st->osr, 1, sizeof(st->osr));
ret = devm_mutex_init(dev, &st->lock);
if (ret)
@@ -1552,11 +1737,17 @@ static int ad4691_probe(struct spi_device *spi)
indio_dev->modes = INDIO_DIRECT_MODE;
if (spi_offload) {
- indio_dev->channels = st->info->offload_info->channels;
+ if (st->manual_mode)
+ indio_dev->channels = st->info->offload_info->manual_channels;
+ else
+ indio_dev->channels = st->info->offload_info->channels;
indio_dev->num_channels = st->info->offload_info->num_channels;
ret = ad4691_setup_offload(indio_dev, st, spi_offload);
} else {
- indio_dev->channels = st->info->sw_info->channels;
+ if (st->manual_mode)
+ indio_dev->channels = st->info->sw_info->manual_channels;
+ else
+ indio_dev->channels = st->info->sw_info->channels;
indio_dev->num_channels = st->info->sw_info->num_channels;
ret = ad4691_setup_triggered_buffer(indio_dev, st);
}
--
2.43.0
^ permalink raw reply related
* [PATCH v8 2/6] iio: adc: ad4691: add initial driver for AD4691 family
From: Radu Sabau via B4 Relay @ 2026-04-16 9:18 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel, Jonathan Corbet, Shuah Khan
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
linux-doc, Radu Sabau
In-Reply-To: <20260416-ad4692-multichannel-sar-adc-driver-v8-0-c415bd048fa3@analog.com>
From: Radu Sabau <radu.sabau@analog.com>
Add support for the Analog Devices AD4691 family of high-speed,
low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS),
AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and
AD4694 (8-ch, 1 MSPS).
The driver implements a custom regmap layer over raw SPI to handle the
device's mixed 1/2/3/4-byte register widths and uses the standard IIO
read_raw/write_raw interface for single-channel reads.
The chip idles in Autonomous Mode so that single-shot read_raw can use
the internal oscillator without disturbing the hardware configuration.
Three voltage supply domains are managed: avdd (required), vio, and a
reference supply on either the REF pin (ref-supply, external buffer)
or the REFIN pin (refin-supply, uses the on-chip reference buffer;
REFBUF_EN is set accordingly). Hardware reset is performed via
the reset controller framework; a software reset through SPI_CONFIG_A
is used as fallback when no hardware reset is available.
Accumulator channel masking for single-shot reads uses ACC_MASK_REG via
an ADDR_DESCENDING SPI write, which covers both mask bytes in a single
16-bit transfer.
Reviewed-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 11 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ad4691.c | 714 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 727 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 438ca850fa1c..24e4502b8292 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1490,6 +1490,7 @@ L: linux-iio@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
+F: drivers/iio/adc/ad4691.c
ANALOG DEVICES INC AD4695 DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 60038ae8dfc4..3685a03aa8dc 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -139,6 +139,17 @@ config AD4170_4
To compile this driver as a module, choose M here: the module will be
called ad4170-4.
+config AD4691
+ tristate "Analog Devices AD4691 Family ADC Driver"
+ depends on SPI
+ select REGMAP
+ help
+ Say yes here to build support for Analog Devices AD4691 Family MuxSAR
+ SPI analog to digital converters (ADC).
+
+ To compile this driver as a module, choose M here: the module will be
+ called ad4691.
+
config AD4695
tristate "Analog Device AD4695 ADC Driver"
depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index c76550415ff1..4ac1ea09d773 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_AD4080) += ad4080.o
obj-$(CONFIG_AD4130) += ad4130.o
obj-$(CONFIG_AD4134) += ad4134.o
obj-$(CONFIG_AD4170_4) += ad4170-4.o
+obj-$(CONFIG_AD4691) += ad4691.o
obj-$(CONFIG_AD4695) += ad4695.o
obj-$(CONFIG_AD4851) += ad4851.o
obj-$(CONFIG_AD7091R) += ad7091r-base.o
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
new file mode 100644
index 000000000000..61b5f74205af
--- /dev/null
+++ b/drivers/iio/adc/ad4691.c
@@ -0,0 +1,714 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024-2026 Analog Devices, Inc.
+ * Author: Radu Sabau <radu.sabau@analog.com>
+ */
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/limits.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/spi/spi.h>
+#include <linux/units.h>
+#include <linux/unaligned.h>
+
+#include <linux/iio/iio.h>
+
+#define AD4691_VREF_uV_MIN 2400000
+#define AD4691_VREF_uV_MAX 5250000
+#define AD4691_VREF_2P5_uV_MAX 2750000
+#define AD4691_VREF_3P0_uV_MAX 3250000
+#define AD4691_VREF_3P3_uV_MAX 3750000
+#define AD4691_VREF_4P096_uV_MAX 4500000
+
+#define AD4691_SPI_CONFIG_A_REG 0x000
+#define AD4691_SW_RESET (BIT(7) | BIT(0))
+
+#define AD4691_STATUS_REG 0x014
+#define AD4691_CLAMP_STATUS1_REG 0x01A
+#define AD4691_CLAMP_STATUS2_REG 0x01B
+#define AD4691_DEVICE_SETUP 0x020
+#define AD4691_LDO_EN BIT(4)
+#define AD4691_REF_CTRL 0x021
+#define AD4691_REF_CTRL_MASK GENMASK(4, 2)
+#define AD4691_REFBUF_EN BIT(0)
+#define AD4691_OSC_FREQ_REG 0x023
+#define AD4691_OSC_FREQ_MASK GENMASK(3, 0)
+#define AD4691_STD_SEQ_CONFIG 0x025
+#define AD4691_SPARE_CONTROL 0x02A
+
+#define AD4691_OSC_EN_REG 0x180
+#define AD4691_STATE_RESET_REG 0x181
+#define AD4691_STATE_RESET_ALL 0x01
+#define AD4691_ADC_SETUP 0x182
+#define AD4691_ADC_MODE_MASK GENMASK(1, 0)
+#define AD4691_AUTONOMOUS_MODE 0x02
+/*
+ * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a
+ * 16-bit BE value to 0x185 auto-decrements to 0x184 for the second byte.
+ */
+#define AD4691_ACC_MASK_REG 0x185
+#define AD4691_ACC_DEPTH_IN(n) (0x186 + (n))
+#define AD4691_GPIO_MODE1_REG 0x196
+#define AD4691_GPIO_MODE2_REG 0x197
+#define AD4691_GPIO_READ 0x1A0
+#define AD4691_ACC_STATUS_FULL1_REG 0x1B0
+#define AD4691_ACC_STATUS_FULL2_REG 0x1B1
+#define AD4691_ACC_STATUS_OVERRUN1_REG 0x1B2
+#define AD4691_ACC_STATUS_OVERRUN2_REG 0x1B3
+#define AD4691_ACC_STATUS_SAT1_REG 0x1B4
+#define AD4691_ACC_STATUS_SAT2_REG 0x1BE
+#define AD4691_ACC_SAT_OVR_REG(n) (0x1C0 + (n))
+#define AD4691_AVG_IN(n) (0x201 + (2 * (n)))
+#define AD4691_AVG_STS_IN(n) (0x222 + (3 * (n)))
+#define AD4691_ACC_IN(n) (0x252 + (3 * (n)))
+#define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n)))
+
+static const char * const ad4691_supplies[] = { "avdd", "vio" };
+
+enum ad4691_ref_ctrl {
+ AD4691_VREF_2P5 = 0,
+ AD4691_VREF_3P0 = 1,
+ AD4691_VREF_3P3 = 2,
+ AD4691_VREF_4P096 = 3,
+ AD4691_VREF_5P0 = 4,
+};
+
+struct ad4691_chip_info {
+ const struct iio_chan_spec *channels;
+ const char *name;
+ unsigned int num_channels;
+ unsigned int max_rate;
+};
+
+#define AD4691_CHANNEL(ch) \
+ { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
+ | BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_separate_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \
+ .channel = ch, \
+ .scan_index = ch, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ }, \
+ }
+
+static const struct iio_chan_spec ad4691_channels[] = {
+ AD4691_CHANNEL(0),
+ AD4691_CHANNEL(1),
+ AD4691_CHANNEL(2),
+ AD4691_CHANNEL(3),
+ AD4691_CHANNEL(4),
+ AD4691_CHANNEL(5),
+ AD4691_CHANNEL(6),
+ AD4691_CHANNEL(7),
+ AD4691_CHANNEL(8),
+ AD4691_CHANNEL(9),
+ AD4691_CHANNEL(10),
+ AD4691_CHANNEL(11),
+ AD4691_CHANNEL(12),
+ AD4691_CHANNEL(13),
+ AD4691_CHANNEL(14),
+ AD4691_CHANNEL(15),
+};
+
+static const struct iio_chan_spec ad4693_channels[] = {
+ AD4691_CHANNEL(0),
+ AD4691_CHANNEL(1),
+ AD4691_CHANNEL(2),
+ AD4691_CHANNEL(3),
+ AD4691_CHANNEL(4),
+ AD4691_CHANNEL(5),
+ AD4691_CHANNEL(6),
+ AD4691_CHANNEL(7),
+};
+
+/*
+ * Internal oscillator frequency table. Index is the OSC_FREQ_REG[3:0] value.
+ * Index 0 (1 MHz) is only valid for AD4692/AD4694; AD4691/AD4693 support
+ * up to 500 kHz and use index 1 as their highest valid rate.
+ */
+static const int ad4691_osc_freqs_Hz[] = {
+ [0x0] = 1000000,
+ [0x1] = 500000,
+ [0x2] = 400000,
+ [0x3] = 250000,
+ [0x4] = 200000,
+ [0x5] = 167000,
+ [0x6] = 133000,
+ [0x7] = 125000,
+ [0x8] = 100000,
+ [0x9] = 50000,
+ [0xA] = 25000,
+ [0xB] = 12500,
+ [0xC] = 10000,
+ [0xD] = 5000,
+ [0xE] = 2500,
+ [0xF] = 1250,
+};
+
+static const struct ad4691_chip_info ad4691_chip_info = {
+ .channels = ad4691_channels,
+ .name = "ad4691",
+ .num_channels = ARRAY_SIZE(ad4691_channels),
+ .max_rate = 500 * HZ_PER_KHZ,
+};
+
+static const struct ad4691_chip_info ad4692_chip_info = {
+ .channels = ad4691_channels,
+ .name = "ad4692",
+ .num_channels = ARRAY_SIZE(ad4691_channels),
+ .max_rate = 1 * HZ_PER_MHZ,
+};
+
+static const struct ad4691_chip_info ad4693_chip_info = {
+ .channels = ad4693_channels,
+ .name = "ad4693",
+ .num_channels = ARRAY_SIZE(ad4693_channels),
+ .max_rate = 500 * HZ_PER_KHZ,
+};
+
+static const struct ad4691_chip_info ad4694_chip_info = {
+ .channels = ad4693_channels,
+ .name = "ad4694",
+ .num_channels = ARRAY_SIZE(ad4693_channels),
+ .max_rate = 1 * HZ_PER_MHZ,
+};
+
+struct ad4691_state {
+ const struct ad4691_chip_info *info;
+ struct regmap *regmap;
+ int vref_uV;
+ bool refbuf_en;
+ bool ldo_en;
+ /*
+ * Synchronize access to members of the driver state, and ensure
+ * atomicity of consecutive SPI operations.
+ */
+ struct mutex lock;
+};
+
+static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct spi_device *spi = context;
+ u8 tx[2], rx[4];
+ int ret;
+
+ /* Set bit 15 to mark the operation as READ. */
+ put_unaligned_be16(0x8000 | reg, tx);
+
+ switch (reg) {
+ case 0 ... AD4691_OSC_FREQ_REG:
+ case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
+ ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 1);
+ if (ret)
+ return ret;
+ *val = rx[0];
+ return 0;
+ case AD4691_STD_SEQ_CONFIG:
+ case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
+ ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 2);
+ if (ret)
+ return ret;
+ *val = get_unaligned_be16(rx);
+ return 0;
+ case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
+ case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
+ ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 3);
+ if (ret)
+ return ret;
+ *val = get_unaligned_be24(rx);
+ return 0;
+ case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
+ ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 4);
+ if (ret)
+ return ret;
+ *val = get_unaligned_be32(rx);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4691_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct spi_device *spi = context;
+ u8 tx[4];
+
+ put_unaligned_be16(reg, tx);
+
+ switch (reg) {
+ case 0 ... AD4691_OSC_FREQ_REG:
+ case AD4691_SPARE_CONTROL ... AD4691_ACC_MASK_REG - 1:
+ case AD4691_ACC_MASK_REG + 1 ... AD4691_GPIO_MODE2_REG:
+ if (val > U8_MAX)
+ return -EINVAL;
+ tx[2] = val;
+ return spi_write_then_read(spi, tx, 3, NULL, 0);
+ case AD4691_ACC_MASK_REG:
+ case AD4691_STD_SEQ_CONFIG:
+ if (val > U16_MAX)
+ return -EINVAL;
+ put_unaligned_be16(val, &tx[2]);
+ return spi_write_then_read(spi, tx, 4, NULL, 0);
+ default:
+ return -EINVAL;
+ }
+}
+
+static bool ad4691_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case AD4691_STATUS_REG:
+ case AD4691_CLAMP_STATUS1_REG:
+ case AD4691_CLAMP_STATUS2_REG:
+ case AD4691_GPIO_READ:
+ case AD4691_ACC_STATUS_FULL1_REG ... AD4691_ACC_STATUS_SAT2_REG:
+ case AD4691_ACC_SAT_OVR_REG(0) ... AD4691_ACC_SAT_OVR_REG(15):
+ case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
+ case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
+ case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
+ case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ad4691_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0 ... AD4691_OSC_FREQ_REG:
+ case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
+ case AD4691_STD_SEQ_CONFIG:
+ case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
+ case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
+ case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
+ case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ad4691_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0 ... AD4691_OSC_FREQ_REG:
+ case AD4691_STD_SEQ_CONFIG:
+ case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config ad4691_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 32,
+ .reg_read = ad4691_reg_read,
+ .reg_write = ad4691_reg_write,
+ .volatile_reg = ad4691_volatile_reg,
+ .readable_reg = ad4691_readable_reg,
+ .writeable_reg = ad4691_writeable_reg,
+ .max_register = AD4691_ACC_STS_DATA(15),
+ .cache_type = REGCACHE_MAPLE,
+};
+
+/*
+ * Index 0 in ad4691_osc_freqs_Hz is 1 MHz — valid only for AD4692/AD4694
+ * (max_rate == 1 MHz). AD4691/AD4693 cap at 500 kHz so their valid range
+ * starts at index 1.
+ */
+static unsigned int ad4691_samp_freq_start(const struct ad4691_chip_info *info)
+{
+ return (info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
+}
+
+static int ad4691_get_sampling_freq(struct ad4691_state *st, int *val)
+{
+ unsigned int reg_val;
+ int ret;
+
+ ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val);
+ if (ret)
+ return ret;
+
+ *val = ad4691_osc_freqs_Hz[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)];
+ return IIO_VAL_INT;
+}
+
+static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ unsigned int start = ad4691_samp_freq_start(st->info);
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ for (unsigned int i = start; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
+ if (ad4691_osc_freqs_Hz[i] != freq)
+ continue;
+ return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG,
+ AD4691_OSC_FREQ_MASK, i);
+ }
+
+ return -EINVAL;
+}
+
+static int ad4691_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type,
+ int *length, long mask)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ unsigned int start = ad4691_samp_freq_start(st->info);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = &ad4691_osc_freqs_Hz[start];
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(ad4691_osc_freqs_Hz) - start;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4691_single_shot_read(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ unsigned int reg_val, osc_idx, period_us;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ /* Use AUTONOMOUS mode for single-shot reads. */
+ ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+ AD4691_STATE_RESET_ALL);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+ BIT(chan->channel));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
+ ~BIT(chan->channel) & GENMASK(15, 0));
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 1);
+ if (ret)
+ return ret;
+
+ osc_idx = FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val);
+ /* Wait 2 oscillator periods for the conversion to complete. */
+ period_us = DIV_ROUND_UP(2UL * USEC_PER_SEC, ad4691_osc_freqs_Hz[osc_idx]);
+ fsleep(period_us);
+
+ ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, AD4691_AVG_IN(chan->channel), ®_val);
+ if (ret)
+ return ret;
+
+ *val = reg_val;
+
+ ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+}
+
+static int ad4691_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long info)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW: {
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ return ad4691_single_shot_read(indio_dev, chan, val);
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return ad4691_get_sampling_freq(st, val);
+ case IIO_CHAN_INFO_SCALE:
+ *val = st->vref_uV / (MICRO / MILLI);
+ *val2 = chan->scan_type.realbits;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4691_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return ad4691_set_sampling_freq(indio_dev, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int writeval, unsigned int *readval)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ if (readval)
+ return regmap_read(st->regmap, reg, readval);
+
+ return regmap_write(st->regmap, reg, writeval);
+}
+
+static const struct iio_info ad4691_info = {
+ .read_raw = &ad4691_read_raw,
+ .write_raw = &ad4691_write_raw,
+ .read_avail = &ad4691_read_avail,
+ .debugfs_reg_access = &ad4691_reg_access,
+};
+
+static int ad4691_regulator_setup(struct ad4691_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ int ret;
+
+ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad4691_supplies),
+ ad4691_supplies);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get and enable supplies\n");
+
+ /*
+ * vdd-supply and ldo-in-supply are mutually exclusive:
+ * vdd-supply present → external 1.8V VDD; disable internal LDO.
+ * vdd-supply absent → enable internal LDO fed from ldo-in-supply.
+ * Having both simultaneously is strongly inadvisable per the datasheet.
+ */
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret == -ENODEV) {
+ ret = devm_regulator_get_enable(dev, "ldo-in");
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get and enable LDO-IN\n");
+ st->ldo_en = true;
+ } else if (ret) {
+ return dev_err_probe(dev, ret, "Failed to get and enable VDD\n");
+ }
+
+ st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "ref");
+ if (st->vref_uV == -ENODEV) {
+ st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "refin");
+ st->refbuf_en = true;
+ }
+ if (st->vref_uV < 0)
+ return dev_err_probe(dev, st->vref_uV,
+ "Failed to get reference supply\n");
+
+ if (st->vref_uV < AD4691_VREF_uV_MIN || st->vref_uV > AD4691_VREF_uV_MAX)
+ return dev_err_probe(dev, -EINVAL,
+ "vref(%d) must be in the range [%u...%u]\n",
+ st->vref_uV, AD4691_VREF_uV_MIN,
+ AD4691_VREF_uV_MAX);
+
+ return 0;
+}
+
+static int ad4691_reset(struct ad4691_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ struct reset_control *rst;
+
+ rst = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(rst))
+ return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n");
+
+ if (rst) {
+ /*
+ * reset_gpio_probe() already drives the pin asserted, so the
+ * device is held in reset before we get here.
+ * devm_reset_control_get_optional_exclusive_deasserted() cannot
+ * be used because it deasserts immediately without delay; the
+ * datasheet (Table 5) requires a ≥300 µs reset pulse width
+ * before deassertion.
+ */
+ fsleep(300);
+ return reset_control_deassert(rst);
+ }
+
+ /* No hardware reset available, fall back to software reset. */
+ return regmap_write(st->regmap, AD4691_SPI_CONFIG_A_REG,
+ AD4691_SW_RESET);
+}
+
+static int ad4691_config(struct ad4691_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ enum ad4691_ref_ctrl ref_val;
+ unsigned int val;
+ int ret;
+
+ switch (st->vref_uV) {
+ case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX:
+ ref_val = AD4691_VREF_2P5;
+ break;
+ case AD4691_VREF_2P5_uV_MAX + 1 ... AD4691_VREF_3P0_uV_MAX:
+ ref_val = AD4691_VREF_3P0;
+ break;
+ case AD4691_VREF_3P0_uV_MAX + 1 ... AD4691_VREF_3P3_uV_MAX:
+ ref_val = AD4691_VREF_3P3;
+ break;
+ case AD4691_VREF_3P3_uV_MAX + 1 ... AD4691_VREF_4P096_uV_MAX:
+ ref_val = AD4691_VREF_4P096;
+ break;
+ case AD4691_VREF_4P096_uV_MAX + 1 ... AD4691_VREF_uV_MAX:
+ ref_val = AD4691_VREF_5P0;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "Unsupported vref voltage: %d uV\n",
+ st->vref_uV);
+ }
+
+ val = FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val);
+ if (st->refbuf_en)
+ val |= AD4691_REFBUF_EN;
+
+ ret = regmap_write(st->regmap, AD4691_REF_CTRL, val);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");
+
+ ret = regmap_assign_bits(st->regmap, AD4691_DEVICE_SETUP,
+ AD4691_LDO_EN, st->ldo_en);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write DEVICE_SETUP\n");
+
+ /*
+ * Set the internal oscillator to the highest rate this chip supports.
+ * Index 0 (1 MHz) exceeds the 500 kHz max of AD4691/AD4693, so those
+ * chips start at index 1 (500 kHz).
+ */
+ ret = regmap_write(st->regmap, AD4691_OSC_FREQ_REG,
+ ad4691_samp_freq_start(st->info));
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n");
+
+ ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
+ AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n");
+
+ return 0;
+}
+
+static int ad4691_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ad4691_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->info = spi_get_device_match_data(spi);
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ st->regmap = devm_regmap_init(dev, NULL, spi, &ad4691_regmap_config);
+ if (IS_ERR(st->regmap))
+ return dev_err_probe(dev, PTR_ERR(st->regmap),
+ "Failed to initialize regmap\n");
+
+ ret = ad4691_regulator_setup(st);
+ if (ret)
+ return ret;
+
+ ret = ad4691_reset(st);
+ if (ret)
+ return ret;
+
+ ret = ad4691_config(st);
+ if (ret)
+ return ret;
+
+ indio_dev->name = st->info->name;
+ indio_dev->info = &ad4691_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ indio_dev->channels = st->info->channels;
+ indio_dev->num_channels = st->info->num_channels;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id ad4691_of_match[] = {
+ { .compatible = "adi,ad4691", .data = &ad4691_chip_info },
+ { .compatible = "adi,ad4692", .data = &ad4692_chip_info },
+ { .compatible = "adi,ad4693", .data = &ad4693_chip_info },
+ { .compatible = "adi,ad4694", .data = &ad4694_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad4691_of_match);
+
+static const struct spi_device_id ad4691_id[] = {
+ { "ad4691", (kernel_ulong_t)&ad4691_chip_info },
+ { "ad4692", (kernel_ulong_t)&ad4692_chip_info },
+ { "ad4693", (kernel_ulong_t)&ad4693_chip_info },
+ { "ad4694", (kernel_ulong_t)&ad4694_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ad4691_id);
+
+static struct spi_driver ad4691_driver = {
+ .driver = {
+ .name = "ad4691",
+ .of_match_table = ad4691_of_match,
+ },
+ .probe = ad4691_probe,
+ .id_table = ad4691_id,
+};
+module_spi_driver(ad4691_driver);
+
+MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver");
+MODULE_LICENSE("GPL");
--
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