* Re: [PATCH v3 0/5] ASoC / rpmsg / remoteproc / soc: qcom: Constify buffer passed to send functions
From: Bjorn Andersson @ 2026-04-06 15:10 UTC (permalink / raw)
To: Mathieu Poirier, Matthias Brugger, AngeloGioacchino Del Regno,
Srinivas Kandagatla, Konrad Dybcio, Liam Girdwood, Mark Brown,
Jaroslav Kysela, Takashi Iwai, Mauro Carvalho Chehab,
Krzysztof Kozlowski
Cc: linux-remoteproc, linux-kernel, linux-arm-kernel, linux-mediatek,
linux-arm-msm, linux-sound, linux-media
In-Reply-To: <20260317-rpmsg-send-const-v3-0-4d7fd27f037f@oss.qualcomm.com>
On Tue, 17 Mar 2026 13:36:49 +0100, Krzysztof Kozlowski wrote:
> This got acks from Mathieu (remoteproc) and Mark (audio), so can we
> funnel everything via Qualcomm remoteproc tree?
>
> Dependencies / merging
> ======================
> Entire patchset is one logical chain, all further patches depend on
> previous ones, thus everything should be taken via same tree or shared
> between trees with tags. Probably everything should go via ASoC with
> necessary acks.
>
> [...]
Applied, thanks!
[1/5] remoteproc: mtk_scp_ipi: Constify buffer passed to scp_ipi_send()
commit: 4251dab9d176212afdf4ced263b59bc0d5292c7f
[2/5] remoteproc: mtk_scp: Constify buffer passed to scp_send_ipi()
commit: 90dacbf4bf13410c727ffaca8fe3ce3276ae58c2
[3/5] rpmsg: Constify buffer passed to send API
commit: b8077b4da2e89917ec4c632b66e60d49089bbda3
[4/5] ASoC: qcom:: Constify GPR packet being send over GPR interface
commit: 66ec83627902d2585e14911692b317496731767a
[5/5] media: platform: mtk-mdp3: Constify buffer passed to mdp_vpu_sendmsg()
commit: 3e2fa997d1e2b651993ae7e81646aadd55470bce
Best regards,
--
Bjorn Andersson <andersson@kernel.org>
^ permalink raw reply
* Re: [PATCH 0/2] hwspinlock: remove platform_data from subsystem
From: Bjorn Andersson @ 2026-04-06 15:10 UTC (permalink / raw)
To: linux-renesas-soc, Wolfram Sang
Cc: linux-arm-kernel, Baolin Wang, linux-remoteproc, Russell King
In-Reply-To: <20260401071141.4718-1-wsa+renesas@sang-engineering.com>
On Wed, 01 Apr 2026 09:11:38 +0200, Wolfram Sang wrote:
> This series removes the use of platform_data from the hwspinlock
> subsystem. First, the last user is removed because it turned out to be
> obsolete. Then, platform_data support is removed from the header.
>
> This has been in a larger refactoring series before. But this
> refactoring turns out to be a rabbit hole where more and more fixes are
> needed before I can do the actual refactoring.
>
> [...]
Applied, thanks!
[1/2] hwspinlock: u8500: delete driver
commit: 392035c8b88b0198721e3b273f0a19ec2150710f
[2/2] hwspinlock: remove now unused pdata from header file
commit: ad5fd5aeb65a4426635cf55ef06c96e60a66e648
Best regards,
--
Bjorn Andersson <andersson@kernel.org>
^ permalink raw reply
* Re: [PATCH net v2] net: airoha: Add dma_rmb() and READ_ONCE() in airoha_qdma_rx_process()
From: Simon Horman @ 2026-04-06 15:25 UTC (permalink / raw)
To: lorenzo
Cc: 'Simon Horman', andrew+netdev, davem, edumazet, kuba,
pabeni, xuegang.lu, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260403-airoha_qdma_rx_process-fix-reordering-v2-1-181e6e23d27b@kernel.org>
From: 'Simon Horman' <horms@kernel.org>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev
---
net: airoha: Add dma_rmb() and READ_ONCE() in airoha_qdma_rx_process()
This commit adds a missing dma_rmb() to ensure DMA read operations are
completed and uses READ_ONCE() to prevent compiler reordering when accessing
descriptor fields.
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index 95ba99b89428e..f1843bc5b9914 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c
[ ... ]
> @@ -612,15 +611,17 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
> while (done < budget) {
> struct airoha_queue_entry *e = &q->entry[q->tail];
> struct airoha_qdma_desc *desc = &q->desc[q->tail];
> - u32 hash, reason, msg1 = le32_to_cpu(desc->msg1);
> - struct page *page = virt_to_head_page(e->buf);
> - u32 desc_ctrl = le32_to_cpu(desc->ctrl);
> + u32 hash, reason, msg1, desc_ctrl;
> struct airoha_gdm_port *port;
> int data_len, len, p;
> + struct page *page;
>
> + desc_ctrl = le32_to_cpu(READ_ONCE(desc->ctrl));
> if (!(desc_ctrl & QDMA_DESC_DONE_MASK))
> break;
[ ... ]
> @@ -633,10 +634,12 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
> if (!len || data_len < len)
> goto free_frag;
Could this jump lead to an uninitialized pointer dereference?
The initialization of page was removed from the declarations above, so it
is uninitialized at this point.
Later in the function, the error path looks like this:
free_frag:
if (q->skb) {
dev_kfree_skb(q->skb);
q->skb = NULL;
} else {
page_pool_put_full_page(q->page_pool, page, true);
}
If q->skb is NULL, passing the uninitialized page to
page_pool_put_full_page() might cause a crash.
Also, this isn't a bug introduced by this patch, but does this error path
leak the page associated with the current descriptor?
If we hit an error on a scattered fragment while q->skb is not NULL, the
SKB is freed, but the current page (which hasn't been attached to the SKB
yet) bypasses the else branch and isn't returned to the page pool.
>
> - p = airoha_qdma_get_gdm_port(eth, desc);
> + msg1 = le32_to_cpu(READ_ONCE(desc->msg1));
> + p = airoha_qdma_get_gdm_port(eth, msg1);
> if (p < 0 || !eth->ports[p])
> goto free_frag;
Similarly, taking this jump leaves page uninitialized before reaching the
error handling path.
>
> + page = virt_to_head_page(e->buf);
> port = eth->ports[p];
> if (!q->skb) { /* first buffer */
^ permalink raw reply
* Re: [PATCH 2/3] arm64: dts: imx8mn-vhip4-evalboard-v2: Correct interrupt flags
From: Marek Vasut @ 2026-04-06 14:49 UTC (permalink / raw)
To: Krzysztof Kozlowski, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Peng Fan, Fedor Ross, Shawn Guo, Shengjiu Wang,
Viorel Suman, devicetree, imx, linux-arm-kernel, linux-kernel
In-Reply-To: <20260406063810.25531-5-krzysztof.kozlowski@oss.qualcomm.com>
On 4/6/26 8:38 AM, Krzysztof Kozlowski wrote:
> GPIO_ACTIVE_x flags are not correct in the context of interrupt flags.
> These are simple defines so they could be used in DTS but they will not
> have the same meaning:
> 1. GPIO_ACTIVE_HIGH = 0 => IRQ_TYPE_NONE
> 2. GPIO_ACTIVE_LOW = 1 => IRQ_TYPE_EDGE_RISING
>
> Correct the interrupt flags, assuming the author of the code wanted the
> same logical behavior behind the name "ACTIVE_xxx", this is:
> ACTIVE_LOW => IRQ_TYPE_LEVEL_LOW
>
> Fixes: 5eb7405db99b ("arm64: dts: imx8mn: Add ifm VHIP4 EvalBoard v1 and v2")
> Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Reviewed-by: Marek Vasut <marex@nabladev.com>
^ permalink raw reply
* Re: [PATCH 1/3] arm64: dts: imx8mn-vhip4-evalboard-v1: Correct interrupt flags
From: Marek Vasut @ 2026-04-06 14:49 UTC (permalink / raw)
To: Krzysztof Kozlowski, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Peng Fan, Fedor Ross, Shawn Guo, Shengjiu Wang,
Viorel Suman, devicetree, imx, linux-arm-kernel, linux-kernel
In-Reply-To: <20260406063810.25531-4-krzysztof.kozlowski@oss.qualcomm.com>
On 4/6/26 8:38 AM, Krzysztof Kozlowski wrote:
> GPIO_ACTIVE_x flags are not correct in the context of interrupt flags.
> These are simple defines so they could be used in DTS but they will not
> have the same meaning:
> 1. GPIO_ACTIVE_HIGH = 0 => IRQ_TYPE_NONE
> 2. GPIO_ACTIVE_LOW = 1 => IRQ_TYPE_EDGE_RISING
>
> Correct the interrupt flags, assuming the author of the code wanted the
> same logical behavior behind the name "ACTIVE_xxx", this is:
> ACTIVE_LOW => IRQ_TYPE_LEVEL_LOW
>
> Fixes: 5eb7405db99b ("arm64: dts: imx8mn: Add ifm VHIP4 EvalBoard v1 and v2")
> Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Reviewed-by: Marek Vasut <marex@nabladev.com>
^ permalink raw reply
* Re: [PATCH RFC 0/2] clk: scmi: DT support for SCMI clock rate rounding modes (per‑clock policy)
From: Brian Masney @ 2026-04-06 15:38 UTC (permalink / raw)
To: Peng Fan (OSS)
Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Sudeep Holla, Cristian Marussi, linux-kernel,
linux-clk, devicetree, arm-scmi, linux-arm-kernel, Peng Fan
In-Reply-To: <20260306-scmi-clk-round-v1-0-61e2a5df9051@nxp.com>
Hi Peng,
On Fri, Mar 06, 2026 at 02:20:11PM +0800, Peng Fan (OSS) wrote:
> The ARM SCMI specification (DEN0056E) defines rounding‑mode flags for the
> CLOCK_RATE_SET command, allowing a client to request that the firmware
> round a requested clock rate down, up, or autonomously choose the
> closest achievable rate.
> This series introduces DT support in the SCMI clock provider to carry a
> per‑clock rounding policy from the device tree into the SCMI protocol.
>
> Patch 1 adds dt‑bindings constants for rounding modes:
> ROUND_DOWN, ROUND_UP, ROUND_AUTO.
>
> Patch 2 extends the SCMI clock provider to optionally support
> "#clock-cells = <2>", where the second cell encodes the rounding mode.
> The first consumer that references a given clock latches the per‑clock
> policy. Subsequent consumers of the same clock must specify the same
> mode; otherwise, the request is rejected to avoid non‑deterministic
> behavior. The selected mode is passed through to the SCMI Clock protocol
> and mapped to the corresponding CLOCK_SET_* flag.
>
> Patch 2 includes changes to drivers/clk/clk-scmi.c and drivers/firmware
> arm_scmi/clock.c, it is hard to separate the changes without breaking,
> so I put the changes in one patch.
>
> This design adopts a per‑clock policy model, not per‑consumer. The rounding
> mode is applied by the provider per clock (index).
> All consumers of the same clock must agree on the rounding mode.
> Conflicting per‑consumer requests for the same clock are invalid and
> are rejected during phandle translation.
>
> This avoids silent clobbering and preserves deterministic behavior.
>
> Existing device trees using #clock-cells = <1> continue to work and
> default to ROUND_DOWN, exactly as before.
>
> Signed-off-by: Peng Fan <peng.fan@nxp.com>
My high level feedback about this:
1) Since you are making changes to the DT schema for the clock-cells,
does the SCMI DT schema document also need to be updated to allow
clock-cells to be 1 or 2?
2) For the ROUND_XXX constants, I would prefix them with something
since the existing ROUND names are fairly generic sounding. Maybe
CLK_SCMI_?
Brian
^ permalink raw reply
* Re: [PATCH v2] stmmac: cleanup dead dependencies on STMMAC_PLATFORM and STMMAC_ETH in Kconfig
From: Jakub Kicinski @ 2026-04-06 15:39 UTC (permalink / raw)
To: Geert Uytterhoeven
Cc: Julian Braha, davem, peppe.cavallaro, alexandre.torgue,
mcoquelin.stm32, linux, netdev, linux-arm-kernel, linux-kernel,
Russell King (Oracle)
In-Reply-To: <CAMuHMdUfzVSQpadJYpEqPJ_UOBAgswnGzD_bp_U3U6jt2dy0dg@mail.gmail.com>
On Mon, 6 Apr 2026 10:23:46 +0200 Geert Uytterhoeven wrote:
> > config STMMAC_PLATFORM
> > tristate "STMMAC Platform bus support"
> > - depends on STMMAC_ETH
> > select MFD_SYSCON
> > default y
>
> This now lets us have STMMAC_PLATFORM=y and STMMAC_ETH=m.
> Does that actually link?
Hm. Sashiko didn't complain when patch was posted.
Typical LLM indeterminism?
^ permalink raw reply
* [RFC][PATCH] firmware: arm_scmi: Rename struct scmi_revision_info to scmi_base_info
From: Marek Vasut @ 2026-04-06 15:52 UTC (permalink / raw)
To: arm-scmi
Cc: Marek Vasut, Cristian Marussi, Geert Uytterhoeven, Sudeep Holla,
linux-arm-kernel, linux-renesas-soc
Rename struct scmi_revision_info to struct scmi_base_info , to
accurately represent its content. The scmi_revision_info is no
longer accurate, because the structure now contains more than
only SCMI base protocol revision, it now also contains number
of protocols, agents, vendor and subvendor strings. All those
are fetched from the base protocol, so rename the structure to
scmi_base_info, to match the other scmi_*_info structure names.
No functional change.
Signed-off-by: Marek Vasut <marek.vasut+renesas@mailbox.org>
---
Cc: Cristian Marussi <cristian.marussi@arm.com>
Cc: Geert Uytterhoeven <geert+renesas@glider.be>
Cc: Sudeep Holla <sudeep.holla@kernel.org>
Cc: arm-scmi@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-renesas-soc@vger.kernel.org
---
drivers/firmware/arm_scmi/base.c | 10 +++++-----
drivers/firmware/arm_scmi/common.h | 2 +-
drivers/firmware/arm_scmi/driver.c | 14 +++++++-------
include/linux/scmi_protocol.h | 6 +++---
4 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/drivers/firmware/arm_scmi/base.c b/drivers/firmware/arm_scmi/base.c
index cd1331c2fc403..4df2620e3c5d0 100644
--- a/drivers/firmware/arm_scmi/base.c
+++ b/drivers/firmware/arm_scmi/base.c
@@ -69,7 +69,7 @@ static int scmi_base_attributes_get(const struct scmi_protocol_handle *ph)
int ret;
struct scmi_xfer *t;
struct scmi_msg_resp_base_attributes *attr_info;
- struct scmi_revision_info *rev = ph->get_priv(ph);
+ struct scmi_base_info *rev = ph->get_priv(ph);
ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES,
0, sizeof(*attr_info), &t);
@@ -103,7 +103,7 @@ scmi_base_vendor_id_get(const struct scmi_protocol_handle *ph, bool sub_vendor)
int ret, size;
char *vendor_id;
struct scmi_xfer *t;
- struct scmi_revision_info *rev = ph->get_priv(ph);
+ struct scmi_base_info *rev = ph->get_priv(ph);
if (sub_vendor) {
cmd = BASE_DISCOVER_SUB_VENDOR;
@@ -143,7 +143,7 @@ scmi_base_implementation_version_get(const struct scmi_protocol_handle *ph)
int ret;
__le32 *impl_ver;
struct scmi_xfer *t;
- struct scmi_revision_info *rev = ph->get_priv(ph);
+ struct scmi_base_info *rev = ph->get_priv(ph);
ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_IMPLEMENT_VERSION,
0, sizeof(*impl_ver), &t);
@@ -180,7 +180,7 @@ scmi_base_implementation_list_get(const struct scmi_protocol_handle *ph,
__le32 *num_skip, *num_ret;
u32 tot_num_ret = 0, loop_num_ret;
struct device *dev = ph->dev;
- struct scmi_revision_info *rev = ph->get_priv(ph);
+ struct scmi_base_info *rev = ph->get_priv(ph);
ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_LIST_PROTOCOLS,
sizeof(*num_skip), 0, &t);
@@ -377,7 +377,7 @@ static int scmi_base_protocol_init(const struct scmi_protocol_handle *ph)
u8 *prot_imp;
char name[SCMI_SHORT_NAME_MAX_SIZE];
struct device *dev = ph->dev;
- struct scmi_revision_info *rev = scmi_revision_area_get(ph);
+ struct scmi_base_info *rev = scmi_revision_area_get(ph);
rev->major_ver = PROTOCOL_REV_MAJOR(ph->version);
rev->minor_ver = PROTOCOL_REV_MINOR(ph->version);
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
index 7c9617d080a02..07a127dec0319 100644
--- a/drivers/firmware/arm_scmi/common.h
+++ b/drivers/firmware/arm_scmi/common.h
@@ -138,7 +138,7 @@ static inline void unpack_scmi_header(u32 msg_hdr, struct scmi_msg_hdr *hdr)
xfer_; \
})
-struct scmi_revision_info *
+struct scmi_base_info *
scmi_revision_area_get(const struct scmi_protocol_handle *ph);
void scmi_setup_protocol_implemented(const struct scmi_protocol_handle *ph,
u8 *prot_imp);
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
index 57785c0c04241..00329935926d3 100644
--- a/drivers/firmware/arm_scmi/driver.c
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -134,7 +134,7 @@ struct scmi_protocol_instance {
* usage.
* @protocols_mtx: A mutex to protect protocols instances initialization.
* @protocols_imp: List of protocols implemented, currently maximum of
- * scmi_revision_info.num_protocols elements allocated by the
+ * scmi_base_info.num_protocols elements allocated by the
* base protocol
* @active_protocols: IDR storing device_nodes for protocols actually defined
* in the DT and confirmed as implemented by fw.
@@ -152,7 +152,7 @@ struct scmi_info {
int id;
struct device *dev;
const struct scmi_desc *desc;
- struct scmi_revision_info version;
+ struct scmi_base_info version;
struct scmi_handle handle;
struct scmi_xfers_info tx_minfo;
struct scmi_xfers_info rx_minfo;
@@ -266,7 +266,7 @@ scmi_vendor_protocol_lookup(int protocol_id, char *vendor_id,
}
static const struct scmi_protocol *
-scmi_vendor_protocol_get(int protocol_id, struct scmi_revision_info *version)
+scmi_vendor_protocol_get(int protocol_id, struct scmi_base_info *version)
{
const struct scmi_protocol *proto;
@@ -304,7 +304,7 @@ scmi_vendor_protocol_get(int protocol_id, struct scmi_revision_info *version)
}
static const struct scmi_protocol *
-scmi_protocol_get(int protocol_id, struct scmi_revision_info *version)
+scmi_protocol_get(int protocol_id, struct scmi_base_info *version)
{
const struct scmi_protocol *proto = NULL;
@@ -2095,7 +2095,7 @@ static const struct scmi_proto_helpers_ops helpers_ops = {
* Return: A reference to the version memory area associated to the SCMI
* instance underlying this protocol handle.
*/
-struct scmi_revision_info *
+struct scmi_base_info *
scmi_revision_area_get(const struct scmi_protocol_handle *ph)
{
const struct scmi_protocol_instance *pi = ph_to_pi(ph);
@@ -2408,7 +2408,7 @@ scmi_is_protocol_implemented(const struct scmi_handle *handle, u8 prot_id)
{
int i;
struct scmi_info *info = handle_to_scmi_info(handle);
- struct scmi_revision_info *rev = handle->version;
+ struct scmi_base_info *rev = handle->version;
if (!info->protocols_imp)
return false;
@@ -3203,7 +3203,7 @@ static const struct scmi_desc *scmi_transport_setup(struct device *dev)
static void scmi_enable_matching_quirks(struct scmi_info *info)
{
- struct scmi_revision_info *rev = &info->version;
+ struct scmi_base_info *rev = &info->version;
dev_dbg(info->dev, "Looking for quirks matching: %s/%s/0x%08X\n",
rev->vendor_id, rev->sub_vendor_id, rev->impl_ver);
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index c710107c2120a..49cc39e0cbca5 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -17,7 +17,7 @@
#define SCMI_SHORT_NAME_MAX_SIZE 16
/**
- * struct scmi_revision_info - version information structure
+ * struct scmi_base_info - version information structure
*
* @major_ver: Major ABI version. Change here implies risk of backward
* compatibility break.
@@ -30,7 +30,7 @@
* @vendor_id: A vendor identifier(Null terminated ASCII string)
* @sub_vendor_id: A sub-vendor identifier(Null terminated ASCII string)
*/
-struct scmi_revision_info {
+struct scmi_base_info {
u16 major_ver;
u16 minor_ver;
u8 num_protocols;
@@ -906,7 +906,7 @@ struct scmi_notify_ops {
*/
struct scmi_handle {
struct device *dev;
- struct scmi_revision_info *version;
+ struct scmi_base_info *version;
int __must_check (*devm_protocol_acquire)(struct scmi_device *sdev,
u8 proto);
--
2.53.0
^ permalink raw reply related
* Re: [PATCHv2] clk: mvebu: use kzalloc_flex
From: Brian Masney @ 2026-04-06 16:22 UTC (permalink / raw)
To: Rosen Penev
Cc: linux-clk, Andrew Lunn, Gregory Clement, Sebastian Hesselbarth,
Michael Turquette, Stephen Boyd, Kees Cook, Gustavo A. R. Silva,
moderated list:ARM/Marvell Kirkwood and Armada 370, 375, 38x,...,
open list,
open list:KERNEL HARDENING (not covered by other areas):Keyword:b__counted_by(_le|_be)?b
In-Reply-To: <20260403194701.11902-1-rosenp@gmail.com>
Hi Rosen,
On Fri, Apr 03, 2026 at 12:47:01PM -0700, Rosen Penev wrote:
> Use a flexible array member to combine kzalloc and kcalloc in one
> allocation so they can be freed together.
>
> Add __counted_by for extra runtime analysis. Move counting variable
> assignment right after allocation as done by kzalloc_flex with GCC >=
> 15.
>
> Signed-off-by: Rosen Penev <rosenp@gmail.com>
> ---
> v2: remove now unused goto label.
Reviewed-by: Brian Masney <bmasney@redhat.com>
This is the third time that I've asked you this [1]: For the future, if
someone asks for changes in a previous version, then be sure to CC them
on the next revision. I was the one that found the unused goto in v1.
[1] https://lore.kernel.org/linux-clk/acvUoSOOF_9UQC75@redhat.com/
https://lore.kernel.org/linux-clk/ac0o7XbBm8aBOa7a@redhat.com/
^ permalink raw reply
* Re: [PATCH v2] stmmac: cleanup dead dependencies on STMMAC_PLATFORM and STMMAC_ETH in Kconfig
From: Russell King (Oracle) @ 2026-04-06 16:46 UTC (permalink / raw)
To: Geert Uytterhoeven
Cc: Julian Braha, davem, peppe.cavallaro, alexandre.torgue,
mcoquelin.stm32, kuba, netdev, linux-arm-kernel, linux-kernel
In-Reply-To: <CAMuHMdUfzVSQpadJYpEqPJ_UOBAgswnGzD_bp_U3U6jt2dy0dg@mail.gmail.com>
On Mon, Apr 06, 2026 at 10:23:46AM +0200, Geert Uytterhoeven wrote:
> Hi Julian,
>
> On Thu, 2 Apr 2026 at 17:07, Julian Braha <julianbraha@gmail.com> wrote:
> > There are already 'if STMMAC_ETH' and 'STMMAC_PLATFORM'
> > conditions wrapping these config options, making the
> > 'depends on' statements duplicate dependencies (dead code).
> >
> > I propose leaving the outer 'if STMMAC_PLATFORM...endif' and
> > 'if STMMAC_ETH...endif' conditions, and removing the
> > individual 'depends on' statements.
> >
> > This dead code was found by kconfirm, a static analysis tool for Kconfig.
> >
> > Signed-off-by: Julian Braha <julianbraha@gmail.com>
> > Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
> > ---
> > v2: add back default STMMAC_PLATFORM for DWMAC_GENERIC
> > Link to v1: https://lore.kernel.org/all/20260331125817.117091-1-julianbraha@gmail.com/
>
> Thanks for your patch, which is now commit e2f152c822cf5d37 ("stmmac:
> cleanup dead dependencies on STMMAC_PLATFORM and STMMAC_ETH in
> Kconfig") in net-next.
>
> > --- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
> > +++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
> > @@ -20,7 +20,6 @@ if STMMAC_ETH
> > config STMMAC_SELFTESTS
> > bool "Support for STMMAC Selftests"
> > depends on INET
> > - depends on STMMAC_ETH
> > default n
> > help
> > This adds support for STMMAC Selftests using ethtool. Enable this
> > @@ -29,7 +28,6 @@ config STMMAC_SELFTESTS
> >
> > config STMMAC_PLATFORM
> > tristate "STMMAC Platform bus support"
> > - depends on STMMAC_ETH
> > select MFD_SYSCON
> > default y
>
> This now lets us have STMMAC_PLATFORM=y and STMMAC_ETH=m.
> Does that actually link?
Really?
Let's try forcing it:
CONFIG_STMMAC_ETH=m
CONFIG_STMMAC_SELFTESTS=y
CONFIG_STMMAC_PLATFORM=y
and make oldconfig...
STMicroelectronics Multi-Gigabit Ethernet driver (STMMAC_ETH) [M/n/y/?] m
Support for STMMAC Selftests (STMMAC_SELFTESTS) [Y/n/?] y
STMMAC Platform bus support (STMMAC_PLATFORM) [M/n/?] m
CONFIG_STMMAC_PLATFORM is forced to 'm'. 'y' is not permitted.
I think you're mistaken.
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply
* [PATCH 0/2] KVM: arm64: Handle unsupported guest translation granules
From: Wei-Lin Chang @ 2026-04-06 16:46 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, linux-kernel
Cc: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Wei-Lin Chang
Hi,
This small series fixes the granule size selection for software stage-1
and stage-2 walks. Previously we treat the guest's TCR/VTCR.TGx as-is
and use the encoded granule size for the walks. However this is
incorrect if the granule sizes are not advertised in the guest's
ID_AA64MMFR0_EL1.TGRAN*. The architecture specifies that when an
unsupported size is programed in TGx, it must be treated as an
implemented size. Fix this by choosing an available one while
prioritizing PAGE_SIZE.
The first patch is a refactor to prepare for the fix, and the fix is
implemented in the second patch. I lightly tested for regressions by
booting up nested guests of each page size.
Thanks!
Wei-Lin Chang (2):
KVM: arm64: Factor out TG0/1 decoding of VTCR and TCR
KVM: arm64: Fallback to a supported value for unsupported guest TGx
arch/arm64/kvm/at.c | 121 +++++++++++++++++++++++++--------
arch/arm64/kvm/nested.c | 145 ++++++++++++++++++++++++++++------------
2 files changed, 194 insertions(+), 72 deletions(-)
--
2.43.0
^ permalink raw reply
* [PATCH 1/2] KVM: arm64: Factor out TG0/1 decoding of VTCR and TCR
From: Wei-Lin Chang @ 2026-04-06 16:46 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, linux-kernel
Cc: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Wei-Lin Chang
In-Reply-To: <20260406164618.3312473-1-weilin.chang@arm.com>
The current code decodes TCR.TG0/TG1 and VTCR.TG0 inline at several
places. Extract this logic into helpers so the granule size is derived
in one place. This enables us to alter the effective granule size in
the same place, which we will need in a later patch.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
arch/arm64/kvm/at.c | 73 +++++++++++++++++++++++++----------------
arch/arm64/kvm/nested.c | 70 ++++++++++++++++++++++++---------------
2 files changed, 89 insertions(+), 54 deletions(-)
diff --git a/arch/arm64/kvm/at.c b/arch/arm64/kvm/at.c
index c5c5644b1878..ff8ba30e917b 100644
--- a/arch/arm64/kvm/at.c
+++ b/arch/arm64/kvm/at.c
@@ -135,14 +135,54 @@ static void compute_s1poe(struct kvm_vcpu *vcpu, struct s1_walk_info *wi)
wi->e0poe = (wi->regime != TR_EL2) && (val & TCR2_EL1_E0POE);
}
+static unsigned int tg0_to_shift(u64 tg0)
+{
+ switch (tg0) {
+ case TCR_EL1_TG0_4K:
+ return 12;
+ case TCR_EL1_TG0_16K:
+ return 14;
+ case TCR_EL1_TG0_64K:
+ default: /* IMPDEF: treat any other value as 64k */
+ return 16;
+ }
+}
+
+static unsigned int tg1_to_shift(u64 tg1)
+{
+ switch (tg1) {
+ case TCR_EL1_TG1_4K:
+ return 12;
+ case TCR_EL1_TG1_16K:
+ return 14;
+ case TCR_EL1_TG1_64K:
+ default: /* IMPDEF: treat any other value as 64k */
+ return 16;
+ }
+}
+
+static u64 tcr_tg_shift(struct kvm *kvm, u64 tcr, bool upper_range)
+{
+ unsigned int shift;
+
+ /* Someone was silly enough to encode TG0/TG1 differently */
+ if (upper_range)
+ shift = tg1_to_shift(FIELD_GET(TCR_EL1_TG1_MASK, tcr));
+ else
+ shift = tg0_to_shift(FIELD_GET(TCR_EL1_TG0_MASK, tcr));
+
+ return shift;
+}
+
static int setup_s1_walk(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
struct s1_walk_result *wr, u64 va)
{
- u64 hcr, sctlr, tcr, tg, ps, ia_bits, ttbr;
+ u64 hcr, sctlr, tcr, ps, ia_bits, ttbr;
unsigned int stride, x;
- bool va55, tbi, lva;
+ bool va55, tbi, lva, upper_range;
va55 = va & BIT(55);
+ upper_range = va55 && wi->regime != TR_EL2;
if (vcpu_has_nv(vcpu)) {
hcr = __vcpu_sys_reg(vcpu, HCR_EL2);
@@ -173,35 +213,12 @@ static int setup_s1_walk(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
BUG();
}
- /* Someone was silly enough to encode TG0/TG1 differently */
- if (va55 && wi->regime != TR_EL2) {
+ if (upper_range)
wi->txsz = FIELD_GET(TCR_T1SZ_MASK, tcr);
- tg = FIELD_GET(TCR_TG1_MASK, tcr);
-
- switch (tg << TCR_TG1_SHIFT) {
- case TCR_TG1_4K:
- wi->pgshift = 12; break;
- case TCR_TG1_16K:
- wi->pgshift = 14; break;
- case TCR_TG1_64K:
- default: /* IMPDEF: treat any other value as 64k */
- wi->pgshift = 16; break;
- }
- } else {
+ else
wi->txsz = FIELD_GET(TCR_T0SZ_MASK, tcr);
- tg = FIELD_GET(TCR_TG0_MASK, tcr);
-
- switch (tg << TCR_TG0_SHIFT) {
- case TCR_TG0_4K:
- wi->pgshift = 12; break;
- case TCR_TG0_16K:
- wi->pgshift = 14; break;
- case TCR_TG0_64K:
- default: /* IMPDEF: treat any other value as 64k */
- wi->pgshift = 16; break;
- }
- }
+ wi->pgshift = tcr_tg_shift(vcpu->kvm, tcr, upper_range);
wi->pa52bit = has_52bit_pa(vcpu, wi, tcr);
ia_bits = get_ia_size(wi);
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index 883b6c1008fb..2bfab3007cb3 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -378,20 +378,36 @@ static int walk_nested_s2_pgd(struct kvm_vcpu *vcpu, phys_addr_t ipa,
return 0;
}
-static void vtcr_to_walk_info(u64 vtcr, struct s2_walk_info *wi)
+static unsigned int tg0_to_shift(u64 tg0)
+{
+ switch (tg0) {
+ case VTCR_EL2_TG0_4K:
+ return 12;
+ case VTCR_EL2_TG0_16K:
+ return 14;
+ case VTCR_EL2_TG0_64K:
+ default: /* IMPDEF: treat any other value as 64k */
+ return 16;
+ }
+}
+
+static u64 vtcr_tg0_shift(struct kvm *kvm, u64 vtcr)
+{
+ u64 tg0 = FIELD_GET(VTCR_EL2_TG0_MASK, vtcr);
+ unsigned int shift = tg0_to_shift(tg0);
+
+ return shift;
+}
+
+static size_t vtcr_tg0_size(struct kvm *kvm, u64 vtcr)
+{
+ return BIT(vtcr_tg0_shift(kvm, vtcr));
+}
+
+static void vtcr_to_walk_info(struct kvm *kvm, u64 vtcr, struct s2_walk_info *wi)
{
wi->t0sz = vtcr & TCR_EL2_T0SZ_MASK;
-
- switch (FIELD_GET(VTCR_EL2_TG0_MASK, vtcr)) {
- case VTCR_EL2_TG0_4K:
- wi->pgshift = 12; break;
- case VTCR_EL2_TG0_16K:
- wi->pgshift = 14; break;
- case VTCR_EL2_TG0_64K:
- default: /* IMPDEF: treat any other value as 64k */
- wi->pgshift = 16; break;
- }
-
+ wi->pgshift = vtcr_tg0_shift(kvm, vtcr);
wi->sl = FIELD_GET(VTCR_EL2_SL0_MASK, vtcr);
/* Global limit for now, should eventually be per-VM */
wi->max_oa_bits = min(get_kvm_ipa_limit(),
@@ -414,7 +430,7 @@ int kvm_walk_nested_s2(struct kvm_vcpu *vcpu, phys_addr_t gipa,
wi.baddr = vcpu_read_sys_reg(vcpu, VTTBR_EL2);
- vtcr_to_walk_info(vtcr, &wi);
+ vtcr_to_walk_info(vcpu->kvm, vtcr, &wi);
wi.be = vcpu_read_sys_reg(vcpu, SCTLR_EL2) & SCTLR_ELx_EE;
@@ -515,17 +531,19 @@ static u8 get_guest_mapping_ttl(struct kvm_s2_mmu *mmu, u64 addr)
u64 tmp, sz = 0, vtcr = mmu->tlb_vtcr;
kvm_pte_t pte;
u8 ttl, level;
+ struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu);
+ size_t tg0_size = vtcr_tg0_size(kvm, vtcr);
- lockdep_assert_held_write(&kvm_s2_mmu_to_kvm(mmu)->mmu_lock);
+ lockdep_assert_held_write(&kvm->mmu_lock);
- switch (FIELD_GET(VTCR_EL2_TG0_MASK, vtcr)) {
- case VTCR_EL2_TG0_4K:
+ switch (tg0_size) {
+ case SZ_4K:
ttl = (TLBI_TTL_TG_4K << 2);
break;
- case VTCR_EL2_TG0_16K:
+ case SZ_16K:
ttl = (TLBI_TTL_TG_16K << 2);
break;
- case VTCR_EL2_TG0_64K:
+ case SZ_64K:
default: /* IMPDEF: treat any other value as 64k */
ttl = (TLBI_TTL_TG_64K << 2);
break;
@@ -535,19 +553,19 @@ static u8 get_guest_mapping_ttl(struct kvm_s2_mmu *mmu, u64 addr)
again:
/* Iteratively compute the block sizes for a particular granule size */
- switch (FIELD_GET(VTCR_EL2_TG0_MASK, vtcr)) {
- case VTCR_EL2_TG0_4K:
+ switch (tg0_size) {
+ case SZ_4K:
if (sz < SZ_4K) sz = SZ_4K;
else if (sz < SZ_2M) sz = SZ_2M;
else if (sz < SZ_1G) sz = SZ_1G;
else sz = 0;
break;
- case VTCR_EL2_TG0_16K:
+ case SZ_16K:
if (sz < SZ_16K) sz = SZ_16K;
else if (sz < SZ_32M) sz = SZ_32M;
else sz = 0;
break;
- case VTCR_EL2_TG0_64K:
+ case SZ_64K:
default: /* IMPDEF: treat any other value as 64k */
if (sz < SZ_64K) sz = SZ_64K;
else if (sz < SZ_512M) sz = SZ_512M;
@@ -598,14 +616,14 @@ unsigned long compute_tlb_inval_range(struct kvm_s2_mmu *mmu, u64 val)
if (!max_size) {
/* Compute the maximum extent of the invalidation */
- switch (FIELD_GET(VTCR_EL2_TG0_MASK, mmu->tlb_vtcr)) {
- case VTCR_EL2_TG0_4K:
+ switch (vtcr_tg0_size(kvm, mmu->tlb_vtcr)) {
+ case SZ_4K:
max_size = SZ_1G;
break;
- case VTCR_EL2_TG0_16K:
+ case SZ_16K:
max_size = SZ_32M;
break;
- case VTCR_EL2_TG0_64K:
+ case SZ_64K:
default: /* IMPDEF: treat any other value as 64k */
/*
* No, we do not support 52bit IPA in nested yet. Once
--
2.43.0
^ permalink raw reply related
* [PATCH 2/2] KVM: arm64: Fallback to a supported value for unsupported guest TGx
From: Wei-Lin Chang @ 2026-04-06 16:46 UTC (permalink / raw)
To: linux-arm-kernel, kvmarm, linux-kernel
Cc: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Wei-Lin Chang
In-Reply-To: <20260406164618.3312473-1-weilin.chang@arm.com>
When KVM derives the translation granule for emulated stage-1 and
stage-2 walks, it decodes TCR/VTCR.TGx and treats the granule as-is.
This is wrong when the guest programs a granule size that is not
advertised in the guest's ID_AA64MMFR0_EL1.TGRAN* fields.
Architecturally, such a value must be treated as an implemented granule
size. Choose an available one while prioritizing PAGE_SIZE.
Signed-off-by: Wei-Lin Chang <weilin.chang@arm.com>
---
arch/arm64/kvm/at.c | 48 ++++++++++++++++++++++++++
arch/arm64/kvm/nested.c | 75 +++++++++++++++++++++++++++++++----------
2 files changed, 105 insertions(+), 18 deletions(-)
diff --git a/arch/arm64/kvm/at.c b/arch/arm64/kvm/at.c
index ff8ba30e917b..6dd883798f83 100644
--- a/arch/arm64/kvm/at.c
+++ b/arch/arm64/kvm/at.c
@@ -135,6 +135,30 @@ static void compute_s1poe(struct kvm_vcpu *vcpu, struct s1_walk_info *wi)
wi->e0poe = (wi->regime != TR_EL2) && (val & TCR2_EL1_E0POE);
}
+#define _has_tgran(__r, __sz) \
+ ({ \
+ u64 _s1, _mmfr0 = __r; \
+ \
+ _s1 = SYS_FIELD_GET(ID_AA64MMFR0_EL1, \
+ TGRAN##__sz, _mmfr0); \
+ \
+ _s1 != ID_AA64MMFR0_EL1_TGRAN##__sz##_NI; \
+ })
+
+static bool has_tgran(u64 mmfr0, unsigned int shift)
+{
+ switch (shift) {
+ case 12:
+ return _has_tgran(mmfr0, 4);
+ case 14:
+ return _has_tgran(mmfr0, 16);
+ case 16:
+ return _has_tgran(mmfr0, 64);
+ default:
+ BUG();
+ }
+}
+
static unsigned int tg0_to_shift(u64 tg0)
{
switch (tg0) {
@@ -161,8 +185,23 @@ static unsigned int tg1_to_shift(u64 tg1)
}
}
+static unsigned int fallback_tgran_shift(u64 mmfr0)
+{
+ if (has_tgran(mmfr0, PAGE_SHIFT))
+ return PAGE_SHIFT;
+ else if (has_tgran(mmfr0, 12))
+ return 12;
+ else if (has_tgran(mmfr0, 14))
+ return 14;
+ else if (has_tgran(mmfr0, 16))
+ return 16;
+ else
+ return PAGE_SHIFT;
+}
+
static u64 tcr_tg_shift(struct kvm *kvm, u64 tcr, bool upper_range)
{
+ u64 mmfr0 = kvm_read_vm_id_reg(kvm, SYS_ID_AA64MMFR0_EL1);
unsigned int shift;
/* Someone was silly enough to encode TG0/TG1 differently */
@@ -171,6 +210,15 @@ static u64 tcr_tg_shift(struct kvm *kvm, u64 tcr, bool upper_range)
else
shift = tg0_to_shift(FIELD_GET(TCR_EL1_TG0_MASK, tcr));
+ /*
+ * If TGx is programmed to an unimplemented value (not advertised in
+ * ID_AA64MMFR0_EL1), we should treat it as if an implemented value is
+ * written, as per the architecture. Choose an available one while
+ * prioritizing PAGE_SIZE.
+ */
+ if (!has_tgran(mmfr0, shift))
+ return fallback_tgran_shift(mmfr0);
+
return shift;
}
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index 2bfab3007cb3..64794ba4848d 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -378,6 +378,36 @@ static int walk_nested_s2_pgd(struct kvm_vcpu *vcpu, phys_addr_t ipa,
return 0;
}
+#define _has_tgran_2(__r, __sz) \
+ ({ \
+ u64 _s1, _s2, _mmfr0 = __r; \
+ \
+ _s2 = SYS_FIELD_GET(ID_AA64MMFR0_EL1, \
+ TGRAN##__sz##_2, _mmfr0); \
+ \
+ _s1 = SYS_FIELD_GET(ID_AA64MMFR0_EL1, \
+ TGRAN##__sz, _mmfr0); \
+ \
+ ((_s2 != ID_AA64MMFR0_EL1_TGRAN##__sz##_2_NI && \
+ _s2 != ID_AA64MMFR0_EL1_TGRAN##__sz##_2_TGRAN##__sz) || \
+ (_s2 == ID_AA64MMFR0_EL1_TGRAN##__sz##_2_TGRAN##__sz && \
+ _s1 != ID_AA64MMFR0_EL1_TGRAN##__sz##_NI)); \
+ })
+
+static bool has_tgran_2(u64 mmfr0, unsigned int shift)
+{
+ switch (shift) {
+ case 12:
+ return _has_tgran_2(mmfr0, 4);
+ case 14:
+ return _has_tgran_2(mmfr0, 16);
+ case 16:
+ return _has_tgran_2(mmfr0, 64);
+ default:
+ BUG();
+ }
+}
+
static unsigned int tg0_to_shift(u64 tg0)
{
switch (tg0) {
@@ -391,11 +421,35 @@ static unsigned int tg0_to_shift(u64 tg0)
}
}
+static unsigned int fallback_tgran2_shift(u64 mmfr0)
+{
+ if (has_tgran_2(mmfr0, PAGE_SHIFT))
+ return PAGE_SHIFT;
+ else if (has_tgran_2(mmfr0, 12))
+ return 12;
+ else if (has_tgran_2(mmfr0, 14))
+ return 14;
+ else if (has_tgran_2(mmfr0, 16))
+ return 16;
+ else
+ return PAGE_SHIFT;
+}
+
static u64 vtcr_tg0_shift(struct kvm *kvm, u64 vtcr)
{
+ u64 mmfr0 = kvm_read_vm_id_reg(kvm, SYS_ID_AA64MMFR0_EL1);
u64 tg0 = FIELD_GET(VTCR_EL2_TG0_MASK, vtcr);
unsigned int shift = tg0_to_shift(tg0);
+ /*
+ * If TGx is programmed to an unimplemented value (not advertised in
+ * ID_AA64MMFR0_EL1), we should treat it as if an implemented value is
+ * written, as per the architecture. Choose an available one while
+ * prioritizing PAGE_SIZE.
+ */
+ if (!has_tgran_2(mmfr0, shift))
+ return fallback_tgran2_shift(mmfr0);
+
return shift;
}
@@ -1516,21 +1570,6 @@ static void kvm_map_l1_vncr(struct kvm_vcpu *vcpu)
}
}
-#define has_tgran_2(__r, __sz) \
- ({ \
- u64 _s1, _s2, _mmfr0 = __r; \
- \
- _s2 = SYS_FIELD_GET(ID_AA64MMFR0_EL1, \
- TGRAN##__sz##_2, _mmfr0); \
- \
- _s1 = SYS_FIELD_GET(ID_AA64MMFR0_EL1, \
- TGRAN##__sz, _mmfr0); \
- \
- ((_s2 != ID_AA64MMFR0_EL1_TGRAN##__sz##_2_NI && \
- _s2 != ID_AA64MMFR0_EL1_TGRAN##__sz##_2_TGRAN##__sz) || \
- (_s2 == ID_AA64MMFR0_EL1_TGRAN##__sz##_2_TGRAN##__sz && \
- _s1 != ID_AA64MMFR0_EL1_TGRAN##__sz##_NI)); \
- })
/*
* Our emulated CPU doesn't support all the possible features. For the
* sake of simplicity (and probably mental sanity), wipe out a number
@@ -1617,15 +1656,15 @@ u64 limit_nv_id_reg(struct kvm *kvm, u32 reg, u64 val)
*/
switch (PAGE_SIZE) {
case SZ_4K:
- if (has_tgran_2(orig_val, 4))
+ if (_has_tgran_2(orig_val, 4))
val |= SYS_FIELD_PREP_ENUM(ID_AA64MMFR0_EL1, TGRAN4_2, IMP);
fallthrough;
case SZ_16K:
- if (has_tgran_2(orig_val, 16))
+ if (_has_tgran_2(orig_val, 16))
val |= SYS_FIELD_PREP_ENUM(ID_AA64MMFR0_EL1, TGRAN16_2, IMP);
fallthrough;
case SZ_64K:
- if (has_tgran_2(orig_val, 64))
+ if (_has_tgran_2(orig_val, 64))
val |= SYS_FIELD_PREP_ENUM(ID_AA64MMFR0_EL1, TGRAN64_2, IMP);
break;
}
--
2.43.0
^ permalink raw reply related
* Re: [GIT PULL] soc fixes for 7.0, part 2
From: pr-tracker-bot @ 2026-04-06 17:08 UTC (permalink / raw)
To: Arnd Bergmann; +Cc: Linus Torvalds, soc, linux-kernel, linux-arm-kernel
In-Reply-To: <23df0cc0-55cb-4c11-907d-f4d8955c6aa9@app.fastmail.com>
The pull request you sent on Mon, 06 Apr 2026 10:03:08 +0200:
> https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git tags/soc-fixes-7.0-2
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/bfe62a454542cfad3379f6ef5680b125f41e20f4
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply
* Re: [PATCH v2] ARM: xen: validate hypervisor compatible before parsing its version
From: Stefano Stabellini @ 2026-04-06 17:45 UTC (permalink / raw)
To: Pengpeng Hou
Cc: Stefano Stabellini, xen-devel, linux-arm-kernel, linux-kernel,
jgross
In-Reply-To: <20260405094005.5-arm-xen-v2-pengpeng@iscas.ac.cn>
On Sun, 5 Apr 2026, Pengpeng Hou wrote:
> fdt_find_hyper_node() reads the raw compatible property and then derives
> hyper_node.version from a prefix match before later printing it with %s.
> Flat DT properties are external boot input, and this path does not prove
> that the first compatible entry is NUL-terminated within the returned
> property length.
>
> Keep the existing flat-DT lookup path, but verify that the first
> compatible entry terminates within the returned property length before
> deriving the version suffix from it.
>
> Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
> ---
> Changes since v1:
> - keep `of_get_flat_dt_prop()` instead of switching to `fdt_stringlist_get()`
> - validate the first compatible entry with bounded `strnlen()`
>
> arch/arm/xen/enlighten.c | 9 ++++++---
> 1 file changed, 6 insertions(+), 3 deletions(-)
>
> diff --git a/arch/arm/xen/enlighten.c b/arch/arm/xen/enlighten.c
> index 4feed2c2..25a0ce3b 100644
> --- a/arch/arm/xen/enlighten.c
> +++ b/arch/arm/xen/enlighten.c
> @@ -218,8 +218,9 @@ static __initdata struct {
> static int __init fdt_find_hyper_node(unsigned long node, const char *uname,
> int depth, void *data)
> {
> - const void *s = NULL;
> + const char *s = NULL;
> int len;
> + size_t prefix_len = strlen(hyper_node.prefix);
>
> if (depth != 1 || strcmp(uname, "hypervisor") != 0)
> return 0;
> @@ -228,9 +229,10 @@ static int __init fdt_find_hyper_node(unsigned long node, const char *uname,
> hyper_node.found = true;
>
> s = of_get_flat_dt_prop(node, "compatible", &len);
> - if (strlen(hyper_node.prefix) + 3 < len &&
> - !strncmp(hyper_node.prefix, s, strlen(hyper_node.prefix)))
> - hyper_node.version = s + strlen(hyper_node.prefix);
> + if (s && len > 0 && strnlen(s, len) < len &&
> + len > prefix_len + 3 &&
> + !strncmp(hyper_node.prefix, s, prefix_len))
> + hyper_node.version = s + prefix_len;
>
> /*
> * Check if Xen supports EFI by checking whether there is the
> --
> 2.50.1
>
^ permalink raw reply
* Re: [PATCH 0/4] perf arm_spe: Dump IMPDEF events
From: Namhyung Kim @ 2026-04-06 18:18 UTC (permalink / raw)
To: James Clark
Cc: John Garry, Will Deacon, Mike Leach, Leo Yan, Peter Zijlstra,
Ingo Molnar, Arnaldo Carvalho de Melo, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
Al Grant, linux-arm-kernel, linux-perf-users, linux-kernel
In-Reply-To: <20260401-james-spe-impdef-decode-v1-0-ad0d372c220c@linaro.org>
Hi James,
On Wed, Apr 01, 2026 at 03:25:48PM +0100, James Clark wrote:
> In the Arm SPE raw data dump, IMPDEF events aren't printed. Add the
> ability to add names for some known events or print the raw event number
> for unknown events.
>
> For example:
>
> $ perf report -D
>
> ... ARM SPE data: size 0xd000 bytes
> 00000000: b0 18 c6 32 80 00 80 ff a0 PC 0xff80008032c618 el1 ns=1
> 00000009: 64 e7 42 00 00 CONTEXT 0x42e7 el1
> 0000000e: 00 00 00 00 00 PAD
> 00000013: 49 00 LD GP-REG
> 00000015: 52 16 10 EV RETIRED L1D-ACCESS TLB-ACCESS
>
> On N1 the event line becomes:
>
> 00000015: 52 16 10 EV RETIRED L1D-ACCESS TLB-ACCESS LATE-PREFETCH
>
> Or on other cores it becomes:
>
> 00000015: 52 16 10 EV RETIRED L1D-ACCESS TLB-ACCESS IMPDEF:12
>
> Signed-off-by: James Clark <james.clark@linaro.org>
> ---
> James Clark (4):
> perf arm_spe: Make a function to get the MIDR
> perf arm_spe: Turn event name mappings into an array
> perf arm_spe: Decode Arm N1 IMPDEF events
> perf arm_spe: Print remaining IMPDEF event numbers
Will you send v2 or do you think it's ok to merge v1?
Thanks,
Namhyung
>
> tools/perf/util/arm-spe-decoder/Build | 2 +
> .../util/arm-spe-decoder/arm-spe-pkt-decoder.c | 147 ++++++++++++++-------
> .../util/arm-spe-decoder/arm-spe-pkt-decoder.h | 5 +-
> tools/perf/util/arm-spe.c | 49 ++++---
> 4 files changed, 135 insertions(+), 68 deletions(-)
> ---
> base-commit: 74e2dbe7be5037a5e5eed6bc1ad562747ac88566
> change-id: 20260331-james-spe-impdef-decode-d944f4fdcff7
>
> Best regards,
> --
> James Clark <james.clark@linaro.org>
>
^ permalink raw reply
* [PATCH v3 0/8] unwind, arm64: add sframe unwinder for kernel
From: Dylan Hatch @ 2026-04-06 18:49 UTC (permalink / raw)
To: Roman Gushchin, Weinan Liu, Will Deacon, Josh Poimboeuf,
Indu Bhagat, Peter Zijlstra, Steven Rostedt, Catalin Marinas,
Jiri Kosina
Cc: Dylan Hatch, Mark Rutland, Prasanna Kumar T S M, Puranjay Mohan,
Song Liu, joe.lawrence, linux-toolchains, linux-kernel,
live-patching, Jens Remus, linux-arm-kernel
Implement a generic kernel sframe-based [1] unwinder. The main goal is
to improve reliable stacktrace on arm64 by unwinding across exception
boundaries.
On x86, the ORC unwinder provides reliable stacktrace through similar
methodology, but arm64 lacks the necessary support from objtool to
create ORC unwind tables.
Currently, there's already a sframe unwinder proposed for userspace: [2].
To maintain common definitions and algorithms for sframe lookup, a
substantial portion of this patch series aims to refactor the sframe
lookup code to support both kernel and userspace sframe sections.
Currently, only GNU Binutils support sframe. This series relies on the
Sframe V3 format, which is supported in binutils 2.46.
These patches are based on Steven Rostedt's sframe/core branch [3],
which is and aggregation of existing work done for x86 sframe userspace
unwind, and contains [2]. This branch is, in turn, based on Linux
v7.0-rc3. This full series (applied to the sframe/core branch) is
available on github: [4].
Ref:
[1]: https://sourceware.org/binutils/docs/sframe-spec.html
[2]: https://lore.kernel.org/lkml/20260127150554.2760964-1-jremus@linux.ibm.com/
[3]: https://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-trace.git/log/?h=sframe/core
[4]: https://github.com/dylanbhatch/linux/tree/sframe-v3-with-v3
Changes since v2:
The biggest change from v2 is the switch from adding a dedicated,
in-kernel sframe-lookup library, to refactoring/using the existing
library developed by Josh, Jens, and Steve. Consequently, this series
now depends on Sframe V3, though this upgrade would likely have been
necessary anyway. Below is a full accounting of the changes since v2.
- (Josh) Add stricter reliability checks during unwind.
- (Puranjay, Indu, Jens) Update to use a common sframe library with
userpace unwind, thus resolving the need to support
SFRAME_F_FDE_FUNC_START_PCREL, added in binutils 2.45.
- (Jens) Add check for sframe V3, thus resolving the prior need for V2
and SFRAME_F_FDE_FUNC_START_PCREL support.
- (Will) Add ARCH_SUPPORTS_SFRAME_UNWINDER, remove SFRAME_UNWIND_TABLE
- (Indu) add support for unsorted FDE tables, allowing for module
sframe lookups.
- (Mark) Prefer frame-pointer unwind when possible, for better
performance.
- Simplify compile-time logic, adding stubbs when necessary.
- Add support for in-kernel SFRAME_VALIDATION.
- Rebase onto core/sframe (with v7.0-rc3 base)
Dylan Hatch (7):
sframe: Allow kernelspace sframe sections.
arm64, unwind: build kernel with sframe V3 info
sframe: Provide PC lookup for vmlinux .sframe section.
sframe: Allow unsorted FDEs.
arm64/module, sframe: Add sframe support for modules.
sframe: Introduce in-kernel SFRAME_VALIDATION.
unwind: arm64: Use sframe to unwind interrupt frames.
Weinan Liu (1):
arm64: entry: add unwind info for various kernel entries
MAINTAINERS | 3 +-
Makefile | 8 +
arch/Kconfig | 13 +-
arch/arm64/Kconfig | 1 +
arch/arm64/Kconfig.debug | 13 +
arch/arm64/include/asm/module.h | 6 +
arch/arm64/include/asm/stacktrace/common.h | 6 +
arch/arm64/include/asm/unwind_sframe.h | 12 +
arch/arm64/kernel/entry.S | 10 +
arch/arm64/kernel/module.c | 8 +
arch/arm64/kernel/setup.c | 2 +
arch/arm64/kernel/stacktrace.c | 242 ++++++++++-
arch/arm64/kernel/vdso/Makefile | 2 +-
.../{unwind_user_sframe.h => unwind_sframe.h} | 6 +-
arch/x86/include/asm/unwind_user.h | 12 +-
include/asm-generic/vmlinux.lds.h | 15 +
include/linux/sframe.h | 105 ++++-
include/linux/unwind_user_types.h | 41 --
kernel/unwind/Makefile | 2 +-
kernel/unwind/sframe.c | 408 ++++++++++++++----
kernel/unwind/user.c | 40 +-
21 files changed, 749 insertions(+), 206 deletions(-)
create mode 100644 arch/arm64/include/asm/unwind_sframe.h
rename arch/x86/include/asm/{unwind_user_sframe.h => unwind_sframe.h} (50%)
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply
* [PATCH v3 2/8] arm64, unwind: build kernel with sframe V3 info
From: Dylan Hatch @ 2026-04-06 18:49 UTC (permalink / raw)
To: Roman Gushchin, Weinan Liu, Will Deacon, Josh Poimboeuf,
Indu Bhagat, Peter Zijlstra, Steven Rostedt, Catalin Marinas,
Jiri Kosina
Cc: Dylan Hatch, Mark Rutland, Prasanna Kumar T S M, Puranjay Mohan,
Song Liu, joe.lawrence, linux-toolchains, linux-kernel,
live-patching, Jens Remus, linux-arm-kernel
In-Reply-To: <20260406185000.1378082-1-dylanbhatch@google.com>
Build with -Wa,--gsframe-3 flags to generate a .sframe section. This
will be used for in-kernel reliable stacktrace in cases where the frame
pointer alone is insufficient.
Currently, the sframe format only supports arm64, x86_64 and s390x
architectures.
Signed-off-by: Weinan Liu <wnliu@google.com>
Signed-off-by: Dylan Hatch <dylanbhatch@google.com>
Reviewed-by: Prasanna Kumar T S M <ptsm@linux.microsoft.com>
---
MAINTAINERS | 1 +
Makefile | 8 ++++++++
arch/Kconfig | 7 +++++++
arch/arm64/Kconfig | 1 +
arch/arm64/Kconfig.debug | 13 +++++++++++++
arch/arm64/include/asm/unwind_sframe.h | 12 ++++++++++++
arch/arm64/kernel/vdso/Makefile | 2 +-
include/asm-generic/vmlinux.lds.h | 15 +++++++++++++++
8 files changed, 58 insertions(+), 1 deletion(-)
create mode 100644 arch/arm64/include/asm/unwind_sframe.h
diff --git a/MAINTAINERS b/MAINTAINERS
index cfc7dec88da4..a7d75f9cb5f4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27561,6 +27561,7 @@ STACK UNWINDING
M: Josh Poimboeuf <jpoimboe@kernel.org>
M: Steven Rostedt <rostedt@goodmis.org>
S: Maintained
+F: arch/*/include/asm/unwind_sframe.h
F: include/linux/sframe.h
F: include/linux/unwind*.h
F: kernel/unwind/
diff --git a/Makefile b/Makefile
index 2b15f0b4a0cb..e03d09ea6a23 100644
--- a/Makefile
+++ b/Makefile
@@ -1110,6 +1110,14 @@ endif
# Ensure compilers do not transform certain loops into calls to wcslen()
KBUILD_CFLAGS += -fno-builtin-wcslen
+# build with sframe table
+ifdef CONFIG_SFRAME_UNWINDER
+CC_FLAGS_SFRAME := -Wa,--gsframe-3
+KBUILD_CFLAGS += $(CC_FLAGS_SFRAME)
+KBUILD_AFLAGS += $(CC_FLAGS_SFRAME)
+export CC_FLAGS_SFRAME
+endif
+
# change __FILE__ to the relative path to the source directory
ifdef building_out_of_srctree
KBUILD_CPPFLAGS += -fmacro-prefix-map=$(srcroot)/=
diff --git a/arch/Kconfig b/arch/Kconfig
index 6695c222c728..c87e489fa978 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -520,6 +520,13 @@ config SFRAME_VALIDATION
If unsure, say N.
+config ARCH_SUPPORTS_SFRAME_UNWINDER
+ bool
+ help
+ An architecture can select this if it enables the sframe (Simple
+ Frame) unwinder for unwinding kernel stack traces. It uses unwind
+ table that is directly generatedby toolchain based on DWARF CFI information.
+
config HAVE_PERF_REGS
bool
help
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 38dba5f7e4d2..189bc199ad2e 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -112,6 +112,7 @@ config ARM64
select ARCH_SUPPORTS_SCHED_SMT
select ARCH_SUPPORTS_SCHED_CLUSTER
select ARCH_SUPPORTS_SCHED_MC
+ select ARCH_SUPPORTS_SFRAME_UNWINDER
select ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH
select ARCH_WANT_COMPAT_IPC_PARSE_VERSION if COMPAT
select ARCH_WANT_DEFAULT_BPF_JIT
diff --git a/arch/arm64/Kconfig.debug b/arch/arm64/Kconfig.debug
index 265c4461031f..df291d64812f 100644
--- a/arch/arm64/Kconfig.debug
+++ b/arch/arm64/Kconfig.debug
@@ -20,4 +20,17 @@ config ARM64_RELOC_TEST
depends on m
tristate "Relocation testing module"
+config SFRAME_UNWINDER
+ bool "Sframe unwinder"
+ depends on AS_SFRAME3
+ depends on 64BIT
+ depends on ARCH_SUPPORTS_SFRAME_UNWINDER
+ select SFRAME_LOOKUP
+ help
+ This option enables the sframe (Simple Frame) unwinder for unwinding
+ kernel stack traces. It uses unwind table that is directly generated
+ by toolchain based on DWARF CFI information. In, practice this can
+ provide more reliable stacktrace results than unwinding with frame
+ pointers alone.
+
source "drivers/hwtracing/coresight/Kconfig"
diff --git a/arch/arm64/include/asm/unwind_sframe.h b/arch/arm64/include/asm/unwind_sframe.h
new file mode 100644
index 000000000000..1682c079e387
--- /dev/null
+++ b/arch/arm64/include/asm/unwind_sframe.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_ARM64_UNWIND_SFRAME_H
+#define _ASM_ARM64_UNWIND_SFRAME_H
+
+#ifdef CONFIG_ARM64
+
+#define SFRAME_REG_SP 31
+#define SFRAME_REG_FP 29
+
+#endif
+
+#endif /* _ASM_ARM64_UNWIND_SFRAME_H */
diff --git a/arch/arm64/kernel/vdso/Makefile b/arch/arm64/kernel/vdso/Makefile
index 7dec05dd33b7..c60ef921956f 100644
--- a/arch/arm64/kernel/vdso/Makefile
+++ b/arch/arm64/kernel/vdso/Makefile
@@ -38,7 +38,7 @@ ccflags-y += -DDISABLE_BRANCH_PROFILING -DBUILD_VDSO
CC_FLAGS_REMOVE_VDSO := $(CC_FLAGS_FTRACE) -Os $(CC_FLAGS_SCS) \
$(RANDSTRUCT_CFLAGS) $(KSTACK_ERASE_CFLAGS) \
$(GCC_PLUGINS_CFLAGS) \
- $(CC_FLAGS_LTO) $(CC_FLAGS_CFI) \
+ $(CC_FLAGS_LTO) $(CC_FLAGS_CFI) $(CC_FLAGS_SFRAME) \
-Wmissing-prototypes -Wmissing-declarations
CC_FLAGS_ADD_VDSO := -O2 -mcmodel=tiny -fasynchronous-unwind-tables
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 1e1580febe4b..0a5c2f6cc4c0 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -491,6 +491,8 @@
*(.rodata1) \
} \
\
+ SFRAME \
+ \
/* PCI quirks */ \
.pci_fixup : AT(ADDR(.pci_fixup) - LOAD_OFFSET) { \
BOUNDED_SECTION_PRE_LABEL(.pci_fixup_early, _pci_fixups_early, __start, __end) \
@@ -911,6 +913,19 @@
#define TRACEDATA
#endif
+#ifdef CONFIG_SFRAME_UNWINDER
+#define SFRAME \
+ /* sframe */ \
+ .sframe : AT(ADDR(.sframe) - LOAD_OFFSET) { \
+ __start_sframe_header = .; \
+ KEEP(*(.sframe)) \
+ KEEP(*(.init.sframe)) \
+ __stop_sframe_header = .; \
+ }
+#else
+#define SFRAME
+#endif
+
#ifdef CONFIG_PRINTK_INDEX
#define PRINTK_INDEX \
.printk_index : AT(ADDR(.printk_index) - LOAD_OFFSET) { \
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* [PATCH v3 1/8] sframe: Allow kernelspace sframe sections.
From: Dylan Hatch @ 2026-04-06 18:49 UTC (permalink / raw)
To: Roman Gushchin, Weinan Liu, Will Deacon, Josh Poimboeuf,
Indu Bhagat, Peter Zijlstra, Steven Rostedt, Catalin Marinas,
Jiri Kosina
Cc: Dylan Hatch, Mark Rutland, Prasanna Kumar T S M, Puranjay Mohan,
Song Liu, joe.lawrence, linux-toolchains, linux-kernel,
live-patching, Jens Remus, linux-arm-kernel
In-Reply-To: <20260406185000.1378082-1-dylanbhatch@google.com>
Generalize the sframe lookup code to support kernelspace sections. This
is done by defining a SFRAME_LOOKUP option that can be activated
separate from UNWIND_USER_SFRAME, as there will be other clients to this
library than just userspace unwind.
Sframe section location is now tracked in a separate sec_type field to
determine whether user-access functions are necessary to read the sframe
data. Relevant type delarations are moved and renamed to reflect the
non-user sframe support.
Signed-off-by: Dylan Hatch <dylanbhatch@google.com>
---
MAINTAINERS | 2 +-
arch/Kconfig | 4 +
.../{unwind_user_sframe.h => unwind_sframe.h} | 6 +-
arch/x86/include/asm/unwind_user.h | 12 +-
include/linux/sframe.h | 88 ++++--
include/linux/unwind_user_types.h | 41 ---
kernel/unwind/Makefile | 2 +-
kernel/unwind/sframe.c | 270 ++++++++++++------
kernel/unwind/user.c | 40 +--
9 files changed, 286 insertions(+), 179 deletions(-)
rename arch/x86/include/asm/{unwind_user_sframe.h => unwind_sframe.h} (50%)
diff --git a/MAINTAINERS b/MAINTAINERS
index 8c46465ee7a9..cfc7dec88da4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27557,7 +27557,7 @@ F: Documentation/driver-api/uio-howto.rst
F: drivers/uio/
F: include/linux/uio_driver.h
-USERSPACE STACK UNWINDING
+STACK UNWINDING
M: Josh Poimboeuf <jpoimboe@kernel.org>
M: Steven Rostedt <rostedt@goodmis.org>
S: Maintained
diff --git a/arch/Kconfig b/arch/Kconfig
index f1ed8bc0806d..6695c222c728 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -486,6 +486,9 @@ config AS_SFRAME3
def_bool $(as-instr,.cfi_startproc\n.cfi_endproc,-Wa$(comma)--gsframe-3)
select AS_SFRAME
+config SFRAME_LOOKUP
+ bool
+
config UNWIND_USER
bool
@@ -496,6 +499,7 @@ config HAVE_UNWIND_USER_FP
config HAVE_UNWIND_USER_SFRAME
bool
select UNWIND_USER
+ select SFRAME_LOOKUP
config SFRAME_VALIDATION
bool "Enable .sframe section debugging"
diff --git a/arch/x86/include/asm/unwind_user_sframe.h b/arch/x86/include/asm/unwind_sframe.h
similarity index 50%
rename from arch/x86/include/asm/unwind_user_sframe.h
rename to arch/x86/include/asm/unwind_sframe.h
index d828ae1a4aac..44d42e6ffde4 100644
--- a/arch/x86/include/asm/unwind_user_sframe.h
+++ b/arch/x86/include/asm/unwind_sframe.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef _ASM_X86_UNWIND_USER_SFRAME_H
-#define _ASM_X86_UNWIND_USER_SFRAME_H
+#ifndef _ASM_X86_UNWIND_SFRAME_H
+#define _ASM_X86_UNWIND_SFRAME_H
#ifdef CONFIG_X86_64
@@ -9,4 +9,4 @@
#endif
-#endif /* _ASM_X86_UNWIND_USER_SFRAME_H */
+#endif /* _ASM_X86_UNWIND_SFRAME_H */
diff --git a/arch/x86/include/asm/unwind_user.h b/arch/x86/include/asm/unwind_user.h
index ae46906c3b39..8fdab3581b86 100644
--- a/arch/x86/include/asm/unwind_user.h
+++ b/arch/x86/include/asm/unwind_user.h
@@ -55,30 +55,30 @@ static inline int unwind_user_get_reg(unsigned long *val, unsigned int regnum)
#define ARCH_INIT_USER_FP_FRAME(ws) \
.cfa = { \
- .rule = UNWIND_USER_CFA_RULE_FP_OFFSET,\
+ .rule = UNWIND_CFA_RULE_FP_OFFSET,\
.offset = 2*(ws), \
}, \
.ra = { \
- .rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\
+ .rule = UNWIND_RULE_CFA_OFFSET_DEREF,\
.offset = -1*(ws), \
}, \
.fp = { \
- .rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\
+ .rule = UNWIND_RULE_CFA_OFFSET_DEREF,\
.offset = -2*(ws), \
}, \
.outermost = false,
#define ARCH_INIT_USER_FP_ENTRY_FRAME(ws) \
.cfa = { \
- .rule = UNWIND_USER_CFA_RULE_SP_OFFSET,\
+ .rule = UNWIND_CFA_RULE_SP_OFFSET,\
.offset = 1*(ws), \
}, \
.ra = { \
- .rule = UNWIND_USER_RULE_CFA_OFFSET_DEREF,\
+ .rule = UNWIND_RULE_CFA_OFFSET_DEREF,\
.offset = -1*(ws), \
}, \
.fp = { \
- .rule = UNWIND_USER_RULE_RETAIN,\
+ .rule = UNWIND_RULE_RETAIN,\
}, \
.outermost = false,
diff --git a/include/linux/sframe.h b/include/linux/sframe.h
index b79c5ec09229..673b9edfc921 100644
--- a/include/linux/sframe.h
+++ b/include/linux/sframe.h
@@ -4,36 +4,85 @@
#include <linux/mm_types.h>
#include <linux/srcu.h>
-#include <linux/unwind_user_types.h>
-#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+#define UNWIND_RULE_DEREF BIT(31)
+
+enum unwind_cfa_rule {
+ UNWIND_CFA_RULE_SP_OFFSET, /* CFA = SP + offset */
+ UNWIND_CFA_RULE_FP_OFFSET, /* CFA = FP + offset */
+ UNWIND_CFA_RULE_REG_OFFSET, /* CFA = reg + offset */
+ /* DEREF variants */
+ UNWIND_CFA_RULE_REG_OFFSET_DEREF = /* CFA = *(reg + offset) */
+ UNWIND_CFA_RULE_REG_OFFSET | UNWIND_RULE_DEREF,
+};
+
+struct unwind_cfa_rule_data {
+ enum unwind_cfa_rule rule;
+ s32 offset;
+ unsigned int regnum;
+};
+
+enum unwind_rule {
+ UNWIND_RULE_RETAIN, /* entity = entity */
+ UNWIND_RULE_CFA_OFFSET, /* entity = CFA + offset */
+ UNWIND_RULE_REG_OFFSET, /* entity = register + offset */
+ /* DEREF variants */
+ UNWIND_RULE_CFA_OFFSET_DEREF = /* entity = *(CFA + offset) */
+ UNWIND_RULE_CFA_OFFSET | UNWIND_RULE_DEREF,
+ UNWIND_RULE_REG_OFFSET_DEREF = /* entity = *(register + offset) */
+ UNWIND_RULE_REG_OFFSET | UNWIND_RULE_DEREF,
+};
+
+struct unwind_rule_data {
+ enum unwind_rule rule;
+ s32 offset;
+ unsigned int regnum;
+};
+
+struct unwind_frame {
+ struct unwind_cfa_rule_data cfa;
+ struct unwind_rule_data ra;
+ struct unwind_rule_data fp;
+ bool outermost;
+};
+
+#ifdef CONFIG_SFRAME_LOOKUP
+
+enum sframe_sec_type {
+ SFRAME_KERNEL,
+ SFRAME_USER,
+};
struct sframe_section {
- struct rcu_head rcu;
+ struct rcu_head rcu;
#ifdef CONFIG_DYNAMIC_DEBUG
- const char *filename;
+ const char *filename;
#endif
- unsigned long sframe_start;
- unsigned long sframe_end;
- unsigned long text_start;
- unsigned long text_end;
-
- unsigned long fdes_start;
- unsigned long fres_start;
- unsigned long fres_end;
- unsigned int num_fdes;
-
- signed char ra_off;
- signed char fp_off;
+ enum sframe_sec_type sec_type;
+ unsigned long sframe_start;
+ unsigned long sframe_end;
+ unsigned long text_start;
+ unsigned long text_end;
+
+ unsigned long fdes_start;
+ unsigned long fres_start;
+ unsigned long fres_end;
+ unsigned int num_fdes;
+
+ signed char ra_off;
+ signed char fp_off;
};
+#endif /* CONFIG_SFRAME_LOOKUP */
+
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+
#define INIT_MM_SFRAME .sframe_mt = MTREE_INIT(sframe_mt, 0),
extern void sframe_free_mm(struct mm_struct *mm);
extern int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
unsigned long text_start, unsigned long text_end);
extern int sframe_remove_section(unsigned long sframe_addr);
-extern int sframe_find(unsigned long ip, struct unwind_user_frame *frame);
static inline bool current_has_sframe(void)
{
@@ -42,6 +91,8 @@ static inline bool current_has_sframe(void)
return mm && !mtree_empty(&mm->sframe_mt);
}
+extern int sframe_find_user(unsigned long ip, struct unwind_frame *frame);
+
#else /* !CONFIG_HAVE_UNWIND_USER_SFRAME */
#define INIT_MM_SFRAME
@@ -52,9 +103,10 @@ static inline int sframe_add_section(unsigned long sframe_start, unsigned long s
return -ENOSYS;
}
static inline int sframe_remove_section(unsigned long sframe_addr) { return -ENOSYS; }
-static inline int sframe_find(unsigned long ip, struct unwind_user_frame *frame) { return -ENOSYS; }
static inline bool current_has_sframe(void) { return false; }
+static inline int sframe_find_user(unsigned long ip, struct unwind_frame *frame) { return -ENOSYS; }
+
#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */
#endif /* _LINUX_SFRAME_H */
diff --git a/include/linux/unwind_user_types.h b/include/linux/unwind_user_types.h
index 059e5c76f2f3..646e5fb774db 100644
--- a/include/linux/unwind_user_types.h
+++ b/include/linux/unwind_user_types.h
@@ -27,47 +27,6 @@ struct unwind_stacktrace {
unsigned long *entries;
};
-#define UNWIND_USER_RULE_DEREF BIT(31)
-
-enum unwind_user_cfa_rule {
- UNWIND_USER_CFA_RULE_SP_OFFSET, /* CFA = SP + offset */
- UNWIND_USER_CFA_RULE_FP_OFFSET, /* CFA = FP + offset */
- UNWIND_USER_CFA_RULE_REG_OFFSET, /* CFA = reg + offset */
- /* DEREF variants */
- UNWIND_USER_CFA_RULE_REG_OFFSET_DEREF = /* CFA = *(reg + offset) */
- UNWIND_USER_CFA_RULE_REG_OFFSET | UNWIND_USER_RULE_DEREF,
-};
-
-struct unwind_user_cfa_rule_data {
- enum unwind_user_cfa_rule rule;
- s32 offset;
- unsigned int regnum;
-};
-
-enum unwind_user_rule {
- UNWIND_USER_RULE_RETAIN, /* entity = entity */
- UNWIND_USER_RULE_CFA_OFFSET, /* entity = CFA + offset */
- UNWIND_USER_RULE_REG_OFFSET, /* entity = register + offset */
- /* DEREF variants */
- UNWIND_USER_RULE_CFA_OFFSET_DEREF = /* entity = *(CFA + offset) */
- UNWIND_USER_RULE_CFA_OFFSET | UNWIND_USER_RULE_DEREF,
- UNWIND_USER_RULE_REG_OFFSET_DEREF = /* entity = *(register + offset) */
- UNWIND_USER_RULE_REG_OFFSET | UNWIND_USER_RULE_DEREF,
-};
-
-struct unwind_user_rule_data {
- enum unwind_user_rule rule;
- s32 offset;
- unsigned int regnum;
-};
-
-struct unwind_user_frame {
- struct unwind_user_cfa_rule_data cfa;
- struct unwind_user_rule_data ra;
- struct unwind_user_rule_data fp;
- bool outermost;
-};
-
struct unwind_user_state {
unsigned long ip;
unsigned long sp;
diff --git a/kernel/unwind/Makefile b/kernel/unwind/Makefile
index 146038165865..6b51302308d0 100644
--- a/kernel/unwind/Makefile
+++ b/kernel/unwind/Makefile
@@ -1,2 +1,2 @@
obj-$(CONFIG_UNWIND_USER) += user.o deferred.o
- obj-$(CONFIG_HAVE_UNWIND_USER_SFRAME) += sframe.o
+ obj-$(CONFIG_SFRAME_LOOKUP) += sframe.o
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index f24997e84e05..cad4384dfb4f 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -12,8 +12,7 @@
#include <linux/mm.h>
#include <linux/string_helpers.h>
#include <linux/sframe.h>
-#include <asm/unwind_user_sframe.h>
-#include <linux/unwind_user_types.h>
+#include <asm/unwind_sframe.h>
#include "sframe.h"
#include "sframe_debug.h"
@@ -44,8 +43,6 @@ struct sframe_fre_internal {
unsigned char dw_size;
};
-DEFINE_STATIC_SRCU(sframe_srcu);
-
static __always_inline unsigned char fre_type_to_size(unsigned char fre_type)
{
if (fre_type > 2)
@@ -60,6 +57,78 @@ static __always_inline unsigned char dataword_size_enum_to_size(unsigned char da
return 1 << dataword_size;
}
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+
+DEFINE_STATIC_SRCU(sframe_srcu);
+
+#define UNSAFE_USER_COPY(to, from, size, label) \
+ unsafe_copy_from_user(to, (void __user *)from, size, label)
+
+#define UNSAFE_USER_GET(to, from, type, label) \
+ unsafe_get_user(to, (type __user *)from, label)
+
+#else /* !CONFIG_HAVE_UNWIND_USER_SFRAME */
+
+#define UNSAFE_USER_COPY(to, from, size, label) do { \
+ (void)to; (void)from; (void)size; \
+ goto label; \
+} while (0)
+
+#define UNSAFE_USER_GET(to, from, type, label) do { \
+ (void)to; (void)from; \
+ goto label; \
+} while (0)
+
+#endif /* !CONFIG_HAVE_UNWIND_USER_SFRAME */
+
+#ifdef CONFIG_SFRAME_UNWINDER
+
+#define KERNEL_COPY(to, from, size) memcpy(to, (void *)from, size)
+#define KERNEL_GET(to, from, type) ({ (to) = *(type *)(from); })
+
+#else /* !CONFIG_SFRAME_UNWINDER */
+
+#define KERNEL_COPY(to, from, size) do { \
+ (void)(to); (void)(from); (void)size; \
+ return -EFAULT; \
+} while (0)
+
+#define KERNEL_GET(to, from, type) do { \
+ (void)(to); (void)(from); \
+ return -EFAULT; \
+} while (0)
+
+
+#endif /* !CONFIG_SFRAME_UNWINDER */
+
+#define DATA_COPY(sec, to, from, size, label) \
+({ \
+ switch (sec->sec_type) { \
+ case SFRAME_KERNEL: \
+ KERNEL_COPY(to, from, size); \
+ break; \
+ case SFRAME_USER: \
+ UNSAFE_USER_COPY(to, from, size, label); \
+ break; \
+ default: \
+ return -EFAULT; \
+ } \
+})
+
+#define DATA_GET(sec, to, from, type, label) \
+({ \
+ switch (sec->sec_type) { \
+ case SFRAME_KERNEL: \
+ KERNEL_GET(to, from, type); \
+ break; \
+ case SFRAME_USER: \
+ UNSAFE_USER_GET(to, from, type, label); \
+ break; \
+ default: \
+ return -EFAULT; \
+ } \
+})
+
static __always_inline int __read_fde(struct sframe_section *sec,
unsigned int fde_num,
struct sframe_fde_internal *fde)
@@ -69,8 +138,8 @@ static __always_inline int __read_fde(struct sframe_section *sec,
struct sframe_fda_v3 _fda;
fde_addr = sec->fdes_start + (fde_num * sizeof(struct sframe_fde_v3));
- unsafe_copy_from_user(&_fde, (void __user *)fde_addr,
- sizeof(struct sframe_fde_v3), Efault);
+ DATA_COPY(sec, &_fde, fde_addr,
+ sizeof(struct sframe_fde_v3), Efault);
func_addr = fde_addr + _fde.func_start_off;
if (func_addr < sec->text_start || func_addr > sec->text_end)
@@ -79,8 +148,8 @@ static __always_inline int __read_fde(struct sframe_section *sec,
fda_addr = sec->fres_start + _fde.fres_off;
if (fda_addr + sizeof(struct sframe_fda_v3) > sec->fres_end)
return -EINVAL;
- unsafe_copy_from_user(&_fda, (void __user *)fda_addr,
- sizeof(struct sframe_fda_v3), Efault);
+ DATA_COPY(sec, &_fda, fda_addr,
+ sizeof(struct sframe_fda_v3), Efault);
fde->func_addr = func_addr;
fde->func_size = _fde.func_size;
@@ -102,21 +171,21 @@ static __always_inline int __find_fde(struct sframe_section *sec,
struct sframe_fde_internal *fde)
{
unsigned long func_addr_low = 0, func_addr_high = ULONG_MAX;
- struct sframe_fde_v3 __user *first, *low, *high, *found = NULL;
+ struct sframe_fde_v3 *first, *low, *high, *found = NULL;
int ret;
- first = (void __user *)sec->fdes_start;
+ first = (void *)sec->fdes_start;
low = first;
high = first + sec->num_fdes - 1;
while (low <= high) {
- struct sframe_fde_v3 __user *mid;
+ struct sframe_fde_v3 *mid;
s64 func_off;
unsigned long func_addr;
mid = low + ((high - low) / 2);
- unsafe_get_user(func_off, (s64 __user *)mid, Efault);
+ DATA_GET(sec, func_off, mid, s64, Efault);
func_addr = (unsigned long)mid + func_off;
if (ip >= func_addr) {
@@ -154,47 +223,47 @@ static __always_inline int __find_fde(struct sframe_section *sec,
return -EFAULT;
}
-#define ____UNSAFE_GET_USER_INC(to, from, type, label) \
+#define ____GET_INC(sec, to, from, type, label) \
({ \
type __to; \
- unsafe_get_user(__to, (type __user *)from, label); \
+ DATA_GET(sec, __to, from, type, label); \
from += sizeof(__to); \
to = __to; \
})
-#define __UNSAFE_GET_USER_INC(to, from, size, label, u_or_s) \
+#define __GET_INC(sec, to, from, size, label, u_or_s) \
({ \
switch (size) { \
case 1: \
- ____UNSAFE_GET_USER_INC(to, from, u_or_s##8, label); \
+ ____GET_INC(sec, to, from, u_or_s##8, label); \
break; \
case 2: \
- ____UNSAFE_GET_USER_INC(to, from, u_or_s##16, label); \
+ ____GET_INC(sec, to, from, u_or_s##16, label); \
break; \
case 4: \
- ____UNSAFE_GET_USER_INC(to, from, u_or_s##32, label); \
+ ____GET_INC(sec, to, from, u_or_s##32, label); \
break; \
default: \
return -EFAULT; \
} \
})
-#define UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label) \
- __UNSAFE_GET_USER_INC(to, from, size, label, u)
+#define GET_UNSIGNED_INC(sec, to, from, size, label) \
+ __GET_INC(sec, to, from, size, label, u)
-#define UNSAFE_GET_USER_SIGNED_INC(to, from, size, label) \
- __UNSAFE_GET_USER_INC(to, from, size, label, s)
+#define GET_SIGNED_INC(sec, to, from, size, label) \
+ __GET_INC(sec, to, from, size, label, s)
-#define UNSAFE_GET_USER_INC(to, from, size, label) \
- _Generic(to, \
- u8 : UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \
- u16 : UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \
- u32 : UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \
- u64 : UNSAFE_GET_USER_UNSIGNED_INC(to, from, size, label), \
- s8 : UNSAFE_GET_USER_SIGNED_INC(to, from, size, label), \
- s16 : UNSAFE_GET_USER_SIGNED_INC(to, from, size, label), \
- s32 : UNSAFE_GET_USER_SIGNED_INC(to, from, size, label), \
- s64 : UNSAFE_GET_USER_SIGNED_INC(to, from, size, label))
+#define GET_INC(sec, to, from, size, label) \
+ _Generic(to, \
+ u8 : GET_UNSIGNED_INC(sec, to, from, size, label), \
+ u16 : GET_UNSIGNED_INC(sec, to, from, size, label), \
+ u32 : GET_UNSIGNED_INC(sec, to, from, size, label), \
+ u64 : GET_UNSIGNED_INC(sec, to, from, size, label), \
+ s8 : GET_SIGNED_INC(sec, to, from, size, label), \
+ s16 : GET_SIGNED_INC(sec, to, from, size, label), \
+ s32 : GET_SIGNED_INC(sec, to, from, size, label), \
+ s64 : GET_SIGNED_INC(sec, to, from, size, label))
static __always_inline int
__read_regular_fre_datawords(struct sframe_section *sec,
@@ -207,19 +276,19 @@ __read_regular_fre_datawords(struct sframe_section *sec,
s32 cfa_off, ra_off, fp_off;
unsigned int cfa_regnum;
- UNSAFE_GET_USER_INC(cfa_off, cur, dataword_size, Efault);
+ GET_INC(sec, cfa_off, cur, dataword_size, Efault);
dataword_count--;
ra_off = sec->ra_off;
if (!ra_off && dataword_count) {
dataword_count--;
- UNSAFE_GET_USER_INC(ra_off, cur, dataword_size, Efault);
+ GET_INC(sec, ra_off, cur, dataword_size, Efault);
}
fp_off = sec->fp_off;
if (!fp_off && dataword_count) {
dataword_count--;
- UNSAFE_GET_USER_INC(fp_off, cur, dataword_size, Efault);
+ GET_INC(sec, fp_off, cur, dataword_size, Efault);
}
if (dataword_count)
@@ -255,17 +324,17 @@ __read_flex_fde_fre_datawords(struct sframe_section *sec,
if (dataword_count < 2)
return -EFAULT;
- UNSAFE_GET_USER_INC(cfa_ctl, cur, dataword_size, Efault);
- UNSAFE_GET_USER_INC(cfa_off, cur, dataword_size, Efault);
+ GET_INC(sec, cfa_ctl, cur, dataword_size, Efault);
+ GET_INC(sec, cfa_off, cur, dataword_size, Efault);
dataword_count -= 2;
ra_off = sec->ra_off;
ra_ctl = ra_off ? 2 : 0; /* regnum=0, deref_p=(ra_off != 0), reg_p=0 */
if (dataword_count >= 2) {
- UNSAFE_GET_USER_INC(ra_ctl, cur, dataword_size, Efault);
+ GET_INC(sec, ra_ctl, cur, dataword_size, Efault);
dataword_count--;
if (ra_ctl) {
- UNSAFE_GET_USER_INC(ra_off, cur, dataword_size, Efault);
+ GET_INC(sec, ra_off, cur, dataword_size, Efault);
dataword_count--;
} else {
/* Padding RA location info */
@@ -276,10 +345,10 @@ __read_flex_fde_fre_datawords(struct sframe_section *sec,
fp_off = sec->fp_off;
fp_ctl = fp_off ? 2 : 0; /* regnum=0, deref_p=(fp_off != 0), reg_p=0 */
if (dataword_count >= 2) {
- UNSAFE_GET_USER_INC(fp_ctl, cur, dataword_size, Efault);
+ GET_INC(sec, fp_ctl, cur, dataword_size, Efault);
dataword_count--;
if (fp_ctl) {
- UNSAFE_GET_USER_INC(fp_off, cur, dataword_size, Efault);
+ GET_INC(sec, fp_off, cur, dataword_size, Efault);
dataword_count--;
} else {
/* Padding FP location info */
@@ -353,11 +422,11 @@ static __always_inline int __read_fre(struct sframe_section *sec,
if (fre_addr + addr_size + 1 > sec->fres_end)
return -EFAULT;
- UNSAFE_GET_USER_INC(ip_off, cur, addr_size, Efault);
+ GET_INC(sec, ip_off, cur, addr_size, Efault);
if (fde_pctype == SFRAME_FDE_PCTYPE_INC && ip_off > fde->func_size)
return -EFAULT;
- UNSAFE_GET_USER_INC(info, cur, 1, Efault);
+ GET_INC(sec, info, cur, 1, Efault);
dataword_count = SFRAME_V3_FRE_DATAWORD_COUNT(info);
dataword_size = dataword_size_enum_to_size(SFRAME_V3_FRE_DATAWORD_SIZE(info));
if (!dataword_size)
@@ -380,7 +449,7 @@ static __always_inline int __read_fre(struct sframe_section *sec,
}
static __always_inline int
-sframe_init_cfa_rule_data(struct unwind_user_cfa_rule_data *cfa_rule_data,
+sframe_init_cfa_rule_data(struct unwind_cfa_rule_data *cfa_rule_data,
u32 ctlword, s32 offset)
{
bool deref_p = SFRAME_V3_FLEX_FDE_CTLWORD_DEREF_P(ctlword);
@@ -391,13 +460,13 @@ sframe_init_cfa_rule_data(struct unwind_user_cfa_rule_data *cfa_rule_data,
switch (regnum) {
case SFRAME_REG_SP:
- cfa_rule_data->rule = UNWIND_USER_CFA_RULE_SP_OFFSET;
+ cfa_rule_data->rule = UNWIND_CFA_RULE_SP_OFFSET;
break;
case SFRAME_REG_FP:
- cfa_rule_data->rule = UNWIND_USER_CFA_RULE_FP_OFFSET;
+ cfa_rule_data->rule = UNWIND_CFA_RULE_FP_OFFSET;
break;
default:
- cfa_rule_data->rule = UNWIND_USER_CFA_RULE_REG_OFFSET;
+ cfa_rule_data->rule = UNWIND_CFA_RULE_REG_OFFSET;
cfa_rule_data->regnum = regnum;
}
} else {
@@ -405,7 +474,7 @@ sframe_init_cfa_rule_data(struct unwind_user_cfa_rule_data *cfa_rule_data,
}
if (deref_p)
- cfa_rule_data->rule |= UNWIND_USER_RULE_DEREF;
+ cfa_rule_data->rule |= UNWIND_RULE_DEREF;
cfa_rule_data->offset = offset;
@@ -413,27 +482,27 @@ sframe_init_cfa_rule_data(struct unwind_user_cfa_rule_data *cfa_rule_data,
}
static __always_inline void
-sframe_init_rule_data(struct unwind_user_rule_data *rule_data,
+sframe_init_rule_data(struct unwind_rule_data *rule_data,
u32 ctlword, s32 offset)
{
bool deref_p = SFRAME_V3_FLEX_FDE_CTLWORD_DEREF_P(ctlword);
bool reg_p = SFRAME_V3_FLEX_FDE_CTLWORD_REG_P(ctlword);
if (!ctlword && !offset) {
- rule_data->rule = UNWIND_USER_RULE_RETAIN;
+ rule_data->rule = UNWIND_RULE_RETAIN;
return;
}
if (reg_p) {
unsigned int regnum = SFRAME_V3_FLEX_FDE_CTLWORD_REGNUM(ctlword);
- rule_data->rule = UNWIND_USER_RULE_REG_OFFSET;
+ rule_data->rule = UNWIND_RULE_REG_OFFSET;
rule_data->regnum = regnum;
} else {
- rule_data->rule = UNWIND_USER_RULE_CFA_OFFSET;
+ rule_data->rule = UNWIND_RULE_CFA_OFFSET;
}
if (deref_p)
- rule_data->rule |= UNWIND_USER_RULE_DEREF;
+ rule_data->rule |= UNWIND_RULE_DEREF;
rule_data->offset = offset;
}
@@ -441,7 +510,7 @@ sframe_init_rule_data(struct unwind_user_rule_data *rule_data,
static __always_inline int __find_fre(struct sframe_section *sec,
struct sframe_fde_internal *fde,
unsigned long ip,
- struct unwind_user_frame *frame)
+ struct unwind_frame *frame)
{
unsigned char fde_pctype = SFRAME_V3_FDE_PCTYPE(fde->info);
struct sframe_fre_internal *fre, *prev_fre = NULL;
@@ -501,40 +570,18 @@ static __always_inline int __find_fre(struct sframe_section *sec,
return 0;
}
-int sframe_find(unsigned long ip, struct unwind_user_frame *frame)
+static __always_inline int __sframe_find(struct sframe_section *sec,
+ unsigned long ip,
+ struct unwind_frame *frame)
{
- struct mm_struct *mm = current->mm;
- struct sframe_section *sec;
struct sframe_fde_internal fde;
int ret;
- if (!mm)
- return -EINVAL;
-
- guard(srcu)(&sframe_srcu);
-
- sec = mtree_load(&mm->sframe_mt, ip);
- if (!sec)
- return -EINVAL;
-
- if (!user_read_access_begin((void __user *)sec->sframe_start,
- sec->sframe_end - sec->sframe_start))
- return -EFAULT;
-
ret = __find_fde(sec, ip, &fde);
if (ret)
- goto end;
-
- ret = __find_fre(sec, &fde, ip, frame);
-end:
- user_read_access_end();
-
- if (ret == -EFAULT) {
- dbg_sec("removing bad .sframe section\n");
- WARN_ON_ONCE(sframe_remove_section(sec->sframe_start));
- }
+ return ret;
- return ret;
+ return __find_fre(sec, &fde, ip, frame);
}
#ifdef CONFIG_SFRAME_VALIDATION
@@ -657,20 +704,23 @@ static int sframe_validate_section(struct sframe_section *sec) { return 0; }
#endif /* !CONFIG_SFRAME_VALIDATION */
-static void free_section(struct sframe_section *sec)
-{
- dbg_free(sec);
- kfree(sec);
-}
-
static int sframe_read_header(struct sframe_section *sec)
{
unsigned long header_end, fdes_start, fdes_end, fres_start, fres_end;
struct sframe_header shdr;
unsigned int num_fdes;
- if (copy_from_user(&shdr, (void __user *)sec->sframe_start, sizeof(shdr))) {
- dbg_sec("header usercopy failed\n");
+ switch (sec->sec_type) {
+ case SFRAME_USER:
+ if (copy_from_user(&shdr, (void __user *)sec->sframe_start, sizeof(shdr))) {
+ dbg_sec("header usercopy failed\n");
+ return -EFAULT;
+ }
+ break;
+ case SFRAME_KERNEL:
+ shdr = *(struct sframe_header *)sec->sframe_start;
+ break;
+ default:
return -EFAULT;
}
@@ -717,6 +767,45 @@ static int sframe_read_header(struct sframe_section *sec)
return 0;
}
+#ifdef CONFIG_HAVE_UNWIND_USER_SFRAME
+
+int sframe_find_user(unsigned long ip, struct unwind_frame *frame)
+{
+ struct mm_struct *mm = current->mm;
+ struct sframe_section *sec;
+ int ret;
+
+ if (!mm)
+ return -EINVAL;
+
+ guard(srcu)(&sframe_srcu);
+
+ sec = mtree_load(&mm->sframe_mt, ip);
+ if (!sec)
+ return -EINVAL;
+
+ if (!user_read_access_begin((void __user *)sec->sframe_start,
+ sec->sframe_end - sec->sframe_start))
+ return -EFAULT;
+
+ ret = __sframe_find(sec, ip, frame);
+
+ user_read_access_end();
+
+ if (ret == -EFAULT) {
+ dbg_sec("removing bad .sframe section\n");
+ WARN_ON_ONCE(sframe_remove_section(sec->sframe_start));
+ }
+
+ return ret;
+}
+
+static void free_section(struct sframe_section *sec)
+{
+ dbg_free(sec);
+ kfree(sec);
+}
+
int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
unsigned long text_start, unsigned long text_end)
{
@@ -753,6 +842,7 @@ int sframe_add_section(unsigned long sframe_start, unsigned long sframe_end,
if (!sec)
return -ENOMEM;
+ sec->sec_type = SFRAME_USER;
sec->sframe_start = sframe_start;
sec->sframe_end = sframe_end;
sec->text_start = text_start;
@@ -838,3 +928,5 @@ void sframe_free_mm(struct mm_struct *mm)
mtree_destroy(&mm->sframe_mt);
}
+
+#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */
diff --git a/kernel/unwind/user.c b/kernel/unwind/user.c
index eb7d9489f671..f9abd08ed83b 100644
--- a/kernel/unwind/user.c
+++ b/kernel/unwind/user.c
@@ -28,7 +28,7 @@ get_user_word(unsigned long *word, unsigned long base, int off, unsigned int ws)
}
static int unwind_user_next_common(struct unwind_user_state *state,
- const struct unwind_user_frame *frame)
+ const struct unwind_frame *frame)
{
unsigned long cfa, fp, ra;
@@ -40,16 +40,16 @@ static int unwind_user_next_common(struct unwind_user_state *state,
/* Get the Canonical Frame Address (CFA) */
switch (frame->cfa.rule) {
- case UNWIND_USER_CFA_RULE_SP_OFFSET:
+ case UNWIND_CFA_RULE_SP_OFFSET:
cfa = state->sp;
break;
- case UNWIND_USER_CFA_RULE_FP_OFFSET:
+ case UNWIND_CFA_RULE_FP_OFFSET:
if (state->fp < state->sp)
return -EINVAL;
cfa = state->fp;
break;
- case UNWIND_USER_CFA_RULE_REG_OFFSET:
- case UNWIND_USER_CFA_RULE_REG_OFFSET_DEREF:
+ case UNWIND_CFA_RULE_REG_OFFSET:
+ case UNWIND_CFA_RULE_REG_OFFSET_DEREF:
if (!state->topmost || unwind_user_get_reg(&cfa, frame->cfa.regnum))
return -EINVAL;
break;
@@ -58,7 +58,7 @@ static int unwind_user_next_common(struct unwind_user_state *state,
return -EINVAL;
}
cfa += frame->cfa.offset;
- if (frame->cfa.rule & UNWIND_USER_RULE_DEREF &&
+ if (frame->cfa.rule & UNWIND_RULE_DEREF &&
get_user_word(&cfa, cfa, 0, state->ws))
return -EINVAL;
@@ -76,16 +76,16 @@ static int unwind_user_next_common(struct unwind_user_state *state,
/* Get the Return Address (RA) */
switch (frame->ra.rule) {
- case UNWIND_USER_RULE_RETAIN:
+ case UNWIND_RULE_RETAIN:
if (!state->topmost || unwind_user_get_ra_reg(&ra))
return -EINVAL;
break;
/* UNWIND_USER_RULE_CFA_OFFSET not implemented on purpose */
- case UNWIND_USER_RULE_CFA_OFFSET_DEREF:
+ case UNWIND_RULE_CFA_OFFSET_DEREF:
ra = cfa + frame->ra.offset;
break;
- case UNWIND_USER_RULE_REG_OFFSET:
- case UNWIND_USER_RULE_REG_OFFSET_DEREF:
+ case UNWIND_RULE_REG_OFFSET:
+ case UNWIND_RULE_REG_OFFSET_DEREF:
if (!state->topmost || unwind_user_get_reg(&ra, frame->ra.regnum))
return -EINVAL;
ra += frame->ra.offset;
@@ -94,21 +94,21 @@ static int unwind_user_next_common(struct unwind_user_state *state,
WARN_ON_ONCE(1);
return -EINVAL;
}
- if (frame->ra.rule & UNWIND_USER_RULE_DEREF &&
+ if (frame->ra.rule & UNWIND_RULE_DEREF &&
get_user_word(&ra, ra, 0, state->ws))
return -EINVAL;
/* Get the Frame Pointer (FP) */
switch (frame->fp.rule) {
- case UNWIND_USER_RULE_RETAIN:
+ case UNWIND_RULE_RETAIN:
fp = state->fp;
break;
/* UNWIND_USER_RULE_CFA_OFFSET not implemented on purpose */
- case UNWIND_USER_RULE_CFA_OFFSET_DEREF:
+ case UNWIND_RULE_CFA_OFFSET_DEREF:
fp = cfa + frame->fp.offset;
break;
- case UNWIND_USER_RULE_REG_OFFSET:
- case UNWIND_USER_RULE_REG_OFFSET_DEREF:
+ case UNWIND_RULE_REG_OFFSET:
+ case UNWIND_RULE_REG_OFFSET_DEREF:
if (!state->topmost || unwind_user_get_reg(&fp, frame->fp.regnum))
return -EINVAL;
fp += frame->fp.offset;
@@ -117,7 +117,7 @@ static int unwind_user_next_common(struct unwind_user_state *state,
WARN_ON_ONCE(1);
return -EINVAL;
}
- if (frame->fp.rule & UNWIND_USER_RULE_DEREF &&
+ if (frame->fp.rule & UNWIND_RULE_DEREF &&
get_user_word(&fp, fp, 0, state->ws))
return -EINVAL;
@@ -133,13 +133,13 @@ static int unwind_user_next_fp(struct unwind_user_state *state)
struct pt_regs *regs = task_pt_regs(current);
if (state->topmost && unwind_user_at_function_start(regs)) {
- const struct unwind_user_frame fp_entry_frame = {
+ const struct unwind_frame fp_entry_frame = {
ARCH_INIT_USER_FP_ENTRY_FRAME(state->ws)
};
return unwind_user_next_common(state, &fp_entry_frame);
}
- const struct unwind_user_frame fp_frame = {
+ const struct unwind_frame fp_frame = {
ARCH_INIT_USER_FP_FRAME(state->ws)
};
return unwind_user_next_common(state, &fp_frame);
@@ -147,10 +147,10 @@ static int unwind_user_next_fp(struct unwind_user_state *state)
static int unwind_user_next_sframe(struct unwind_user_state *state)
{
- struct unwind_user_frame frame;
+ struct unwind_frame frame;
/* sframe expects the frame to be local storage */
- if (sframe_find(state->ip, &frame))
+ if (sframe_find_user(state->ip, &frame))
return -ENOENT;
return unwind_user_next_common(state, &frame);
}
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* [PATCH v3 3/8] arm64: entry: add unwind info for various kernel entries
From: Dylan Hatch @ 2026-04-06 18:49 UTC (permalink / raw)
To: Roman Gushchin, Weinan Liu, Will Deacon, Josh Poimboeuf,
Indu Bhagat, Peter Zijlstra, Steven Rostedt, Catalin Marinas,
Jiri Kosina
Cc: Dylan Hatch, Mark Rutland, Prasanna Kumar T S M, Puranjay Mohan,
Song Liu, joe.lawrence, linux-toolchains, linux-kernel,
live-patching, Jens Remus, linux-arm-kernel
In-Reply-To: <20260406185000.1378082-1-dylanbhatch@google.com>
From: Weinan Liu <wnliu@google.com>
DWARF CFI (Call Frame Information) specifies how to recover the return
address and callee-saved registers at each PC in a given function.
Compilers are able to generate the CFI annotations when they compile
the code to assembly language. For handcrafted assembly, we need to
annotate them by hand.
Annotate CFI unwind info for assembly for interrupt and exception
handlers.
Signed-off-by: Weinan Liu <wnliu@google.com>
Signed-off-by: Dylan Hatch <dylanbhatch@google.com>
---
arch/arm64/kernel/entry.S | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S
index f8018b5c1f9a..3148ede8c2c6 100644
--- a/arch/arm64/kernel/entry.S
+++ b/arch/arm64/kernel/entry.S
@@ -575,7 +575,12 @@ SYM_CODE_START_LOCAL(el\el\ht\()_\regsize\()_\label)
.if \el == 0
b ret_to_user
.else
+ .cfi_startproc
+ .cfi_def_cfa_offset PT_REGS_SIZE
+ .cfi_offset 29, S_FP - PT_REGS_SIZE
+ .cfi_offset 30, S_LR - PT_REGS_SIZE
b ret_to_kernel
+ .cfi_endproc
.endif
SYM_CODE_END(el\el\ht\()_\regsize\()_\label)
.endm
@@ -889,6 +894,10 @@ SYM_FUNC_START(call_on_irq_stack)
add sp, x16, #IRQ_STACK_SIZE
restore_irq x9
blr x1
+ .cfi_startproc
+ .cfi_def_cfa 29, 16
+ .cfi_offset 29, -16
+ .cfi_offset 30, -8
save_and_disable_daif x9
/*
@@ -900,6 +909,7 @@ SYM_FUNC_START(call_on_irq_stack)
scs_load_current
restore_irq x9
ret
+ .cfi_endproc
SYM_FUNC_END(call_on_irq_stack)
NOKPROBE(call_on_irq_stack)
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* [PATCH v3 4/8] sframe: Provide PC lookup for vmlinux .sframe section.
From: Dylan Hatch @ 2026-04-06 18:49 UTC (permalink / raw)
To: Roman Gushchin, Weinan Liu, Will Deacon, Josh Poimboeuf,
Indu Bhagat, Peter Zijlstra, Steven Rostedt, Catalin Marinas,
Jiri Kosina
Cc: Dylan Hatch, Mark Rutland, Prasanna Kumar T S M, Puranjay Mohan,
Song Liu, joe.lawrence, linux-toolchains, linux-kernel,
live-patching, Jens Remus, linux-arm-kernel
In-Reply-To: <20260406185000.1378082-1-dylanbhatch@google.com>
With SFRAME_UNWINDER, read in the .sframe section at boot. This provides
unwind data as an alternative/supplement to frame pointer-based
unwinding.
Signed-off-by: Dylan Hatch <dylanbhatch@google.com>
---
arch/arm64/kernel/setup.c | 2 ++
include/linux/sframe.h | 14 ++++++++++++++
kernel/unwind/sframe.c | 39 +++++++++++++++++++++++++++++++++++++++
3 files changed, 55 insertions(+)
diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c
index 23c05dc7a8f2..4a633bc7aefb 100644
--- a/arch/arm64/kernel/setup.c
+++ b/arch/arm64/kernel/setup.c
@@ -32,6 +32,7 @@
#include <linux/sched/task.h>
#include <linux/scs.h>
#include <linux/mm.h>
+#include <linux/sframe.h>
#include <asm/acpi.h>
#include <asm/fixmap.h>
@@ -375,6 +376,7 @@ void __init __no_sanitize_address setup_arch(char **cmdline_p)
"This indicates a broken bootloader or old kernel\n",
boot_args[1], boot_args[2], boot_args[3]);
}
+ init_sframe_table();
}
static inline bool cpu_can_disable(unsigned int cpu)
diff --git a/include/linux/sframe.h b/include/linux/sframe.h
index 673b9edfc921..905775c3fde2 100644
--- a/include/linux/sframe.h
+++ b/include/linux/sframe.h
@@ -109,4 +109,18 @@ static inline int sframe_find_user(unsigned long ip, struct unwind_frame *frame)
#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */
+#ifdef CONFIG_SFRAME_UNWINDER
+
+void __init init_sframe_table(void);
+void sframe_module_init(struct module *mod, void *sframe, size_t sframe_size,
+ void *text, size_t text_size);
+
+extern int sframe_find_kernel(unsigned long ip, struct unwind_frame *frame);
+
+#else
+
+static inline void __init init_sframe_table(void) {}
+
+#endif /* CONFIG_SFRAME_UNWINDER */
+
#endif /* _LINUX_SFRAME_H */
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index cad4384dfb4f..321d0615aec7 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -13,10 +13,23 @@
#include <linux/string_helpers.h>
#include <linux/sframe.h>
#include <asm/unwind_sframe.h>
+#ifdef CONFIG_SFRAME_UNWINDER
+#include <linux/kallsyms.h>
+#endif
#include "sframe.h"
#include "sframe_debug.h"
+#ifdef CONFIG_SFRAME_UNWINDER
+
+extern char __start_sframe_header[];
+extern char __stop_sframe_header[];
+
+static bool sframe_init __ro_after_init;
+static struct sframe_section kernel_sfsec __ro_after_init;
+
+#endif /* CONFIG_SFRAME_UNWINDER */
+
struct sframe_fde_internal {
unsigned long func_addr;
u32 func_size;
@@ -930,3 +943,29 @@ void sframe_free_mm(struct mm_struct *mm)
}
#endif /* CONFIG_HAVE_UNWIND_USER_SFRAME */
+
+#ifdef CONFIG_SFRAME_UNWINDER
+
+int sframe_find_kernel(unsigned long ip, struct unwind_frame *frame)
+{
+ if (!frame || !sframe_init)
+ return -EINVAL;
+
+ return __sframe_find(&kernel_sfsec, ip, frame);
+}
+
+void __init init_sframe_table(void)
+{
+ kernel_sfsec.sec_type = SFRAME_KERNEL;
+ kernel_sfsec.sframe_start = (unsigned long)__start_sframe_header;
+ kernel_sfsec.sframe_end = (unsigned long)__stop_sframe_header;
+ kernel_sfsec.text_start = (unsigned long)_stext;
+ kernel_sfsec.text_end = (unsigned long)_etext;
+
+ if (WARN_ON(sframe_read_header(&kernel_sfsec)))
+ return;
+
+ sframe_init = true;
+}
+
+#endif /* CONFIG_SFRAME_UNWINDER */
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* [PATCH v3 5/8] sframe: Allow unsorted FDEs.
From: Dylan Hatch @ 2026-04-06 18:49 UTC (permalink / raw)
To: Roman Gushchin, Weinan Liu, Will Deacon, Josh Poimboeuf,
Indu Bhagat, Peter Zijlstra, Steven Rostedt, Catalin Marinas,
Jiri Kosina
Cc: Dylan Hatch, Mark Rutland, Prasanna Kumar T S M, Puranjay Mohan,
Song Liu, joe.lawrence, linux-toolchains, linux-kernel,
live-patching, Jens Remus, linux-arm-kernel
In-Reply-To: <20260406185000.1378082-1-dylanbhatch@google.com>
The .sframe in kernel modules is built without SFRAME_F_FDE_SORTED set.
In order to allow sframe PC lookup in modules, add a code path to handle
unsorted FDE tables by doing a simple linear search.
Signed-off-by: Dylan Hatch <dylanbhatch@google.com>
---
include/linux/sframe.h | 1 +
kernel/unwind/sframe.c | 44 +++++++++++++++++++++++++++++++++++++-----
2 files changed, 40 insertions(+), 5 deletions(-)
diff --git a/include/linux/sframe.h b/include/linux/sframe.h
index 905775c3fde2..593b60715cd6 100644
--- a/include/linux/sframe.h
+++ b/include/linux/sframe.h
@@ -64,6 +64,7 @@ struct sframe_section {
unsigned long text_start;
unsigned long text_end;
+ bool fdes_sorted;
unsigned long fdes_start;
unsigned long fres_start;
unsigned long fres_end;
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 321d0615aec7..4dd3612f9e7a 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -179,9 +179,34 @@ static __always_inline int __read_fde(struct sframe_section *sec,
return -EFAULT;
}
-static __always_inline int __find_fde(struct sframe_section *sec,
- unsigned long ip,
- struct sframe_fde_internal *fde)
+static __always_inline int __find_fde_unsorted(struct sframe_section *sec,
+ unsigned long ip,
+ struct sframe_fde_internal *fde)
+{
+ struct sframe_fde_v3 *cur, *start, *end;
+
+ start = (struct sframe_fde_v3 *)sec->fdes_start;
+ end = start + sec->num_fdes;
+
+ for (cur = start; cur < end; cur++) {
+ s64 func_off;
+ u32 func_size;
+ unsigned long func_addr;
+
+ DATA_GET(sec, func_off, &cur->func_start_off, s64, Efault);
+ DATA_GET(sec, func_size, &cur->func_size, u32, Efault);
+ func_addr = (unsigned long)cur + func_off;
+
+ if (ip >= func_addr && ip < func_addr + func_size)
+ return __read_fde(sec, cur - start, fde);
+ }
+Efault:
+ return -EFAULT;
+}
+
+static __always_inline int __find_fde_sorted(struct sframe_section *sec,
+ unsigned long ip,
+ struct sframe_fde_internal *fde)
{
unsigned long func_addr_low = 0, func_addr_high = ULONG_MAX;
struct sframe_fde_v3 *first, *low, *high, *found = NULL;
@@ -236,6 +261,15 @@ static __always_inline int __find_fde(struct sframe_section *sec,
return -EFAULT;
}
+static __always_inline int __find_fde(struct sframe_section *sec,
+ unsigned long ip,
+ struct sframe_fde_internal *fde)
+{
+ if (sec->fdes_sorted)
+ return __find_fde_sorted(sec, ip, fde);
+ return __find_fde_unsorted(sec, ip, fde);
+}
+
#define ____GET_INC(sec, to, from, type, label) \
({ \
type __to; \
@@ -660,7 +694,7 @@ static int sframe_validate_section(struct sframe_section *sec)
return ret;
ip = fde.func_addr;
- if (ip <= prev_ip) {
+ if (sec->fdes_sorted && ip <= prev_ip) {
dbg_sec("fde %u not sorted\n", i);
return -EFAULT;
}
@@ -739,7 +773,6 @@ static int sframe_read_header(struct sframe_section *sec)
if (shdr.preamble.magic != SFRAME_MAGIC ||
shdr.preamble.version != SFRAME_VERSION_3 ||
- !(shdr.preamble.flags & SFRAME_F_FDE_SORTED) ||
!(shdr.preamble.flags & SFRAME_F_FDE_FUNC_START_PCREL) ||
shdr.auxhdr_len) {
dbg_sec("bad/unsupported sframe header\n");
@@ -769,6 +802,7 @@ static int sframe_read_header(struct sframe_section *sec)
return -EINVAL;
}
+ sec->fdes_sorted = shdr.preamble.flags & SFRAME_F_FDE_SORTED;
sec->num_fdes = num_fdes;
sec->fdes_start = fdes_start;
sec->fres_start = fres_start;
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* [PATCH v3 6/8] arm64/module, sframe: Add sframe support for modules.
From: Dylan Hatch @ 2026-04-06 18:49 UTC (permalink / raw)
To: Roman Gushchin, Weinan Liu, Will Deacon, Josh Poimboeuf,
Indu Bhagat, Peter Zijlstra, Steven Rostedt, Catalin Marinas,
Jiri Kosina
Cc: Dylan Hatch, Mark Rutland, Prasanna Kumar T S M, Puranjay Mohan,
Song Liu, joe.lawrence, linux-toolchains, linux-kernel,
live-patching, Jens Remus, linux-arm-kernel
In-Reply-To: <20260406185000.1378082-1-dylanbhatch@google.com>
Add sframe table to mod_arch_specific and support sframe PC lookups when
an .sframe section can be found on incoming modules.
Signed-off-by: Dylan Hatch <dylanbhatch@google.com>
Signed-off-by: Weinan Liu <wnliu@google.com>
---
arch/arm64/include/asm/module.h | 6 +++++
arch/arm64/kernel/module.c | 8 +++++++
include/linux/sframe.h | 2 ++
kernel/unwind/sframe.c | 39 +++++++++++++++++++++++++++++++--
4 files changed, 53 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/module.h b/arch/arm64/include/asm/module.h
index fb9b88eebeb1..59fb6fba88d0 100644
--- a/arch/arm64/include/asm/module.h
+++ b/arch/arm64/include/asm/module.h
@@ -6,6 +6,7 @@
#define __ASM_MODULE_H
#include <asm-generic/module.h>
+#include <linux/sframe.h>
struct mod_plt_sec {
int plt_shndx;
@@ -17,6 +18,11 @@ struct mod_arch_specific {
struct mod_plt_sec core;
struct mod_plt_sec init;
+#ifdef CONFIG_SFRAME_UNWINDER
+ struct sframe_section sframe_sec;
+ bool sframe_init;
+#endif
+
/* for CONFIG_DYNAMIC_FTRACE */
struct plt_entry *ftrace_trampolines;
struct plt_entry *init_ftrace_trampolines;
diff --git a/arch/arm64/kernel/module.c b/arch/arm64/kernel/module.c
index 24adb581af0e..427f187e9531 100644
--- a/arch/arm64/kernel/module.c
+++ b/arch/arm64/kernel/module.c
@@ -18,6 +18,7 @@
#include <linux/moduleloader.h>
#include <linux/random.h>
#include <linux/scs.h>
+#include <linux/sframe.h>
#include <asm/alternative.h>
#include <asm/insn.h>
@@ -515,5 +516,12 @@ int module_finalize(const Elf_Ehdr *hdr,
}
}
+ s = find_section(hdr, sechdrs, ".sframe");
+ if (s) {
+ struct module_memory *t = &me->mem[MOD_TEXT];
+
+ sframe_module_init(me, (void *)s->sh_addr, s->sh_size,
+ t->base, t->size);
+ }
return module_init_ftrace_plt(hdr, sechdrs, me);
}
diff --git a/include/linux/sframe.h b/include/linux/sframe.h
index 593b60715cd6..06fdda1dd116 100644
--- a/include/linux/sframe.h
+++ b/include/linux/sframe.h
@@ -121,6 +121,8 @@ extern int sframe_find_kernel(unsigned long ip, struct unwind_frame *frame);
#else
static inline void __init init_sframe_table(void) {}
+static inline void sframe_module_init(struct module *mod, void *sframe, size_t sframe_size,
+ void *text, size_t text_size) {}
#endif /* CONFIG_SFRAME_UNWINDER */
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 4dd3612f9e7a..180f64040846 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -982,10 +982,27 @@ void sframe_free_mm(struct mm_struct *mm)
int sframe_find_kernel(unsigned long ip, struct unwind_frame *frame)
{
- if (!frame || !sframe_init)
+ struct sframe_section *sec;
+
+ if (!frame)
return -EINVAL;
- return __sframe_find(&kernel_sfsec, ip, frame);
+ if (is_ksym_addr(ip)) {
+ if (!sframe_init)
+ return -EINVAL;
+
+ sec = &kernel_sfsec;
+ } else {
+ struct module *mod;
+
+ mod = __module_address(ip);
+ if (!mod || !mod->arch.sframe_init)
+ return -EINVAL;
+
+ sec = &mod->arch.sframe_sec;
+ }
+
+ return __sframe_find(sec, ip, frame);
}
void __init init_sframe_table(void)
@@ -1002,4 +1019,22 @@ void __init init_sframe_table(void)
sframe_init = true;
}
+void sframe_module_init(struct module *mod, void *sframe, size_t sframe_size,
+ void *text, size_t text_size)
+{
+ struct sframe_section sec;
+
+ sec.sec_type = SFRAME_KERNEL;
+ sec.sframe_start = (unsigned long)sframe;
+ sec.sframe_end = (unsigned long)sframe + sframe_size;
+ sec.text_start = (unsigned long)text;
+ sec.text_end = (unsigned long)text + text_size;
+
+ if (WARN_ON(sframe_read_header(&sec)))
+ return;
+
+ mod->arch.sframe_sec = sec;
+ mod->arch.sframe_init = true;
+}
+
#endif /* CONFIG_SFRAME_UNWINDER */
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* [PATCH v3 7/8] sframe: Introduce in-kernel SFRAME_VALIDATION.
From: Dylan Hatch @ 2026-04-06 18:49 UTC (permalink / raw)
To: Roman Gushchin, Weinan Liu, Will Deacon, Josh Poimboeuf,
Indu Bhagat, Peter Zijlstra, Steven Rostedt, Catalin Marinas,
Jiri Kosina
Cc: Dylan Hatch, Mark Rutland, Prasanna Kumar T S M, Puranjay Mohan,
Song Liu, joe.lawrence, linux-toolchains, linux-kernel,
live-patching, Jens Remus, linux-arm-kernel
In-Reply-To: <20260406185000.1378082-1-dylanbhatch@google.com>
Generalize the __safe* helpers to support a non-user-access code path.
Allow for kernel FDE read failures due to the presence of .rodata.text.
This section contains code that can't be executed by the kernel
direclty, and thus lies ouside the normal kernel-text bounds.
Signed-off-by: Dylan Hatch <dylanbhatch@google.com>
---
arch/Kconfig | 2 +-
kernel/unwind/sframe.c | 20 ++++++++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/arch/Kconfig b/arch/Kconfig
index c87e489fa978..6e9f21231b98 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -503,7 +503,7 @@ config HAVE_UNWIND_USER_SFRAME
config SFRAME_VALIDATION
bool "Enable .sframe section debugging"
- depends on HAVE_UNWIND_USER_SFRAME
+ depends on SFRAME_LOOKUP
depends on DYNAMIC_DEBUG
help
When adding an .sframe section for a task, validate the entire
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 180f64040846..7096e0a244b4 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -638,6 +638,9 @@ static int safe_read_fde(struct sframe_section *sec,
{
int ret;
+ if (sec->sec_type == SFRAME_KERNEL)
+ return __read_fde(sec, fde_num, fde);
+
if (!user_read_access_begin((void __user *)sec->sframe_start,
sec->sframe_end - sec->sframe_start))
return -EFAULT;
@@ -653,6 +656,9 @@ static int safe_read_fre(struct sframe_section *sec,
{
int ret;
+ if (sec->sec_type == SFRAME_KERNEL)
+ return __read_fre(sec, fde, fre_addr, fre);
+
if (!user_read_access_begin((void __user *)sec->sframe_start,
sec->sframe_end - sec->sframe_start))
return -EFAULT;
@@ -667,6 +673,9 @@ static int safe_read_fre_datawords(struct sframe_section *sec,
{
int ret;
+ if (sec->sec_type == SFRAME_KERNEL)
+ return __read_fre_datawords(sec, fde, fre);
+
if (!user_read_access_begin((void __user *)sec->sframe_start,
sec->sframe_end - sec->sframe_start))
return -EFAULT;
@@ -690,6 +699,13 @@ static int sframe_validate_section(struct sframe_section *sec)
int ret;
ret = safe_read_fde(sec, i, &fde);
+ /*
+ * Code in .rodata.text is not considered part of normal kernel
+ * text, but there is no easy way to prevent sframe data from
+ * being generated for it.
+ */
+ if (ret && sec->sec_type == SFRAME_KERNEL)
+ continue;
if (ret)
return ret;
@@ -1015,6 +1031,8 @@ void __init init_sframe_table(void)
if (WARN_ON(sframe_read_header(&kernel_sfsec)))
return;
+ if (WARN_ON(sframe_validate_section(&kernel_sfsec)))
+ return;
sframe_init = true;
}
@@ -1032,6 +1050,8 @@ void sframe_module_init(struct module *mod, void *sframe, size_t sframe_size,
if (WARN_ON(sframe_read_header(&sec)))
return;
+ if (WARN_ON(sframe_validate_section(&sec)))
+ return;
mod->arch.sframe_sec = sec;
mod->arch.sframe_init = true;
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* [PATCH v3 8/8] unwind: arm64: Use sframe to unwind interrupt frames.
From: Dylan Hatch @ 2026-04-06 18:50 UTC (permalink / raw)
To: Roman Gushchin, Weinan Liu, Will Deacon, Josh Poimboeuf,
Indu Bhagat, Peter Zijlstra, Steven Rostedt, Catalin Marinas,
Jiri Kosina
Cc: Dylan Hatch, Mark Rutland, Prasanna Kumar T S M, Puranjay Mohan,
Song Liu, joe.lawrence, linux-toolchains, linux-kernel,
live-patching, Jens Remus, linux-arm-kernel
In-Reply-To: <20260406185000.1378082-1-dylanbhatch@google.com>
Add unwind_next_frame_sframe() function to unwind by sframe info if
present. Use this method at exception boundaries, falling back to
frame-pointer unwind only on failure. In such failure cases, the
stacktrace is considered unreliable.
During normal unwind, prefer frame pointer unwind (for better
performance) with sframe as a backup.
This change restores the LR behavior originally introduced in commit
c2c6b27b5aa14fa2 ("arm64: stacktrace: unwind exception boundaries"),
But later removed in commit 32ed1205682e ("arm64: stacktrace: Skip
reporting LR at exception boundaries")
This can be done because the sframe data can be used to determine
whether the LR is current for the PC value recovered from pt_regs at the
exception boundary.
Signed-off-by: Weinan Liu <wnliu@google.com>
Signed-off-by: Dylan Hatch <dylanbhatch@google.com>
Reviewed-by: Prasanna Kumar T S M <ptsm@linux.microsoft.com>
---
arch/arm64/include/asm/stacktrace/common.h | 6 +
arch/arm64/kernel/stacktrace.c | 242 +++++++++++++++++++--
2 files changed, 228 insertions(+), 20 deletions(-)
diff --git a/arch/arm64/include/asm/stacktrace/common.h b/arch/arm64/include/asm/stacktrace/common.h
index 821a8fdd31af..96c4c0a7e6de 100644
--- a/arch/arm64/include/asm/stacktrace/common.h
+++ b/arch/arm64/include/asm/stacktrace/common.h
@@ -21,6 +21,8 @@ struct stack_info {
*
* @fp: The fp value in the frame record (or the real fp)
* @pc: The lr value in the frame record (or the real lr)
+ * @sp: The sp value at the call site of the current function.
+ * @unreliable: Stacktrace is unreliable.
*
* @stack: The stack currently being unwound.
* @stacks: An array of stacks which can be unwound.
@@ -29,7 +31,11 @@ struct stack_info {
struct unwind_state {
unsigned long fp;
unsigned long pc;
+#ifdef CONFIG_SFRAME_UNWINDER
+ unsigned long sp;
+#endif
+ bool unreliable;
struct stack_info stack;
struct stack_info *stacks;
int nr_stacks;
diff --git a/arch/arm64/kernel/stacktrace.c b/arch/arm64/kernel/stacktrace.c
index 3ebcf8c53fb0..16a4eb31c5c1 100644
--- a/arch/arm64/kernel/stacktrace.c
+++ b/arch/arm64/kernel/stacktrace.c
@@ -14,6 +14,7 @@
#include <linux/sched/debug.h>
#include <linux/sched/task_stack.h>
#include <linux/stacktrace.h>
+#include <linux/sframe.h>
#include <asm/efi.h>
#include <asm/irq.h>
@@ -26,6 +27,7 @@ enum kunwind_source {
KUNWIND_SOURCE_CALLER,
KUNWIND_SOURCE_TASK,
KUNWIND_SOURCE_REGS_PC,
+ KUNWIND_SOURCE_REGS_LR,
};
union unwind_flags {
@@ -85,6 +87,9 @@ kunwind_init_from_regs(struct kunwind_state *state,
state->regs = regs;
state->common.fp = regs->regs[29];
state->common.pc = regs->pc;
+#ifdef CONFIG_SFRAME_UNWINDER
+ state->common.sp = regs->sp;
+#endif
state->source = KUNWIND_SOURCE_REGS_PC;
}
@@ -103,6 +108,9 @@ kunwind_init_from_caller(struct kunwind_state *state)
state->common.fp = (unsigned long)__builtin_frame_address(1);
state->common.pc = (unsigned long)__builtin_return_address(0);
+#ifdef CONFIG_SFRAME_UNWINDER
+ state->common.sp = (unsigned long)__builtin_frame_address(0);
+#endif
state->source = KUNWIND_SOURCE_CALLER;
}
@@ -124,6 +132,9 @@ kunwind_init_from_task(struct kunwind_state *state,
state->common.fp = thread_saved_fp(task);
state->common.pc = thread_saved_pc(task);
+#ifdef CONFIG_SFRAME_UNWINDER
+ state->common.sp = thread_saved_sp(task);
+#endif
state->source = KUNWIND_SOURCE_TASK;
}
@@ -181,7 +192,6 @@ int kunwind_next_regs_pc(struct kunwind_state *state)
state->regs = regs;
state->common.pc = regs->pc;
state->common.fp = regs->regs[29];
- state->regs = NULL;
state->source = KUNWIND_SOURCE_REGS_PC;
return 0;
}
@@ -237,6 +247,9 @@ kunwind_next_frame_record(struct kunwind_state *state)
unwind_consume_stack(&state->common, info, fp, sizeof(*record));
+#ifdef CONFIG_SFRAME_UNWINDER
+ state->common.sp = state->common.fp;
+#endif
state->common.fp = new_fp;
state->common.pc = new_pc;
state->source = KUNWIND_SOURCE_FRAME;
@@ -244,6 +257,172 @@ kunwind_next_frame_record(struct kunwind_state *state)
return 0;
}
+#ifdef CONFIG_SFRAME_UNWINDER
+
+static __always_inline struct stack_info *
+get_word(struct unwind_state *state, unsigned long *word)
+{
+ unsigned long addr = *word;
+ struct stack_info *info;
+
+ info = unwind_find_stack(state, addr, sizeof(addr));
+ if (!info)
+ return info;
+
+ *word = READ_ONCE(*(unsigned long *)addr);
+
+ return info;
+}
+
+static __always_inline int
+get_consume_word(struct unwind_state *state, unsigned long *word)
+{
+ struct stack_info *info;
+ unsigned long addr = *word;
+
+ info = get_word(state, word);
+ if (!info)
+ return -EINVAL;
+
+ unwind_consume_stack(state, info, addr, sizeof(addr));
+ return 0;
+}
+
+/*
+ * Unwind to the next frame according to sframe.
+ */
+static __always_inline int
+unwind_next_frame_sframe(struct kunwind_state *state)
+{
+ struct unwind_frame frame;
+ unsigned long cfa, fp, ra;
+ enum kunwind_source source = KUNWIND_SOURCE_FRAME;
+ struct pt_regs *regs = state->regs;
+
+ int err;
+
+ /* FP/SP alignment 8 bytes */
+ if (state->common.fp & 0x7 || state->common.sp & 0x7)
+ return -EINVAL;
+
+ /*
+ * Most/all outermost functions are not visible to sframe. So, check for
+ * a meta frame record if the sframe lookup fails.
+ */
+ err = sframe_find_kernel(state->common.pc, &frame);
+ if (err)
+ return kunwind_next_frame_record_meta(state);
+
+ if (frame.outermost)
+ return -ENOENT;
+
+ /* Get the Canonical Frame Address (CFA) */
+ switch (frame.cfa.rule) {
+ case UNWIND_CFA_RULE_SP_OFFSET:
+ cfa = state->common.sp;
+ break;
+ case UNWIND_CFA_RULE_FP_OFFSET:
+ if (state->common.fp < state->common.sp)
+ return -EINVAL;
+ cfa = state->common.fp;
+ break;
+ case UNWIND_CFA_RULE_REG_OFFSET:
+ case UNWIND_CFA_RULE_REG_OFFSET_DEREF:
+ if (!regs)
+ return -EINVAL;
+ cfa = regs->regs[frame.cfa.regnum];
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+ cfa += frame.cfa.offset;
+
+ /*
+ * CFA typically points to a higher address than RA or FP, so don't
+ * consume from the stack when we read it.
+ */
+ if (frame.cfa.rule & UNWIND_RULE_DEREF &&
+ !get_word(&state->common, &cfa))
+ return -EINVAL;
+
+ /* CFA alignment 8 bytes */
+ if (cfa & 0x7)
+ return -EINVAL;
+
+ /* Get the Return Address (RA) */
+ switch (frame.ra.rule) {
+ case UNWIND_RULE_RETAIN:
+ if (!regs)
+ return -EINVAL;
+ ra = regs->regs[30];
+ source = KUNWIND_SOURCE_REGS_LR;
+ break;
+ /* UNWIND_USER_RULE_CFA_OFFSET not implemented on purpose */
+ case UNWIND_RULE_CFA_OFFSET_DEREF:
+ ra = cfa + frame.ra.offset;
+ break;
+ case UNWIND_RULE_REG_OFFSET:
+ case UNWIND_RULE_REG_OFFSET_DEREF:
+ if (!regs)
+ return -EINVAL;
+ ra = regs->regs[frame.cfa.regnum];
+ ra += frame.ra.offset;
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
+ /* Get the Frame Pointer (FP) */
+ switch (frame.fp.rule) {
+ case UNWIND_RULE_RETAIN:
+ fp = state->common.fp;
+ break;
+ /* UNWIND_USER_RULE_CFA_OFFSET not implemented on purpose */
+ case UNWIND_RULE_CFA_OFFSET_DEREF:
+ fp = cfa + frame.fp.offset;
+ break;
+ case UNWIND_RULE_REG_OFFSET:
+ case UNWIND_RULE_REG_OFFSET_DEREF:
+ if (!regs)
+ return -EINVAL;
+ fp = regs->regs[frame.fp.regnum];
+ fp += frame.fp.offset;
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
+ /*
+ * Consume RA and FP from the stack. The frame record puts FP at a lower
+ * address than RA, so we always read FP first.
+ */
+ if (frame.fp.rule & UNWIND_RULE_DEREF &&
+ !get_word(&state->common, &fp))
+ return -EINVAL;
+
+ if (frame.ra.rule & UNWIND_RULE_DEREF &&
+ get_consume_word(&state->common, &ra))
+ return -EINVAL;
+
+ state->common.pc = ra;
+ state->common.sp = cfa;
+ state->common.fp = fp;
+
+ state->source = source;
+
+ return 0;
+}
+
+#else
+
+static __always_inline int
+unwind_next_frame_sframe(struct kunwind_state *state) { return -EINVAL; }
+
+#endif
+
/*
* Unwind from one frame record (A) to the next frame record (B).
*
@@ -259,12 +438,25 @@ kunwind_next(struct kunwind_state *state)
state->flags.all = 0;
switch (state->source) {
+ case KUNWIND_SOURCE_REGS_PC:
+ err = unwind_next_frame_sframe(state);
+
+ if (err && err != -ENOENT) {
+ /* Fallback to FP based unwinder */
+ err = kunwind_next_frame_record(state);
+ state->common.unreliable = true;
+ }
+ state->regs = NULL;
+ break;
case KUNWIND_SOURCE_FRAME:
case KUNWIND_SOURCE_CALLER:
case KUNWIND_SOURCE_TASK:
- case KUNWIND_SOURCE_REGS_PC:
+ case KUNWIND_SOURCE_REGS_LR:
err = kunwind_next_frame_record(state);
+ if (err && err != -ENOENT)
+ err = unwind_next_frame_sframe(state);
break;
+
default:
err = -EINVAL;
}
@@ -350,6 +542,9 @@ kunwind_stack_walk(kunwind_consume_fn consume_state,
.common = {
.stacks = stacks,
.nr_stacks = ARRAY_SIZE(stacks),
+#ifdef CONFIG_SFRAME_UNWINDER
+ .sp = 0,
+#endif
},
};
@@ -390,34 +585,40 @@ noinline noinstr void arch_stack_walk(stack_trace_consume_fn consume_entry,
kunwind_stack_walk(arch_kunwind_consume_entry, &data, task, regs);
}
+struct kunwind_reliable_consume_entry_data {
+ stack_trace_consume_fn consume_entry;
+ void *cookie;
+ bool unreliable;
+};
+
static __always_inline bool
-arch_reliable_kunwind_consume_entry(const struct kunwind_state *state, void *cookie)
+arch_kunwind_reliable_consume_entry(const struct kunwind_state *state, void *cookie)
{
- /*
- * At an exception boundary we can reliably consume the saved PC. We do
- * not know whether the LR was live when the exception was taken, and
- * so we cannot perform the next unwind step reliably.
- *
- * All that matters is whether the *entire* unwind is reliable, so give
- * up as soon as we hit an exception boundary.
- */
- if (state->source == KUNWIND_SOURCE_REGS_PC)
- return false;
+ struct kunwind_reliable_consume_entry_data *data = cookie;
- return arch_kunwind_consume_entry(state, cookie);
+ if (state->common.unreliable) {
+ data->unreliable = true;
+ return false;
+ }
+ return data->consume_entry(data->cookie, state->common.pc);
}
-noinline noinstr int arch_stack_walk_reliable(stack_trace_consume_fn consume_entry,
- void *cookie,
- struct task_struct *task)
+noinline notrace int arch_stack_walk_reliable(
+ stack_trace_consume_fn consume_entry,
+ void *cookie, struct task_struct *task)
{
- struct kunwind_consume_entry_data data = {
+ struct kunwind_reliable_consume_entry_data data = {
.consume_entry = consume_entry,
.cookie = cookie,
+ .unreliable = false,
};
- return kunwind_stack_walk(arch_reliable_kunwind_consume_entry, &data,
- task, NULL);
+ kunwind_stack_walk(arch_kunwind_reliable_consume_entry, &data, task, NULL);
+
+ if (data.unreliable)
+ return -EINVAL;
+
+ return 0;
}
struct bpf_unwind_consume_entry_data {
@@ -452,6 +653,7 @@ static const char *state_source_string(const struct kunwind_state *state)
case KUNWIND_SOURCE_CALLER: return "C";
case KUNWIND_SOURCE_TASK: return "T";
case KUNWIND_SOURCE_REGS_PC: return "P";
+ case KUNWIND_SOURCE_REGS_LR: return "L";
default: return "U";
}
}
--
2.53.0.1213.gd9a14994de-goog
^ 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