From c3e1549811747e4b4ff7e4bba691980d9dab2d9e Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 15 Dec 2025 15:29:41 +0200 Subject: ASoC: SOF: Add support for on-demand DSP boot On system suspend / resume we always power up the DSP and boot the firmware, which is not strictly needed as right after the firmware booted up we power the DSP down again on suspend and we also power it down after resume after some inactivity. Out of caution, add a new platform descriptor flag to enable on-demand DSP boot since this might not work without changes to platform code on certain platforms. With the on-demand dsp boot enabled we will not boot the DSP and firmware up on system or rpm resume, just enable audio subsystem since audio IPs, like HDA and SoundWire might be needed (codecs suspend/resume operation). Only boot up the DSP during the first hw_params() call when the DSP is really going to be needed. In this way we can handle the audio related use cases: normal audio use (rpm suspend/resume) system suspend/resume without active audio system suspend/resume with active audio system suspend/resume without active audio, and audio start before the rpm suspend timeout Add module option to force the on-demand DSP boot to allow it to be disabled or enabled without kernel change for testing. Signed-off-by: Peter Ujfalusi Reviewed-by: Bard Liao Reviewed-by: Kai Vehmanen Reviewed-by: Liam Girdwood Link: https://patch.msgid.link/20251215132946.2155-4-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- include/sound/sof.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'include') diff --git a/include/sound/sof.h b/include/sound/sof.h index eddea82c7b5a..38d6c8cb5e83 100644 --- a/include/sound/sof.h +++ b/include/sound/sof.h @@ -159,6 +159,9 @@ struct sof_dev_desc { /* The platform supports DSPless mode */ bool dspless_mode_supported; + /* On demand DSP booting is possible on the platform */ + bool on_demand_dsp_boot; + /* defaults paths for firmware, library and topology files */ const char *default_fw_path[SOF_IPC_TYPE_COUNT]; const char *default_lib_path[SOF_IPC_TYPE_COUNT]; -- cgit v1.2.3 From a1bcb66209a745c9ca18deae9f1c207b009dee1c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 12 Dec 2025 19:16:20 +0100 Subject: ASoC: Fix acronym for Intel Gemini Lake While the used GML is consistent with the pattern for other Intel * Lake SoCs, the de facto use is GLK. Update the acronym and users accordingly. Note, a handful of the drivers for Gemini Lake in the Linux kernel use GLK already (LPC, MEI, pin control, SDHCI, ...) and even some in ASoC. The only ones in this patch used the inconsistent one. Acked-by: Bjorn Helgaas # pci_ids.h Signed-off-by: Andy Shevchenko Reviewed-by: Peter Ujfalusi Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20251212181742.3944789-1-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- include/linux/pci_ids.h | 3 ++- sound/hda/controllers/intel.c | 2 +- sound/hda/core/intel-dsp-config.c | 4 ++-- sound/soc/intel/avs/board_selection.c | 2 +- sound/soc/intel/avs/core.c | 2 +- sound/soc/sof/intel/pci-apl.c | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index a9a089566b7c..84b830036fb4 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2950,7 +2950,8 @@ #define PCI_DEVICE_ID_INTEL_LYNNFIELD_MC_CH2_ADDR_REV2 0x2db1 #define PCI_DEVICE_ID_INTEL_LYNNFIELD_MC_CH2_RANK_REV2 0x2db2 #define PCI_DEVICE_ID_INTEL_LYNNFIELD_MC_CH2_TC_REV2 0x2db3 -#define PCI_DEVICE_ID_INTEL_HDA_GML 0x3198 +/* In a few of the Intel documents the GML acronym is used for Gemini Lake */ +#define PCI_DEVICE_ID_INTEL_HDA_GLK 0x3198 #define PCI_DEVICE_ID_INTEL_82855PM_HB 0x3340 #define PCI_DEVICE_ID_INTEL_IOAT_TBG4 0x3429 #define PCI_DEVICE_ID_INTEL_IOAT_TBG5 0x342a diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 1e8e3d61291a..bb9a64d41580 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -2555,7 +2555,7 @@ static const struct pci_device_id azx_ids[] = { /* Apollolake (Broxton-P) */ { PCI_DEVICE_DATA(INTEL, HDA_APL, AZX_DRIVER_SKL | AZX_DCAPS_INTEL_BROXTON) }, /* Gemini-Lake */ - { PCI_DEVICE_DATA(INTEL, HDA_GML, AZX_DRIVER_SKL | AZX_DCAPS_INTEL_BROXTON) }, + { PCI_DEVICE_DATA(INTEL, HDA_GLK, AZX_DRIVER_SKL | AZX_DCAPS_INTEL_BROXTON) }, /* Haswell */ { PCI_DEVICE_DATA(INTEL, HDA_HSW_0, AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_HASWELL) }, { PCI_DEVICE_DATA(INTEL, HDA_HSW_2, AZX_DRIVER_HDMI | AZX_DCAPS_INTEL_HASWELL) }, diff --git a/sound/hda/core/intel-dsp-config.c b/sound/hda/core/intel-dsp-config.c index 0c25e87408de..ddb8db3e8e39 100644 --- a/sound/hda/core/intel-dsp-config.c +++ b/sound/hda/core/intel-dsp-config.c @@ -154,7 +154,7 @@ static const struct config_entry config_table[] = { #if IS_ENABLED(CONFIG_SND_SOC_SOF_GEMINILAKE) { .flags = FLAG_SOF, - .device = PCI_DEVICE_ID_INTEL_HDA_GML, + .device = PCI_DEVICE_ID_INTEL_HDA_GLK, .dmi_table = (const struct dmi_system_id []) { { .ident = "Google Chromebooks", @@ -167,7 +167,7 @@ static const struct config_entry config_table[] = { }, { .flags = FLAG_SOF, - .device = PCI_DEVICE_ID_INTEL_HDA_GML, + .device = PCI_DEVICE_ID_INTEL_HDA_GLK, .codec_hid = &essx_83x6, }, #endif diff --git a/sound/soc/intel/avs/board_selection.c b/sound/soc/intel/avs/board_selection.c index 52e6266a7cb8..8a46285181fa 100644 --- a/sound/soc/intel/avs/board_selection.c +++ b/sound/soc/intel/avs/board_selection.c @@ -367,7 +367,7 @@ static const struct avs_acpi_boards i2s_boards[] = { AVS_MACH_ENTRY(HDA_SKL_LP, avs_skl_i2s_machines), AVS_MACH_ENTRY(HDA_KBL_LP, avs_kbl_i2s_machines), AVS_MACH_ENTRY(HDA_APL, avs_apl_i2s_machines), - AVS_MACH_ENTRY(HDA_GML, avs_gml_i2s_machines), + AVS_MACH_ENTRY(HDA_GLK, avs_gml_i2s_machines), AVS_MACH_ENTRY(HDA_CNL_LP, avs_cnl_i2s_machines), AVS_MACH_ENTRY(HDA_CNL_H, avs_cnl_i2s_machines), AVS_MACH_ENTRY(HDA_CML_LP, avs_cnl_i2s_machines), diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c index 6e0e65584c7f..1a53856c2ffb 100644 --- a/sound/soc/intel/avs/core.c +++ b/sound/soc/intel/avs/core.c @@ -897,7 +897,7 @@ static const struct pci_device_id avs_ids[] = { { PCI_DEVICE_DATA(INTEL, HDA_KBL_H, &skl_desc) }, { PCI_DEVICE_DATA(INTEL, HDA_CML_S, &skl_desc) }, { PCI_DEVICE_DATA(INTEL, HDA_APL, &apl_desc) }, - { PCI_DEVICE_DATA(INTEL, HDA_GML, &apl_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_GLK, &apl_desc) }, { PCI_DEVICE_DATA(INTEL, HDA_CNL_LP, &cnl_desc) }, { PCI_DEVICE_DATA(INTEL, HDA_CNL_H, &cnl_desc) }, { PCI_DEVICE_DATA(INTEL, HDA_CML_LP, &cnl_desc) }, diff --git a/sound/soc/sof/intel/pci-apl.c b/sound/soc/sof/intel/pci-apl.c index 0bf7ee753bc3..3241403efa60 100644 --- a/sound/soc/sof/intel/pci-apl.c +++ b/sound/soc/sof/intel/pci-apl.c @@ -86,7 +86,7 @@ static const struct sof_dev_desc glk_desc = { /* PCI IDs */ static const struct pci_device_id sof_pci_ids[] = { { PCI_DEVICE_DATA(INTEL, HDA_APL, &bxt_desc) }, - { PCI_DEVICE_DATA(INTEL, HDA_GML, &glk_desc) }, + { PCI_DEVICE_DATA(INTEL, HDA_GLK, &glk_desc) }, { 0, } }; MODULE_DEVICE_TABLE(pci, sof_pci_ids); -- cgit v1.2.3 From 45e1be5ddec98db71e7481fa7a3005673200d85c Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 2 Dec 2025 18:36:20 +0100 Subject: dt-bindings: power: qcom,rpmpd: Add SC8280XP_MXC_AO Not sure how useful it's gonna be in practice, but the definition is missing (unlike the previously-unused SC8280XP_MXC-non-_AO), so add it to allow the driver to create the corresponding pmdomain. Fixes: dbfb5f94e084 ("dt-bindings: power: rpmpd: Add sc8280xp RPMh power-domains") Acked-by: Rob Herring (Arm) Signed-off-by: Konrad Dybcio Reviewed-by: Ulf Hansson Link: https://lore.kernel.org/r/20251202-topic-8280_mxc-v2-1-46cdf47a829e@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- include/dt-bindings/power/qcom,rpmhpd.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/dt-bindings/power/qcom,rpmhpd.h b/include/dt-bindings/power/qcom,rpmhpd.h index 50e7c886709d..06851363ae0e 100644 --- a/include/dt-bindings/power/qcom,rpmhpd.h +++ b/include/dt-bindings/power/qcom,rpmhpd.h @@ -264,5 +264,6 @@ #define SC8280XP_NSP 13 #define SC8280XP_QPHY 14 #define SC8280XP_XO 15 +#define SC8280XP_MXC_AO 16 #endif -- cgit v1.2.3 From 331786db1b464fae42c36f53d6901d1d54975e04 Mon Sep 17 00:00:00 2001 From: David Lin Date: Wed, 17 Dec 2025 19:04:31 +0800 Subject: ASoC: Intel: ti-common: support tas2563 amplifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement tas2563 support code in this common module so it could be shared between multiple SOF machine drivers. Signed-off-by: David Lin Reviewed-by: Péter Ujfalusi Signed-off-by: Bard Liao Link: https://patch.msgid.link/20251217110433.3558136-2-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- include/sound/soc-acpi-intel-ssp-common.h | 4 ++ sound/soc/intel/boards/Kconfig | 3 + sound/soc/intel/boards/Makefile | 3 + sound/soc/intel/boards/sof_ti_common.c | 76 ++++++++++++++++++++++ sound/soc/intel/boards/sof_ti_common.h | 24 +++++++ sound/soc/intel/common/soc-acpi-intel-ssp-common.c | 3 + 6 files changed, 113 insertions(+) create mode 100644 sound/soc/intel/boards/sof_ti_common.c create mode 100644 sound/soc/intel/boards/sof_ti_common.h (limited to 'include') diff --git a/include/sound/soc-acpi-intel-ssp-common.h b/include/sound/soc-acpi-intel-ssp-common.h index b4597c8dac78..fdb2fce42115 100644 --- a/include/sound/soc-acpi-intel-ssp-common.h +++ b/include/sound/soc-acpi-intel-ssp-common.h @@ -37,6 +37,9 @@ #define RT5682_ACPI_HID "10EC5682" #define RT5682S_ACPI_HID "RTL5682" +/* Texas Instruments */ +#define TAS2563_ACPI_HID "TXNW2563" + enum snd_soc_acpi_intel_codec { CODEC_NONE, @@ -63,6 +66,7 @@ enum snd_soc_acpi_intel_codec { CODEC_RT1015P, CODEC_RT1019P, CODEC_RT1308, + CODEC_TAS2563, }; enum snd_soc_acpi_intel_codec diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index c23fdb6aad4c..724064149906 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -41,6 +41,9 @@ config SND_SOC_INTEL_SOF_CIRRUS_COMMON config SND_SOC_INTEL_SOF_NUVOTON_COMMON tristate +config SND_SOC_INTEL_SOF_TI_COMMON + tristate + config SND_SOC_INTEL_SOF_BOARD_HELPERS select SND_SOC_ACPI_INTEL_MATCH tristate diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index fcd517d6c279..25a1a9066cbf 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -69,5 +69,8 @@ obj-$(CONFIG_SND_SOC_INTEL_SOF_CIRRUS_COMMON) += snd-soc-intel-sof-cirrus-common snd-soc-intel-sof-nuvoton-common-y += sof_nuvoton_common.o obj-$(CONFIG_SND_SOC_INTEL_SOF_NUVOTON_COMMON) += snd-soc-intel-sof-nuvoton-common.o +snd-soc-intel-sof-ti-common-y += sof_ti_common.o +obj-$(CONFIG_SND_SOC_INTEL_SOF_TI_COMMON) += snd-soc-intel-sof-ti-common.o + snd-soc-intel-sof-board-helpers-y += sof_board_helpers.o obj-$(CONFIG_SND_SOC_INTEL_SOF_BOARD_HELPERS) += snd-soc-intel-sof-board-helpers.o diff --git a/sound/soc/intel/boards/sof_ti_common.c b/sound/soc/intel/boards/sof_ti_common.c new file mode 100644 index 000000000000..218c3536723c --- /dev/null +++ b/sound/soc/intel/boards/sof_ti_common.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2025 Intel Corporation +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common/soc-intel-quirks.h" +#include "sof_ti_common.h" + +/* + * Texas Instruments TAS2563 just mount one device to manage multiple devices, + * so the kcontrols, widgets and routes just keep one item, respectively. + */ +static const struct snd_kcontrol_new tas2563_spk_kcontrols[] = { + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +static const struct snd_soc_dapm_widget tas2563_spk_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Spk", NULL), +}; + +static const struct snd_soc_dapm_route tas2563_spk_dapm_routes[] = { + { "Spk", NULL, "OUT" }, +}; + +static struct snd_soc_dai_link_component tas2563_dai_link_components[] = { + { + .name = TAS2563_DEV0_NAME, + .dai_name = TAS2563_CODEC_DAI, + }, +}; + +static int tas2563_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_soc_dapm_new_controls(&card->dapm, tas2563_spk_dapm_widgets, + ARRAY_SIZE(tas2563_spk_dapm_widgets)); + if (ret) { + dev_err(rtd->dev, "unable to add dapm widgets, ret %d\n", ret); + return ret; + } + + ret = snd_soc_add_card_controls(card, tas2563_spk_kcontrols, + ARRAY_SIZE(tas2563_spk_kcontrols)); + if (ret) { + dev_err(rtd->dev, "unable to add controls, ret %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, tas2563_spk_dapm_routes, + ARRAY_SIZE(tas2563_spk_dapm_routes)); + if (ret) + dev_err(rtd->dev, "unable to add dapm routes, ret %d\n", ret); + + return ret; +} + +void sof_tas2563_dai_link(struct snd_soc_dai_link *link) +{ + link->codecs = tas2563_dai_link_components; + link->num_codecs = ARRAY_SIZE(tas2563_dai_link_components); + link->init = tas2563_init; +} +EXPORT_SYMBOL_NS(sof_tas2563_dai_link, "SND_SOC_INTEL_SOF_TI_COMMON"); + +MODULE_DESCRIPTION("ASoC Intel SOF Texas Instruments helpers"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/boards/sof_ti_common.h b/sound/soc/intel/boards/sof_ti_common.h new file mode 100644 index 000000000000..de15845aff0c --- /dev/null +++ b/sound/soc/intel/boards/sof_ti_common.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2025 Intel Corporation. + */ + +/* + * This file defines data structures used in Machine Driver for Intel + * platforms with Texas Instruments Codecs. + */ +#ifndef __SOF_TI_COMMON_H +#define __SOF_TI_COMMON_H + +#include +#include + +/* + * Texas Instruments TAS2563 + */ +#define TAS2563_CODEC_DAI "tasdev_codec" +#define TAS2563_DEV0_NAME "i2c-" TAS2563_ACPI_HID ":00" + +void sof_tas2563_dai_link(struct snd_soc_dai_link *link); + +#endif /* __SOF_TI_COMMON_H */ diff --git a/sound/soc/intel/common/soc-acpi-intel-ssp-common.c b/sound/soc/intel/common/soc-acpi-intel-ssp-common.c index f56f4bfa5187..a12b11f2cd7a 100644 --- a/sound/soc/intel/common/soc-acpi-intel-ssp-common.c +++ b/sound/soc/intel/common/soc-acpi-intel-ssp-common.c @@ -65,6 +65,9 @@ static const struct codec_map amps[] = { CODEC_MAP_ENTRY("RT1019P", "rt1019", RT1019P_ACPI_HID, CODEC_RT1019P), CODEC_MAP_ENTRY("RT1308", "rt1308", RT1308_ACPI_HID, CODEC_RT1308), + /* Texas Instruments */ + CODEC_MAP_ENTRY("TAS2563", "tas2563", TAS2563_ACPI_HID, CODEC_TAS2563), + /* * Monolithic components * -- cgit v1.2.3 From 86af3c229245fe1e59f428fc6abe19127ce15f5f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 30 Nov 2025 10:40:23 +0100 Subject: ASoC: qcom: Constify APR callback response data APR bus driver calls each APR client callback with pointer to the APR response packet. The callbacks are not suppose to modify that response packet, so make it a pointer to const to document that expectation explicitly. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20251130-asoc-apr-const-v1-1-d0833f3ed423@oss.qualcomm.com Signed-off-by: Mark Brown --- include/linux/soc/qcom/apr.h | 2 +- sound/soc/qcom/qdsp6/q6adm.c | 4 ++-- sound/soc/qcom/qdsp6/q6afe.c | 4 ++-- sound/soc/qcom/qdsp6/q6asm.c | 8 ++++---- sound/soc/qcom/qdsp6/q6core.c | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) (limited to 'include') diff --git a/include/linux/soc/qcom/apr.h b/include/linux/soc/qcom/apr.h index a532d1e4b1f4..35f44cd868cb 100644 --- a/include/linux/soc/qcom/apr.h +++ b/include/linux/soc/qcom/apr.h @@ -155,7 +155,7 @@ struct apr_driver { int (*probe)(struct apr_device *sl); void (*remove)(struct apr_device *sl); int (*callback)(struct apr_device *a, - struct apr_resp_pkt *d); + const struct apr_resp_pkt *d); int (*gpr_callback)(struct gpr_resp_pkt *d, void *data, int op); struct device_driver driver; const struct apr_device_id *id_table; diff --git a/sound/soc/qcom/qdsp6/q6adm.c b/sound/soc/qcom/qdsp6/q6adm.c index 0b8d06ec8b26..608ca0e41539 100644 --- a/sound/soc/qcom/qdsp6/q6adm.c +++ b/sound/soc/qcom/qdsp6/q6adm.c @@ -186,11 +186,11 @@ static void q6adm_free_copp(struct kref *ref) kfree(c); } -static int q6adm_callback(struct apr_device *adev, struct apr_resp_pkt *data) +static int q6adm_callback(struct apr_device *adev, const struct apr_resp_pkt *data) { struct aprv2_ibasic_rsp_result_t *result = data->payload; int port_idx, copp_idx; - struct apr_hdr *hdr = &data->hdr; + const struct apr_hdr *hdr = &data->hdr; struct q6copp *copp; struct q6adm *adm = dev_get_drvdata(&adev->dev); diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c index 0b01fc9e13a7..a9f8b7d68a96 100644 --- a/sound/soc/qcom/qdsp6/q6afe.c +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -958,11 +958,11 @@ static struct q6afe_port *q6afe_find_port(struct q6afe *afe, int token) return ret; } -static int q6afe_callback(struct apr_device *adev, struct apr_resp_pkt *data) +static int q6afe_callback(struct apr_device *adev, const struct apr_resp_pkt *data) { struct q6afe *afe = dev_get_drvdata(&adev->dev); struct aprv2_ibasic_rsp_result_t *res; - struct apr_hdr *hdr = &data->hdr; + const struct apr_hdr *hdr = &data->hdr; struct q6afe_port *port; if (!data->payload_size) diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c index e7295b7b2461..df183b7a4019 100644 --- a/sound/soc/qcom/qdsp6/q6asm.c +++ b/sound/soc/qcom/qdsp6/q6asm.c @@ -599,12 +599,12 @@ int q6asm_get_hw_pointer(struct audio_client *ac, unsigned int dir) EXPORT_SYMBOL_GPL(q6asm_get_hw_pointer); static int32_t q6asm_stream_callback(struct apr_device *adev, - struct apr_resp_pkt *data, + const struct apr_resp_pkt *data, int session_id) { struct q6asm *q6asm = dev_get_drvdata(&adev->dev); struct aprv2_ibasic_rsp_result_t *result; - struct apr_hdr *hdr = &data->hdr; + const struct apr_hdr *hdr = &data->hdr; struct audio_port_data *port; struct audio_client *ac; uint32_t client_event = 0; @@ -744,13 +744,13 @@ done: } static int q6asm_srvc_callback(struct apr_device *adev, - struct apr_resp_pkt *data) + const struct apr_resp_pkt *data) { struct q6asm *q6asm = dev_get_drvdata(&adev->dev); struct aprv2_ibasic_rsp_result_t *result; struct audio_port_data *port; struct audio_client *ac = NULL; - struct apr_hdr *hdr = &data->hdr; + const struct apr_hdr *hdr = &data->hdr; struct q6asm *a; uint32_t sid = 0; uint32_t dir = 0; diff --git a/sound/soc/qcom/qdsp6/q6core.c b/sound/soc/qcom/qdsp6/q6core.c index 49cfb32cd209..51398199bff3 100644 --- a/sound/soc/qcom/qdsp6/q6core.c +++ b/sound/soc/qcom/qdsp6/q6core.c @@ -67,11 +67,11 @@ struct q6core { static struct q6core *g_core; -static int q6core_callback(struct apr_device *adev, struct apr_resp_pkt *data) +static int q6core_callback(struct apr_device *adev, const struct apr_resp_pkt *data) { struct q6core *core = dev_get_drvdata(&adev->dev); struct aprv2_ibasic_rsp_result_t *result; - struct apr_hdr *hdr = &data->hdr; + const struct apr_hdr *hdr = &data->hdr; result = data->payload; switch (hdr->opcode) { -- cgit v1.2.3 From c66cea195d76c7c396c4c565b967d3e2a709e762 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 30 Nov 2025 10:40:24 +0100 Subject: soc: qcom: apr: Use typedef for GPR callback member There is already a typedef for GPR callback used in 'struct pkt_router_svc', so use it also in 'struct apr_driver', because it is the same type - one is assigned to another in apr_device_probe(). Signed-off-by: Krzysztof Kozlowski Acked-by: Bjorn Andersson Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20251130-asoc-apr-const-v1-2-d0833f3ed423@oss.qualcomm.com Signed-off-by: Mark Brown --- include/linux/soc/qcom/apr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/soc/qcom/apr.h b/include/linux/soc/qcom/apr.h index 35f44cd868cb..b16530f319ad 100644 --- a/include/linux/soc/qcom/apr.h +++ b/include/linux/soc/qcom/apr.h @@ -156,7 +156,7 @@ struct apr_driver { void (*remove)(struct apr_device *sl); int (*callback)(struct apr_device *a, const struct apr_resp_pkt *d); - int (*gpr_callback)(struct gpr_resp_pkt *d, void *data, int op); + gpr_port_cb gpr_callback; struct device_driver driver; const struct apr_device_id *id_table; }; -- cgit v1.2.3 From f3a86870c5938fe82ce02c29235326d417010ffb Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 30 Nov 2025 10:40:25 +0100 Subject: ASoC: qcom: Constify GPR callback response data GPR bus driver calls each GPR client callback with pointer to the GPR response packet. The callbacks are not suppose to modify that response packet, so make it a pointer to const to document that expectation explicitly. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20251130-asoc-apr-const-v1-3-d0833f3ed423@oss.qualcomm.com Signed-off-by: Mark Brown --- include/linux/soc/qcom/apr.h | 2 +- sound/soc/qcom/qdsp6/q6apm.c | 8 ++++---- sound/soc/qcom/qdsp6/q6prm.c | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/linux/soc/qcom/apr.h b/include/linux/soc/qcom/apr.h index b16530f319ad..6e1b1202e818 100644 --- a/include/linux/soc/qcom/apr.h +++ b/include/linux/soc/qcom/apr.h @@ -122,7 +122,7 @@ struct gpr_ibasic_rsp_accepted_t { #define APR_SVC_MAJOR_VERSION(v) ((v >> 16) & 0xFF) #define APR_SVC_MINOR_VERSION(v) (v & 0xFF) -typedef int (*gpr_port_cb) (struct gpr_resp_pkt *d, void *priv, int op); +typedef int (*gpr_port_cb) (const struct gpr_resp_pkt *d, void *priv, int op); struct packet_router; struct pkt_router_svc { struct device *dev; diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 94cc6376a367..cec135c53b99 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -487,14 +487,14 @@ int q6apm_get_hw_pointer(struct q6apm_graph *graph, int dir) } EXPORT_SYMBOL_GPL(q6apm_get_hw_pointer); -static int graph_callback(struct gpr_resp_pkt *data, void *priv, int op) +static int graph_callback(const struct gpr_resp_pkt *data, void *priv, int op) { struct data_cmd_rsp_rd_sh_mem_ep_data_buffer_done_v2 *rd_done; struct data_cmd_rsp_wr_sh_mem_ep_data_buffer_done_v2 *done; struct apm_cmd_rsp_shared_mem_map_regions *rsp; struct gpr_ibasic_rsp_result_t *result; struct q6apm_graph *graph = priv; - struct gpr_hdr *hdr = &data->hdr; + const struct gpr_hdr *hdr = &data->hdr; struct device *dev = graph->dev; uint32_t client_event; phys_addr_t phys; @@ -761,13 +761,13 @@ struct audioreach_module *q6apm_find_module_by_mid(struct q6apm_graph *graph, ui } -static int apm_callback(struct gpr_resp_pkt *data, void *priv, int op) +static int apm_callback(const struct gpr_resp_pkt *data, void *priv, int op) { gpr_device_t *gdev = priv; struct q6apm *apm = dev_get_drvdata(&gdev->dev); struct device *dev = &gdev->dev; struct gpr_ibasic_rsp_result_t *result; - struct gpr_hdr *hdr = &data->hdr; + const struct gpr_hdr *hdr = &data->hdr; result = data->payload; diff --git a/sound/soc/qcom/qdsp6/q6prm.c b/sound/soc/qcom/qdsp6/q6prm.c index 0b8fad0bc832..eaec6d211cf8 100644 --- a/sound/soc/qcom/qdsp6/q6prm.c +++ b/sound/soc/qcom/qdsp6/q6prm.c @@ -175,12 +175,12 @@ int q6prm_set_lpass_clock(struct device *dev, int clk_id, int clk_attr, int clk_ } EXPORT_SYMBOL_GPL(q6prm_set_lpass_clock); -static int prm_callback(struct gpr_resp_pkt *data, void *priv, int op) +static int prm_callback(const struct gpr_resp_pkt *data, void *priv, int op) { gpr_device_t *gdev = priv; struct q6prm *prm = dev_get_drvdata(&gdev->dev); struct gpr_ibasic_rsp_result_t *result; - struct gpr_hdr *hdr = &data->hdr; + const struct gpr_hdr *hdr = &data->hdr; switch (hdr->opcode) { case PRM_CMD_RSP_REQUEST_HW_RSC: -- cgit v1.2.3 From 3addd63d1fba8d9013e00b06d9420e39271c0c4e Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 15 Dec 2025 15:36:47 +0000 Subject: ASoC: SDCA: Factor out jack handling into new c file The jack code is perhaps a bit large for being in the interrupt code directly. Improve the encapsulation by factoring out the jack handling code into a new c file, as is already done for HID and FDL. Whilst doing so also add a jack_state structure to hold the jack state for improved expandability in the future. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20251215153650.3913117-2-ckeepax@opensource.cirrus.com Reviewed-by: Bard Liao Signed-off-by: Mark Brown --- include/sound/sdca_jack.h | 27 ++++++++ sound/soc/sdca/Makefile | 2 +- sound/soc/sdca/sdca_interrupts.c | 83 ++--------------------- sound/soc/sdca/sdca_jack.c | 140 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+), 77 deletions(-) create mode 100644 include/sound/sdca_jack.h create mode 100644 sound/soc/sdca/sdca_jack.c (limited to 'include') diff --git a/include/sound/sdca_jack.h b/include/sound/sdca_jack.h new file mode 100644 index 000000000000..9fad5f22cbb9 --- /dev/null +++ b/include/sound/sdca_jack.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * The MIPI SDCA specification is available for public downloads at + * https://www.mipi.org/mipi-sdca-v1-0-download + * + * Copyright (C) 2025 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef __SDCA_JACK_H__ +#define __SDCA_JACK_H__ + +struct sdca_interrupt; +struct snd_kcontrol; + +/** + * struct jack_state - Jack state structure to keep data between interrupts + * @kctl: Pointer to the ALSA control attached to this jack + */ +struct jack_state { + struct snd_kcontrol *kctl; +}; + +int sdca_jack_alloc_state(struct sdca_interrupt *interrupt); +int sdca_jack_process(struct sdca_interrupt *interrupt); + +#endif // __SDCA_JACK_H__ diff --git a/sound/soc/sdca/Makefile b/sound/soc/sdca/Makefile index f6b73275d964..b3b0f5d94c8d 100644 --- a/sound/soc/sdca/Makefile +++ b/sound/soc/sdca/Makefile @@ -3,7 +3,7 @@ snd-soc-sdca-y := sdca_functions.o sdca_device.o sdca_function_device.o \ sdca_regmap.o sdca_asoc.o sdca_ump.o snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_HID) += sdca_hid.o -snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_IRQ) += sdca_interrupts.o +snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_IRQ) += sdca_interrupts.o sdca_jack.o snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_FDL) += sdca_fdl.o snd-soc-sdca-class-y := sdca_class.o diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index 8f6a2adfb6fb..ff3a7e405fdc 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -155,14 +156,7 @@ static irqreturn_t detected_mode_handler(int irq, void *data) { struct sdca_interrupt *interrupt = data; struct device *dev = interrupt->dev; - struct snd_soc_component *component = interrupt->component; - struct snd_soc_card *card = component->card; - struct rw_semaphore *rwsem = &card->snd_card->controls_rwsem; - struct snd_kcontrol *kctl = interrupt->priv; - struct snd_ctl_elem_value *ucontrol __free(kfree) = NULL; - struct soc_enum *soc_enum; irqreturn_t irqret = IRQ_NONE; - unsigned int reg, val; int ret; ret = pm_runtime_get_sync(dev); @@ -171,76 +165,9 @@ static irqreturn_t detected_mode_handler(int irq, void *data) goto error; } - if (!kctl) { - const char *name __free(kfree) = kasprintf(GFP_KERNEL, "%s %s", - interrupt->entity->label, - SDCA_CTL_SELECTED_MODE_NAME); - - if (!name) - goto error; - - kctl = snd_soc_component_get_kcontrol(component, name); - if (!kctl) { - dev_dbg(dev, "control not found: %s\n", name); - goto error; - } - - interrupt->priv = kctl; - } - - soc_enum = (struct soc_enum *)kctl->private_value; - - reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id, - interrupt->control->sel, 0); - - ret = regmap_read(interrupt->function_regmap, reg, &val); - if (ret < 0) { - dev_err(dev, "failed to read detected mode: %d\n", ret); - goto error; - } - - switch (val) { - case SDCA_DETECTED_MODE_DETECTION_IN_PROGRESS: - case SDCA_DETECTED_MODE_JACK_UNKNOWN: - reg = SDW_SDCA_CTL(interrupt->function->desc->adr, - interrupt->entity->id, - SDCA_CTL_GE_SELECTED_MODE, 0); - - /* - * Selected mode is not normally marked as volatile register - * (RW), but here force a read from the hardware. If the - * detected mode is unknown we need to see what the device - * selected as a "safe" option. - */ - regcache_drop_region(interrupt->function_regmap, reg, reg); - - ret = regmap_read(interrupt->function_regmap, reg, &val); - if (ret) { - dev_err(dev, "failed to re-check selected mode: %d\n", ret); - goto error; - } - break; - default: - break; - } - - dev_dbg(dev, "%s: %#x\n", interrupt->name, val); - - ucontrol = kzalloc(sizeof(*ucontrol), GFP_KERNEL); - if (!ucontrol) - goto error; - - ucontrol->value.enumerated.item[0] = snd_soc_enum_val_to_item(soc_enum, val); - - down_write(rwsem); - ret = kctl->put(kctl, ucontrol); - up_write(rwsem); - if (ret < 0) { - dev_err(dev, "failed to update selected mode: %d\n", ret); + ret = sdca_jack_process(interrupt); + if (ret) goto error; - } - - snd_ctl_notify(card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); irqret = IRQ_HANDLED; error: @@ -536,6 +463,10 @@ int sdca_irq_populate(struct sdca_function_data *function, handler = function_status_handler; break; case SDCA_CTL_TYPE_S(GE, DETECTED_MODE): + ret = sdca_jack_alloc_state(interrupt); + if (ret) + return ret; + handler = detected_mode_handler; break; case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER): diff --git a/sound/soc/sdca/sdca_jack.c b/sound/soc/sdca/sdca_jack.c new file mode 100644 index 000000000000..83b2b9cc81f0 --- /dev/null +++ b/sound/soc/sdca/sdca_jack.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2025 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. + +/* + * The MIPI SDCA specification is available for public downloads at + * https://www.mipi.org/mipi-sdca-v1-0-download + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * sdca_jack_process - Process an SDCA jack event + * @interrupt: SDCA interrupt structure + * + * Return: Zero on success or a negative error code. + */ +int sdca_jack_process(struct sdca_interrupt *interrupt) +{ + struct device *dev = interrupt->dev; + struct snd_soc_component *component = interrupt->component; + struct snd_soc_card *card = component->card; + struct rw_semaphore *rwsem = &card->snd_card->controls_rwsem; + struct jack_state *state = interrupt->priv; + struct snd_kcontrol *kctl = state->kctl; + struct snd_ctl_elem_value *ucontrol __free(kfree) = NULL; + struct soc_enum *soc_enum; + unsigned int reg, val; + int ret; + + if (!kctl) { + const char *name __free(kfree) = kasprintf(GFP_KERNEL, "%s %s", + interrupt->entity->label, + SDCA_CTL_SELECTED_MODE_NAME); + + if (!name) + return -ENOMEM; + + kctl = snd_soc_component_get_kcontrol(component, name); + if (!kctl) { + dev_dbg(dev, "control not found: %s\n", name); + return -ENOENT; + } + + state->kctl = kctl; + } + + soc_enum = (struct soc_enum *)kctl->private_value; + + reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id, + interrupt->control->sel, 0); + + ret = regmap_read(interrupt->function_regmap, reg, &val); + if (ret < 0) { + dev_err(dev, "failed to read detected mode: %d\n", ret); + return ret; + } + + switch (val) { + case SDCA_DETECTED_MODE_DETECTION_IN_PROGRESS: + case SDCA_DETECTED_MODE_JACK_UNKNOWN: + reg = SDW_SDCA_CTL(interrupt->function->desc->adr, + interrupt->entity->id, + SDCA_CTL_GE_SELECTED_MODE, 0); + + /* + * Selected mode is not normally marked as volatile register + * (RW), but here force a read from the hardware. If the + * detected mode is unknown we need to see what the device + * selected as a "safe" option. + */ + regcache_drop_region(interrupt->function_regmap, reg, reg); + + ret = regmap_read(interrupt->function_regmap, reg, &val); + if (ret) { + dev_err(dev, "failed to re-check selected mode: %d\n", ret); + return ret; + } + break; + default: + break; + } + + dev_dbg(dev, "%s: %#x\n", interrupt->name, val); + + ucontrol = kzalloc(sizeof(*ucontrol), GFP_KERNEL); + if (!ucontrol) + return -ENOMEM; + + ucontrol->value.enumerated.item[0] = snd_soc_enum_val_to_item(soc_enum, val); + + down_write(rwsem); + ret = kctl->put(kctl, ucontrol); + up_write(rwsem); + if (ret < 0) { + dev_err(dev, "failed to update selected mode: %d\n", ret); + return ret; + } + + snd_ctl_notify(card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sdca_jack_process, "SND_SOC_SDCA"); + +/** + * sdca_jack_alloc_state - allocate state for a jack interrupt + * @interrupt: SDCA interrupt structure. + * + * Return: Zero on success or a negative error code. + */ +int sdca_jack_alloc_state(struct sdca_interrupt *interrupt) +{ + struct device *dev = interrupt->dev; + struct jack_state *jack_state; + + jack_state = devm_kzalloc(dev, sizeof(*jack_state), GFP_KERNEL); + if (!jack_state) + return -ENOMEM; + + interrupt->priv = jack_state; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sdca_jack_alloc_state, "SND_SOC_SDCA"); -- cgit v1.2.3 From 82e12800f563baf663277ef0017f40a335b8e84c Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 15 Dec 2025 15:36:48 +0000 Subject: ASoC: SDCA: Add ability to connect SDCA jacks to ASoC jacks Add handling for the ASoC jack API to SDCA to allow user-space to be hooked up normally. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20251215153650.3913117-3-ckeepax@opensource.cirrus.com Reviewed-by: Bard Liao Signed-off-by: Mark Brown --- include/sound/sdca_jack.h | 5 +++ sound/soc/sdca/sdca_jack.c | 106 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/sound/sdca_jack.h b/include/sound/sdca_jack.h index 9fad5f22cbb9..3ec22046d3eb 100644 --- a/include/sound/sdca_jack.h +++ b/include/sound/sdca_jack.h @@ -12,16 +12,21 @@ struct sdca_interrupt; struct snd_kcontrol; +struct snd_soc_jack; /** * struct jack_state - Jack state structure to keep data between interrupts * @kctl: Pointer to the ALSA control attached to this jack + * @jack: Pointer to the ASoC jack struct for this jack */ struct jack_state { struct snd_kcontrol *kctl; + struct snd_soc_jack *jack; }; int sdca_jack_alloc_state(struct sdca_interrupt *interrupt); int sdca_jack_process(struct sdca_interrupt *interrupt); +int sdca_jack_set_jack(struct sdca_interrupt_info *info, struct snd_soc_jack *jack); +int sdca_jack_report(struct sdca_interrupt *interrupt); #endif // __SDCA_JACK_H__ diff --git a/sound/soc/sdca/sdca_jack.c b/sound/soc/sdca/sdca_jack.c index 83b2b9cc81f0..5b9cf69cbcd6 100644 --- a/sound/soc/sdca/sdca_jack.c +++ b/sound/soc/sdca/sdca_jack.c @@ -17,11 +17,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include /** @@ -114,7 +116,7 @@ int sdca_jack_process(struct sdca_interrupt *interrupt) snd_ctl_notify(card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); - return 0; + return sdca_jack_report(interrupt); } EXPORT_SYMBOL_NS_GPL(sdca_jack_process, "SND_SOC_SDCA"); @@ -138,3 +140,105 @@ int sdca_jack_alloc_state(struct sdca_interrupt *interrupt) return 0; } EXPORT_SYMBOL_NS_GPL(sdca_jack_alloc_state, "SND_SOC_SDCA"); + +/** + * sdca_jack_set_jack - attach an ASoC jack to SDCA + * @info: SDCA interrupt information. + * @jack: ASoC jack to be attached. + * + * Return: Zero on success or a negative error code. + */ +int sdca_jack_set_jack(struct sdca_interrupt_info *info, struct snd_soc_jack *jack) +{ + int i, ret; + + guard(mutex)(&info->irq_lock); + + for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) { + struct sdca_interrupt *interrupt = &info->irqs[i]; + struct sdca_control *control = interrupt->control; + struct sdca_entity *entity = interrupt->entity; + struct jack_state *jack_state; + + if (!interrupt->irq) + continue; + + switch (SDCA_CTL_TYPE(entity->type, control->sel)) { + case SDCA_CTL_TYPE_S(GE, DETECTED_MODE): + jack_state = interrupt->priv; + jack_state->jack = jack; + + /* Report initial state in case IRQ was already handled */ + ret = sdca_jack_report(interrupt); + if (ret) + return ret; + break; + default: + break; + } + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sdca_jack_set_jack, "SND_SOC_SDCA"); + +int sdca_jack_report(struct sdca_interrupt *interrupt) +{ + struct jack_state *jack_state = interrupt->priv; + struct sdca_control_range *range; + enum sdca_terminal_type type; + unsigned int report = 0; + unsigned int reg, val; + int ret; + + reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id, + SDCA_CTL_GE_SELECTED_MODE, 0); + + ret = regmap_read(interrupt->function_regmap, reg, &val); + if (ret) { + dev_err(interrupt->dev, "failed to read selected mode: %d\n", ret); + return ret; + } + + range = sdca_selector_find_range(interrupt->dev, interrupt->entity, + SDCA_CTL_GE_SELECTED_MODE, + SDCA_SELECTED_MODE_NCOLS, 0); + if (!range) + return -EINVAL; + + type = sdca_range_search(range, SDCA_SELECTED_MODE_INDEX, + val, SDCA_SELECTED_MODE_TERM_TYPE); + + switch (type) { + case SDCA_TERM_TYPE_LINEIN_STEREO: + case SDCA_TERM_TYPE_LINEIN_FRONT_LR: + case SDCA_TERM_TYPE_LINEIN_CENTER_LFE: + case SDCA_TERM_TYPE_LINEIN_SURROUND_LR: + case SDCA_TERM_TYPE_LINEIN_REAR_LR: + report = SND_JACK_LINEIN; + break; + case SDCA_TERM_TYPE_LINEOUT_STEREO: + case SDCA_TERM_TYPE_LINEOUT_FRONT_LR: + case SDCA_TERM_TYPE_LINEOUT_CENTER_LFE: + case SDCA_TERM_TYPE_LINEOUT_SURROUND_LR: + case SDCA_TERM_TYPE_LINEOUT_REAR_LR: + report = SND_JACK_LINEOUT; + break; + case SDCA_TERM_TYPE_MIC_JACK: + report = SND_JACK_MICROPHONE; + break; + case SDCA_TERM_TYPE_HEADPHONE_JACK: + report = SND_JACK_HEADPHONE; + break; + case SDCA_TERM_TYPE_HEADSET_JACK: + report = SND_JACK_HEADSET; + break; + default: + break; + } + + snd_soc_jack_report(jack_state->jack, report, 0xFFFF); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sdca_jack_report, "SND_SOC_SDCA"); -- cgit v1.2.3 From 9910159f06590c17df4fbddedaabb4c0201cc4cb Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Mon, 15 Dec 2025 14:17:23 +0100 Subject: iio: core: add separate lockdep class for info_exist_lock When one iio device is a consumer of another, it is possible that the ->info_exist_lock of both ends up being taken when reading the value of the consumer device. Since they currently belong to the same lockdep class (being initialized in a single location with mutex_init()), that results in a lockdep warning CPU0 ---- lock(&iio_dev_opaque->info_exist_lock); lock(&iio_dev_opaque->info_exist_lock); *** DEADLOCK *** May be due to missing lock nesting notation 4 locks held by sensors/414: #0: c31fd6dc (&p->lock){+.+.}-{3:3}, at: seq_read_iter+0x44/0x4e4 #1: c4f5a1c4 (&of->mutex){+.+.}-{3:3}, at: kernfs_seq_start+0x1c/0xac #2: c2827548 (kn->active#34){.+.+}-{0:0}, at: kernfs_seq_start+0x30/0xac #3: c1dd2b68 (&iio_dev_opaque->info_exist_lock){+.+.}-{3:3}, at: iio_read_channel_processed_scale+0x24/0xd8 stack backtrace: CPU: 0 UID: 0 PID: 414 Comm: sensors Not tainted 6.17.11 #5 NONE Hardware name: Generic AM33XX (Flattened Device Tree) Call trace: unwind_backtrace from show_stack+0x10/0x14 show_stack from dump_stack_lvl+0x44/0x60 dump_stack_lvl from print_deadlock_bug+0x2b8/0x334 print_deadlock_bug from __lock_acquire+0x13a4/0x2ab0 __lock_acquire from lock_acquire+0xd0/0x2c0 lock_acquire from __mutex_lock+0xa0/0xe8c __mutex_lock from mutex_lock_nested+0x1c/0x24 mutex_lock_nested from iio_read_channel_raw+0x20/0x6c iio_read_channel_raw from rescale_read_raw+0x128/0x1c4 rescale_read_raw from iio_channel_read+0xe4/0xf4 iio_channel_read from iio_read_channel_processed_scale+0x6c/0xd8 iio_read_channel_processed_scale from iio_hwmon_read_val+0x68/0xbc iio_hwmon_read_val from dev_attr_show+0x18/0x48 dev_attr_show from sysfs_kf_seq_show+0x80/0x110 sysfs_kf_seq_show from seq_read_iter+0xdc/0x4e4 seq_read_iter from vfs_read+0x238/0x2e4 vfs_read from ksys_read+0x6c/0xec ksys_read from ret_fast_syscall+0x0/0x1c Just as the mlock_key already has its own lockdep class, add a lock_class_key for the info_exist mutex. Note that this has in theory been a problem since before IIO first left staging, but it only occurs when a chain of consumers is in use and that is not often done. Fixes: ac917a81117c ("staging:iio:core set the iio_dev.info pointer to null on unregister under lock.") Signed-off-by: Rasmus Villemoes Reviewed-by: Peter Rosin Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 4 +++- include/linux/iio/iio-opaque.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index f69deefcfb6f..117ffad4f376 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1657,6 +1657,7 @@ static void iio_dev_release(struct device *device) mutex_destroy(&iio_dev_opaque->info_exist_lock); mutex_destroy(&iio_dev_opaque->mlock); + lockdep_unregister_key(&iio_dev_opaque->info_exist_key); lockdep_unregister_key(&iio_dev_opaque->mlock_key); ida_free(&iio_ida, iio_dev_opaque->id); @@ -1717,9 +1718,10 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) INIT_LIST_HEAD(&iio_dev_opaque->ioctl_handlers); lockdep_register_key(&iio_dev_opaque->mlock_key); + lockdep_register_key(&iio_dev_opaque->info_exist_key); mutex_init_with_key(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key); - mutex_init(&iio_dev_opaque->info_exist_lock); + mutex_init_with_key(&iio_dev_opaque->info_exist_lock, &iio_dev_opaque->info_exist_key); indio_dev->dev.parent = parent; indio_dev->dev.type = &iio_device_type; diff --git a/include/linux/iio/iio-opaque.h b/include/linux/iio/iio-opaque.h index 4247497f3f8b..b87841a355f8 100644 --- a/include/linux/iio/iio-opaque.h +++ b/include/linux/iio/iio-opaque.h @@ -14,6 +14,7 @@ * @mlock: lock used to prevent simultaneous device state changes * @mlock_key: lockdep class for iio_dev lock * @info_exist_lock: lock to prevent use during removal + * @info_exist_key: lockdep class for info_exist lock * @trig_readonly: mark the current trigger immutable * @event_interface: event chrdevs associated with interrupt lines * @attached_buffers: array of buffers statically attached by the driver @@ -47,6 +48,7 @@ struct iio_dev_opaque { struct mutex mlock; struct lock_class_key mlock_key; struct mutex info_exist_lock; + struct lock_class_key info_exist_key; bool trig_readonly; struct iio_event_interface *event_interface; struct iio_buffer **attached_buffers; -- cgit v1.2.3 From bc0305cb294c693b2762cf863324defb9e5175e5 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 31 Dec 2025 17:27:04 +0000 Subject: firmware: cs_dsp: Handle long-offset data blocks Handle a new type of data block that has a 32-bit offset. These are identical to the normal blocks except that the offset is now in the 32-bit field that was previously 'sr'. A new file version of 3 indicates that it is mandatory to process the long-offset blocks, so that older code without that support will reject the file. The original 'sr' field was never used by the driver so it has been renamed offset32. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20251231172711.450024-2-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- drivers/firmware/cirrus/cs_dsp.c | 19 +++++++++++++++---- include/linux/firmware/cirrus/wmfw.h | 7 ++++++- 2 files changed, 21 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index d35d0f5ccaf7..aa6e740f9cd7 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -2138,7 +2138,8 @@ static int cs_dsp_load_coeff(struct cs_dsp *dsp, const struct firmware *firmware const struct cs_dsp_region *mem; struct cs_dsp_alg_region *alg_region; const char *region_name; - int ret, pos, blocks, type, offset, reg, version; + int ret, pos, blocks, type, version; + unsigned int offset, reg; u8 *buf = NULL; size_t buf_len = 0; size_t region_len; @@ -2163,6 +2164,7 @@ static int cs_dsp_load_coeff(struct cs_dsp *dsp, const struct firmware *firmware switch (be32_to_cpu(hdr->rev) & 0xff) { case 1: case 2: + case 3: break; default: cs_dsp_err(dsp, "%s: Unsupported coefficient file format %d\n", @@ -2171,7 +2173,8 @@ static int cs_dsp_load_coeff(struct cs_dsp *dsp, const struct firmware *firmware goto out_fw; } - cs_dsp_info(dsp, "%s: v%d.%d.%d\n", file, + cs_dsp_info(dsp, "%s (v%d): v%d.%d.%d\n", file, + be32_to_cpu(hdr->rev) & 0xff, (le32_to_cpu(hdr->ver) >> 16) & 0xff, (le32_to_cpu(hdr->ver) >> 8) & 0xff, le32_to_cpu(hdr->ver) & 0xff); @@ -2202,8 +2205,9 @@ static int cs_dsp_load_coeff(struct cs_dsp *dsp, const struct firmware *firmware (le32_to_cpu(blk->ver) >> 16) & 0xff, (le32_to_cpu(blk->ver) >> 8) & 0xff, le32_to_cpu(blk->ver) & 0xff); - cs_dsp_dbg(dsp, "%s.%d: %d bytes at 0x%x in %x\n", - file, blocks, le32_to_cpu(blk->len), offset, type); + cs_dsp_dbg(dsp, "%s.%d: %d bytes off:%#x off32:%#x in %#x\n", + file, blocks, le32_to_cpu(blk->len), offset, + le32_to_cpu(blk->offset32), type); reg = 0; region_name = "Unknown"; @@ -2236,6 +2240,13 @@ static int cs_dsp_load_coeff(struct cs_dsp *dsp, const struct firmware *firmware } break; + case WMFW_ADSP2_XM_LONG: + case WMFW_ADSP2_YM_LONG: + case WMFW_HALO_XM_PACKED_LONG: + case WMFW_HALO_YM_PACKED_LONG: + offset = le32_to_cpu(blk->offset32); + type &= 0xff; /* strip extended block type flags */ + fallthrough; case WMFW_ADSP1_DM: case WMFW_ADSP1_ZM: case WMFW_ADSP2_XM: diff --git a/include/linux/firmware/cirrus/wmfw.h b/include/linux/firmware/cirrus/wmfw.h index 74e5a4f6c13a..eae24dde9e41 100644 --- a/include/linux/firmware/cirrus/wmfw.h +++ b/include/linux/firmware/cirrus/wmfw.h @@ -172,7 +172,7 @@ struct wmfw_coeff_item { __le16 type; __le32 id; __le32 ver; - __le32 sr; + __le32 offset32; __le32 len; u8 data[]; } __packed; @@ -200,4 +200,9 @@ struct wmfw_coeff_item { #define WMFW_HALO_XM_PACKED 0x11 #define WMFW_HALO_YM_PACKED 0x12 +#define WMFW_ADSP2_XM_LONG 0xf405 +#define WMFW_ADSP2_YM_LONG 0xf406 +#define WMFW_HALO_XM_PACKED_LONG 0xf411 +#define WMFW_HALO_YM_PACKED_LONG 0xf412 + #endif -- cgit v1.2.3 From 9e6f4c5b2d3af58390cf554ada9591935c5ac774 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 31 Dec 2025 17:27:07 +0000 Subject: firmware: cs_dsp: mock_bin: Pass offset32 to cs_dsp_mock_bin_add_raw_block() Add an argument to cs_dsp_mock_bin_add_raw_block() to pass a 32-bit offset, and change the type of the existing offset argument to u16. The cs_dsp_test_bin_error.c test uses cs_dsp_mock_bin_add_raw_block() so it needs corresponding updates to pass 0 as the 32-bit offset. Version 1 and 2 of the bin file format had a 16-bit offset on blocks and the sample rate field of the blocks was not used. Version 3 adds new block types that change the old sample rate field to be a 32-bit offset with the old offset currently unused. cs_dsp_mock_bin_add_raw_block() doesn't attempt to do any magic - its purpose is to create a raw block exactly as specified by the calling test code. So the test case can pass a value for both offset fields. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20251231172711.450024-5-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- drivers/firmware/cirrus/test/cs_dsp_mock_bin.c | 10 ++++++---- drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c | 14 +++++++------- include/linux/firmware/cirrus/cs_dsp_test_utils.h | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/drivers/firmware/cirrus/test/cs_dsp_mock_bin.c b/drivers/firmware/cirrus/test/cs_dsp_mock_bin.c index 3f8777ee4dc0..bc6b8651259c 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_mock_bin.c +++ b/drivers/firmware/cirrus/test/cs_dsp_mock_bin.c @@ -56,13 +56,14 @@ EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_bin_get_firmware, "FW_CS_DSP_KUNIT_TEST_UTILS") * @alg_id: Algorithm ID. * @alg_ver: Algorithm version. * @type: Type of the block. - * @offset: Offset. + * @offset: 16-bit offset. + * @offset32: 32-bit offset (sample rate on V1 and V2 file formats). * @payload_data: Pointer to buffer containing the payload data. * @payload_len_bytes: Length of payload data in bytes. */ void cs_dsp_mock_bin_add_raw_block(struct cs_dsp_mock_bin_builder *builder, unsigned int alg_id, unsigned int alg_ver, - int type, unsigned int offset, + int type, u16 offset, u32 offset32, const void *payload_data, size_t payload_len_bytes) { struct wmfw_coeff_item *item; @@ -75,6 +76,7 @@ void cs_dsp_mock_bin_add_raw_block(struct cs_dsp_mock_bin_builder *builder, item = builder->write_p; item->offset = cpu_to_le16(offset); + item->offset32 = cpu_to_le32(offset32); item->type = cpu_to_le16(type); item->id = cpu_to_le32(alg_id); item->ver = cpu_to_le32(alg_ver << 8); @@ -104,7 +106,7 @@ static void cs_dsp_mock_bin_add_name_or_info(struct cs_dsp_mock_bin_builder *bui info = tmp; } - cs_dsp_mock_bin_add_raw_block(builder, 0, 0, WMFW_INFO_TEXT, 0, info, info_len); + cs_dsp_mock_bin_add_raw_block(builder, 0, 0, WMFW_INFO_TEXT, 0, 0, info, info_len); kunit_kfree(builder->test_priv->test, tmp); } @@ -156,7 +158,7 @@ void cs_dsp_mock_bin_add_patch(struct cs_dsp_mock_bin_builder *builder, KUNIT_ASSERT_EQ(builder->test_priv->test, payload_len_bytes % 4, 0); cs_dsp_mock_bin_add_raw_block(builder, alg_id, alg_ver, - mem_region, reg_addr_offset, + mem_region, (u16)reg_addr_offset, 0, payload_data, payload_len_bytes); } EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_bin_add_patch, "FW_CS_DSP_KUNIT_TEST_UTILS"); diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c b/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c index a7ec956d2724..fe0112dc3077 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_bin_error.c @@ -66,24 +66,24 @@ static void bin_load_with_unknown_blocks(struct kunit *test) cs_dsp_mock_bin_add_raw_block(local->bin_builder, cs_dsp_bin_err_test_mock_algs[0].id, cs_dsp_bin_err_test_mock_algs[0].ver, - 0xf5, 0, + 0xf5, 0, 0, random_data, sizeof(random_data)); cs_dsp_mock_bin_add_raw_block(local->bin_builder, cs_dsp_bin_err_test_mock_algs[0].id, cs_dsp_bin_err_test_mock_algs[0].ver, - 0xf500, 0, + 0xf500, 0, 0, random_data, sizeof(random_data)); cs_dsp_mock_bin_add_raw_block(local->bin_builder, cs_dsp_bin_err_test_mock_algs[0].id, cs_dsp_bin_err_test_mock_algs[0].ver, - 0xc300, 0, + 0xc300, 0, 0, random_data, sizeof(random_data)); /* Add a single payload to be written to DSP memory */ cs_dsp_mock_bin_add_raw_block(local->bin_builder, cs_dsp_bin_err_test_mock_algs[0].id, cs_dsp_bin_err_test_mock_algs[0].ver, - WMFW_ADSP2_YM, 0, + WMFW_ADSP2_YM, 0, 0, payload_data, payload_size_bytes); bin = cs_dsp_mock_bin_get_firmware(local->bin_builder); @@ -277,7 +277,7 @@ static void bin_too_short_for_block_header(struct kunit *test) cs_dsp_mock_bin_add_raw_block(local->bin_builder, cs_dsp_bin_err_test_mock_algs[0].id, cs_dsp_bin_err_test_mock_algs[0].ver, - param->block_type, 0, + param->block_type, 0, 0, NULL, 0); bin = cs_dsp_mock_bin_get_firmware(local->bin_builder); @@ -309,7 +309,7 @@ static void bin_too_short_for_block_payload(struct kunit *test) cs_dsp_mock_bin_add_raw_block(local->bin_builder, cs_dsp_bin_err_test_mock_algs[0].id, cs_dsp_bin_err_test_mock_algs[0].ver, - param->block_type, 0, + param->block_type, 0, 0, payload, sizeof(payload)); bin = cs_dsp_mock_bin_get_firmware(local->bin_builder); @@ -341,7 +341,7 @@ static void bin_block_payload_len_garbage(struct kunit *test) cs_dsp_mock_bin_add_raw_block(local->bin_builder, cs_dsp_bin_err_test_mock_algs[0].id, cs_dsp_bin_err_test_mock_algs[0].ver, - param->block_type, 0, + param->block_type, 0, 0, &payload, sizeof(payload)); bin = cs_dsp_mock_bin_get_firmware(local->bin_builder); diff --git a/include/linux/firmware/cirrus/cs_dsp_test_utils.h b/include/linux/firmware/cirrus/cs_dsp_test_utils.h index 1f97764fdfd7..877fa4a496dd 100644 --- a/include/linux/firmware/cirrus/cs_dsp_test_utils.h +++ b/include/linux/firmware/cirrus/cs_dsp_test_utils.h @@ -126,7 +126,7 @@ struct cs_dsp_mock_bin_builder *cs_dsp_mock_bin_init(struct cs_dsp_test *priv, unsigned int fw_version); void cs_dsp_mock_bin_add_raw_block(struct cs_dsp_mock_bin_builder *builder, unsigned int alg_id, unsigned int alg_ver, - int type, unsigned int offset, + int type, u16 offset, u32 offset32, const void *payload_data, size_t payload_len_bytes); void cs_dsp_mock_bin_add_info(struct cs_dsp_mock_bin_builder *builder, const char *info); -- cgit v1.2.3 From 880f1eb5b95ccf250f567927462a7d3fa8f2a727 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 31 Dec 2025 17:27:08 +0000 Subject: firmware: cs_dsp: mock_bin: Add function to create long-offset patches Add cs_dsp_mock_bin_add_patch_off32(). This is the same as cs_dsp_mock_bin_add_patch() except that it puts the offset in the new 32-bit offset field and modifies the block type to indicate that it uses the long offset. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20251231172711.450024-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- drivers/firmware/cirrus/test/cs_dsp_mock_bin.c | 28 +++++++++++++++++++++++ include/linux/firmware/cirrus/cs_dsp_test_utils.h | 4 ++++ 2 files changed, 32 insertions(+) (limited to 'include') diff --git a/drivers/firmware/cirrus/test/cs_dsp_mock_bin.c b/drivers/firmware/cirrus/test/cs_dsp_mock_bin.c index bc6b8651259c..635e917e0516 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_mock_bin.c +++ b/drivers/firmware/cirrus/test/cs_dsp_mock_bin.c @@ -163,6 +163,34 @@ void cs_dsp_mock_bin_add_patch(struct cs_dsp_mock_bin_builder *builder, } EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_bin_add_patch, "FW_CS_DSP_KUNIT_TEST_UTILS"); +/** + * cs_dsp_mock_bin_add_patch_off32() - Add a patch data block with 32-bit offset. + * + * @builder: Pointer to struct cs_dsp_mock_bin_builder. + * @alg_id: Algorithm ID for the patch. + * @alg_ver: Algorithm version for the patch. + * @mem_region: Memory region for the patch. + * @reg_addr_offset: Offset to start of data in register addresses. + * @payload_data: Pointer to buffer containing the payload data. + * @payload_len_bytes: Length of payload data in bytes. + */ +void cs_dsp_mock_bin_add_patch_off32(struct cs_dsp_mock_bin_builder *builder, + unsigned int alg_id, unsigned int alg_ver, + int mem_region, unsigned int reg_addr_offset, + const void *payload_data, size_t payload_len_bytes) +{ + /* Payload length must be a multiple of 4 */ + KUNIT_ASSERT_EQ(builder->test_priv->test, payload_len_bytes % 4, 0); + + /* Mark the block as using the 32-bit offset */ + mem_region |= 0xf400; + + cs_dsp_mock_bin_add_raw_block(builder, alg_id, alg_ver, + mem_region, 0, reg_addr_offset, + payload_data, payload_len_bytes); +} +EXPORT_SYMBOL_NS_GPL(cs_dsp_mock_bin_add_patch_off32, "FW_CS_DSP_KUNIT_TEST_UTILS"); + /** * cs_dsp_mock_bin_init() - Initialize a struct cs_dsp_mock_bin_builder. * diff --git a/include/linux/firmware/cirrus/cs_dsp_test_utils.h b/include/linux/firmware/cirrus/cs_dsp_test_utils.h index 877fa4a496dd..51e99f47e90e 100644 --- a/include/linux/firmware/cirrus/cs_dsp_test_utils.h +++ b/include/linux/firmware/cirrus/cs_dsp_test_utils.h @@ -136,6 +136,10 @@ void cs_dsp_mock_bin_add_patch(struct cs_dsp_mock_bin_builder *builder, unsigned int alg_id, unsigned int alg_ver, int mem_region, unsigned int reg_addr_offset, const void *payload_data, size_t payload_len_bytes); +void cs_dsp_mock_bin_add_patch_off32(struct cs_dsp_mock_bin_builder *builder, + unsigned int alg_id, unsigned int alg_ver, + int mem_region, unsigned int reg_addr_offset, + const void *payload_data, size_t payload_len_bytes); struct firmware *cs_dsp_mock_bin_get_firmware(struct cs_dsp_mock_bin_builder *builder); struct cs_dsp_mock_wmfw_builder *cs_dsp_mock_wmfw_init(struct cs_dsp_test *priv, -- cgit v1.2.3 From ee69f55eb183efb43da14cdad72910b1b1cc2932 Mon Sep 17 00:00:00 2001 From: Oder Chiou Date: Wed, 31 Dec 2025 10:35:44 +0800 Subject: spi: export of_find_spi_controller_by_node() Some devices are primarily described on another bus (e.g. I2C) but also have an additional SPI connection that serves as a transport for firmware loading. Export of_find_spi_controller_by_node() so drivers can obtain the SPI controller referenced by a DT phandle. Signed-off-by: Oder Chiou Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/0e572a00aa305e588357162d400ba9472ce56dd3.1767148150.git.oder_chiou@realtek.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 3 ++- include/linux/spi/spi.h | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index e25df9990f82..ecb5281b04a2 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -4771,7 +4771,7 @@ static struct spi_device *of_find_spi_device_by_node(struct device_node *node) } /* The spi controllers are not using spi_bus, so we find it with another way */ -static struct spi_controller *of_find_spi_controller_by_node(struct device_node *node) +struct spi_controller *of_find_spi_controller_by_node(struct device_node *node) { struct device *dev; @@ -4784,6 +4784,7 @@ static struct spi_controller *of_find_spi_controller_by_node(struct device_node /* Reference got in class_find_device */ return container_of(dev, struct spi_controller, dev); } +EXPORT_SYMBOL_GPL(of_find_spi_controller_by_node); static int of_spi_notify(struct notifier_block *nb, unsigned long action, void *arg) diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index cb2c2df31089..e6fdaf02386c 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -882,6 +882,15 @@ extern int devm_spi_register_controller(struct device *dev, struct spi_controller *ctlr); extern void spi_unregister_controller(struct spi_controller *ctlr); +#if IS_ENABLED(CONFIG_OF_DYNAMIC) +extern struct spi_controller *of_find_spi_controller_by_node(struct device_node *node); +#else +static inline struct spi_controller *of_find_spi_controller_by_node(struct device_node *node) +{ + return NULL; +} +#endif + #if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SPI_MASTER) extern struct spi_controller *acpi_spi_find_controller_by_adev(struct acpi_device *adev); extern struct spi_device *acpi_spi_device_alloc(struct spi_controller *ctlr, -- cgit v1.2.3 From 037f8d896688bf3384eb6bf34e24e8fbc9f6e02d Mon Sep 17 00:00:00 2001 From: Oder Chiou Date: Wed, 31 Dec 2025 10:36:49 +0800 Subject: spi: change of_find_spi_controller_by_node() gating to CONFIG_OF Currently, the helper of_find_spi_controller_by_node() is gated under CONFIG_OF_DYNAMIC. This prevents drivers from using it in all CONFIG_OF configurations. This patch moves the gating to CONFIG_OF, keeping the inline fallback returning NULL when Device Tree support is disabled. Signed-off-by: Oder Chiou Link: https://patch.msgid.link/6d8ae977d9f4726ea23ad5382638750593f9a2e4.1767148150.git.oder_chiou@realtek.com Signed-off-by: Mark Brown --- drivers/spi/spi.c | 20 +++++++++++--------- include/linux/spi/spi.h | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index ecb5281b04a2..2badacc7a91c 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -4761,15 +4761,7 @@ EXPORT_SYMBOL_GPL(spi_write_then_read); /*-------------------------------------------------------------------------*/ -#if IS_ENABLED(CONFIG_OF_DYNAMIC) -/* Must call put_device() when done with returned spi_device device */ -static struct spi_device *of_find_spi_device_by_node(struct device_node *node) -{ - struct device *dev = bus_find_device_by_of_node(&spi_bus_type, node); - - return dev ? to_spi_device(dev) : NULL; -} - +#if IS_ENABLED(CONFIG_OF) /* The spi controllers are not using spi_bus, so we find it with another way */ struct spi_controller *of_find_spi_controller_by_node(struct device_node *node) { @@ -4785,6 +4777,16 @@ struct spi_controller *of_find_spi_controller_by_node(struct device_node *node) return container_of(dev, struct spi_controller, dev); } EXPORT_SYMBOL_GPL(of_find_spi_controller_by_node); +#endif + +#if IS_ENABLED(CONFIG_OF_DYNAMIC) +/* Must call put_device() when done with returned spi_device device */ +static struct spi_device *of_find_spi_device_by_node(struct device_node *node) +{ + struct device *dev = bus_find_device_by_of_node(&spi_bus_type, node); + + return dev ? to_spi_device(dev) : NULL; +} static int of_spi_notify(struct notifier_block *nb, unsigned long action, void *arg) diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index e6fdaf02386c..8bc616b00343 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -882,7 +882,7 @@ extern int devm_spi_register_controller(struct device *dev, struct spi_controller *ctlr); extern void spi_unregister_controller(struct spi_controller *ctlr); -#if IS_ENABLED(CONFIG_OF_DYNAMIC) +#if IS_ENABLED(CONFIG_OF) extern struct spi_controller *of_find_spi_controller_by_node(struct device_node *node); #else static inline struct spi_controller *of_find_spi_controller_by_node(struct device_node *node) -- cgit v1.2.3 From c644bce62b9c6b441143a03c910f986109c47001 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Thu, 8 Jan 2026 08:45:22 +0100 Subject: readdir: require opt-in for d_type flags Commit c31f91c6af96 ("fuse: don't allow signals to interrupt getdents copying") introduced the use of high bits in d_type as flags. However, overlayfs was not adapted to handle this change. In ovl_cache_entry_new(), the code checks if d_type == DT_CHR to determine if an entry might be a whiteout. When fuse is used as the lower layer and sets high bits in d_type, this comparison fails, causing whiteout files to not be recognized properly and resulting in incorrect overlayfs behavior. Fix this by requiring callers of iterate_dir() to opt-in for getting flag bits in d_type outside of S_DT_MASK. Fixes: c31f91c6af96 ("fuse: don't allow signals to interrupt getdents copying") Link: https://lore.kernel.org/all/20260107034551.439-1-luochunsheng@ustc.edu/ Link: https://github.com/containerd/stargz-snapshotter/issues/2214 Reported-by: Chunsheng Luo Reviewed-by: Chunsheng Luo Tested-by: Chunsheng Luo Signed-off-by: Amir Goldstein Link: https://patch.msgid.link/20260108074522.3400998-1-amir73il@gmail.com Signed-off-by: Christian Brauner --- fs/readdir.c | 3 +++ include/linux/fs.h | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/fs/readdir.c b/fs/readdir.c index 7764b8638978..73707b6816e9 100644 --- a/fs/readdir.c +++ b/fs/readdir.c @@ -316,6 +316,7 @@ SYSCALL_DEFINE3(getdents, unsigned int, fd, struct getdents_callback buf = { .ctx.actor = filldir, .ctx.count = count, + .ctx.dt_flags_mask = FILLDIR_FLAG_NOINTR, .current_dir = dirent }; int error; @@ -400,6 +401,7 @@ SYSCALL_DEFINE3(getdents64, unsigned int, fd, struct getdents_callback64 buf = { .ctx.actor = filldir64, .ctx.count = count, + .ctx.dt_flags_mask = FILLDIR_FLAG_NOINTR, .current_dir = dirent }; int error; @@ -569,6 +571,7 @@ COMPAT_SYSCALL_DEFINE3(getdents, unsigned int, fd, struct compat_getdents_callback buf = { .ctx.actor = compat_filldir, .ctx.count = count, + .ctx.dt_flags_mask = FILLDIR_FLAG_NOINTR, .current_dir = dirent, }; int error; diff --git a/include/linux/fs.h b/include/linux/fs.h index f5c9cf28c4dc..a01621fa636a 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1855,6 +1855,8 @@ struct dir_context { * INT_MAX unlimited */ int count; + /* @actor supports these flags in d_type high bits */ + unsigned int dt_flags_mask; }; /* If OR-ed with d_type, pending signals are not checked */ @@ -3524,7 +3526,9 @@ static inline bool dir_emit(struct dir_context *ctx, const char *name, int namelen, u64 ino, unsigned type) { - return ctx->actor(ctx, name, namelen, ctx->pos, ino, type); + unsigned int dt_mask = S_DT_MASK | ctx->dt_flags_mask; + + return ctx->actor(ctx, name, namelen, ctx->pos, ino, type & dt_mask); } static inline bool dir_emit_dot(struct file *file, struct dir_context *ctx) { -- cgit v1.2.3 From 0432fe32c129780f89fd5426059cb1ddd8e50858 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Mon, 12 Jan 2026 13:32:18 +0200 Subject: ASoC: sof: ipc4-topology: Add topology tokens domain_in stack & heap_bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add topology tokens for defining user-space domain_id, required stack and heap size byte for a component. The new topology tokens are SOF_TKN_COMP_DOMAIN_ID, SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT and SOF_TKN_COMP_STACK_BYTES_REQUIREMENT for defining required stack and heap size for a component. Signed-off-by: Jyri Sarha Reviewed-by: Ranjani Sridharan Reviewed-by: Guennadi Liakhovetski Reviewed-by: Péter Ujfalusi Signed-off-by: Peter Ujfalusi Link: https://patch.msgid.link/20260112113221.4442-2-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- include/uapi/sound/sof/tokens.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'include') diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h index 9ce72fbd6f11..5fa8ab5088e0 100644 --- a/include/uapi/sound/sof/tokens.h +++ b/include/uapi/sound/sof/tokens.h @@ -107,6 +107,9 @@ #define SOF_TKN_COMP_NO_WNAME_IN_KCONTROL_NAME 417 #define SOF_TKN_COMP_SCHED_DOMAIN 418 +#define SOF_TKN_COMP_DOMAIN_ID 419 +#define SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT 420 +#define SOF_TKN_COMP_STACK_BYTES_REQUIREMENT 421 /* SSP */ #define SOF_TKN_INTEL_SSP_CLKS_CONTROL 500 -- cgit v1.2.3 From 1cd8fbec6dfa9a9c25400b775fed887b59153afd Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Mon, 12 Jan 2026 13:32:20 +0200 Subject: ASoC: SOF: ipc4: sof_ipc4_module_init_ext_init structs and macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add structs and macros for struct sof_ipc4_module_init_ext_init, following struct sof_ipc4_module_init_ext_object array, and struct sof_ipc4_mod_init_ext_dp_memory_data as object payload. Signed-off-by: Jyri Sarha Reviewed-by: Ranjani Sridharan Reviewed-by: Guennadi Liakhovetski Reviewed-by: Péter Ujfalusi Signed-off-by: Peter Ujfalusi Link: https://patch.msgid.link/20260112113221.4442-4-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- include/sound/sof/ipc4/header.h | 75 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) (limited to 'include') diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h index 15fac532688e..4554e5e8cab5 100644 --- a/include/sound/sof/ipc4/header.h +++ b/include/sound/sof/ipc4/header.h @@ -352,6 +352,10 @@ struct sof_ipc4_base_module_cfg { #define SOF_IPC4_MOD_EXT_DOMAIN_MASK BIT(28) #define SOF_IPC4_MOD_EXT_DOMAIN(x) ((x) << SOF_IPC4_MOD_EXT_DOMAIN_SHIFT) +#define SOF_IPC4_MOD_EXT_EXTENDED_INIT_SHIFT 29 +#define SOF_IPC4_MOD_EXT_EXTENDED_INIT_MASK BIT(29) +#define SOF_IPC4_MOD_EXT_EXTENDED_INIT(x) ((x) << SOF_IPC4_MOD_EXT_EXTENDED_SHIFT) + /* bind/unbind module ipc msg */ #define SOF_IPC4_MOD_EXT_DST_MOD_ID_SHIFT 0 #define SOF_IPC4_MOD_EXT_DST_MOD_ID_MASK GENMASK(15, 0) @@ -586,6 +590,77 @@ struct sof_ipc4_notify_module_data { #define SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL 0xA15A0000 #define SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_PARAMID_MASK GENMASK(15, 0) +/* + * Macros for creating struct sof_ipc4_module_init_ext_init payload + * with its associated data. ext_init payload should be the first + * piece of payload following SOF_IPC4_MOD_INIT_INSTANCE msg, and its + * existence is indicated with SOF_IPC4_MOD_EXT_EXTENDED-bit. + * + * The macros below apply to sof_ipc4_module_init_ext_init.word0 + */ +#define SOF_IPC4_MOD_INIT_EXT_RTOS_DOMAIN_SHIFT 0 +#define SOF_IPC4_MOD_INIT_EXT_RTOS_DOMAIN_MASK BIT(0) +#define SOF_IPC4_MOD_INIT_EXT_RTOS_DOMAIN(x) ((x) << SOF_IPC4_MOD_INIT_EXT_RTOS_DOMAIN_SHIFT) + +#define SOF_IPC4_MOD_INIT_EXT_GNA_USED_SHIFT 1 +#define SOF_IPC4_MOD_INIT_EXT_GNA_USED_MASK BIT(1) +#define SOF_IPC4_MOD_INIT_EXT_GNA_USED(x) ((x) << SOF_IPC4_MOD_INIT_EXT_GNA_USED_SHIFT) + +#define SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_SHIFT 2 +#define SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK BIT(2) +#define SOF_IPC4_MOD_INIT_EXT_DATA_ARRAY(x) ((x) << SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_SHIFT) + +struct sof_ipc4_module_init_ext_init { + u32 word0; + u32 rsvd1; + u32 rsvd2; +} __packed __aligned(4); + +/* + * SOF_IPC4_MOD_EXT_EXTENDED payload may be followed by arbitrary + * number of object array objects. SOF_IPC4_MOD_INIT_EXT_DATA_ARRAY + * -bit indicates that an array object follows struct + * sof_ipc4_module_init_ext_init. + * + * The object header's SOF_IPC4_MOD_INIT_EXT_OBJ_LAST-bit in struct + * sof_ipc4_module_init_ext_object indicates if the array is continued + * with another object. The header has also fields to identify the + * object, SOF_IPC4_MOD_INIT_EXT_OBJ_ID, and to indicate the object's + * size in 32-bit words, SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS, not + * including the header itself. + * + * The macros below apply to sof_ipc4_module_init_ext_object.header + */ +#define SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_SHIFT 0 +#define SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_MASK BIT(0) +#define SOF_IPC4_MOD_INIT_EXT_OBJ_LAST(x) ((x) << SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_SHIFT) + +#define SOF_IPC4_MOD_INIT_EXT_OBJ_ID_SHIFT 1 +#define SOF_IPC4_MOD_INIT_EXT_OBJ_ID_MASK GENMASK(15, 1) +#define SOF_IPC4_MOD_INIT_EXT_OBJ_ID(x) ((x) << SOF_IPC4_MOD_INIT_EXT_OBJ_ID_SHIFT) + +#define SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS_SHIFT 16 +#define SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS_MASK GENMASK(31, 16) +#define SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(x) ((x) << SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS_SHIFT) + +struct sof_ipc4_module_init_ext_object { + u32 header; + u32 data[]; +} __packed __aligned(4); + +enum sof_ipc4_mod_init_ext_obj_id { + SOF_IPC4_MOD_INIT_DATA_ID_INVALID = 0, + SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA, + SOF_IPC4_MOD_INIT_DATA_ID_MAX = SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA, +}; + +/* DP module memory configuration data object for ext_init object array */ +struct sof_ipc4_mod_init_ext_dp_memory_data { + u32 domain_id; /* userspace domain ID */ + u32 stack_bytes; /* stack size in bytes, 0 means default size */ + u32 heap_bytes; /* stack size in bytes, 0 means default size */ +} __packed __aligned(4); + /** @}*/ #endif -- cgit v1.2.3 From 6626734dd2b151753e134730e27d17e64784c345 Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Mon, 12 Jan 2026 15:46:37 +0000 Subject: mm_zone: Generalise has_managed_dma() It would be useful to be able to check for potential DMA pages beyond just ZONE_DMA - generalise the existing has_managed_dma() function to allow checking other zones too. Signed-off-by: Robin Murphy Acked-by: David Hildenbrand (Red Hat) Acked-by: Mike Rapoport (Microsoft) Tested-by: Vladimir Kondratiev Reviewed-by: Baoquan He Signed-off-by: Marek Szyprowski Link: https://lore.kernel.org/r/bd002d2351074e57be1ca08f03f333debac658fb.1768230104.git.robin.murphy@arm.com --- include/linux/mmzone.h | 9 +++++---- mm/page_alloc.c | 8 ++------ 2 files changed, 7 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index 75ef7c9f9307..fc5d6c88d2f0 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -1648,14 +1648,15 @@ static inline int is_highmem(const struct zone *zone) return is_highmem_idx(zone_idx(zone)); } -#ifdef CONFIG_ZONE_DMA -bool has_managed_dma(void); -#else +bool has_managed_zone(enum zone_type zone); static inline bool has_managed_dma(void) { +#ifdef CONFIG_ZONE_DMA + return has_managed_zone(ZONE_DMA); +#else return false; -} #endif +} #ifndef CONFIG_NUMA diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 822e05f1a964..36ccc85c5073 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -7418,20 +7418,16 @@ bool put_page_back_buddy(struct page *page) } #endif -#ifdef CONFIG_ZONE_DMA -bool has_managed_dma(void) +bool has_managed_zone(enum zone_type zone) { struct pglist_data *pgdat; for_each_online_pgdat(pgdat) { - struct zone *zone = &pgdat->node_zones[ZONE_DMA]; - - if (managed_zone(zone)) + if (managed_zone(&pgdat->node_zones[zone])) return true; } return false; } -#endif /* CONFIG_ZONE_DMA */ #ifdef CONFIG_UNACCEPTED_MEMORY -- cgit v1.2.3 From 9e3d4f794cbe9a4e286b3052cb97908005807aee Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 9 Jan 2026 14:52:03 +0000 Subject: ASoC: SDCA: Add SDCA IRQ enable/disable helpers Add helpers to enable and disable the SDCA IRQs by Function. These are useful to sequence the powering down and up around system suspend. Signed-off-by: Charles Keepax Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20260109145206.3456151-2-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/sdca_interrupts.h | 7 ++++ sound/soc/sdca/sdca_interrupts.c | 76 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) (limited to 'include') diff --git a/include/sound/sdca_interrupts.h b/include/sound/sdca_interrupts.h index 8f13417d129a..9bcb5d8fd592 100644 --- a/include/sound/sdca_interrupts.h +++ b/include/sound/sdca_interrupts.h @@ -84,4 +84,11 @@ int sdca_irq_populate(struct sdca_function_data *function, struct sdca_interrupt_info *sdca_irq_allocate(struct device *dev, struct regmap *regmap, int irq); +void sdca_irq_enable_early(struct sdca_function_data *function, + struct sdca_interrupt_info *info); +void sdca_irq_enable(struct sdca_function_data *function, + struct sdca_interrupt_info *info); +void sdca_irq_disable(struct sdca_function_data *function, + struct sdca_interrupt_info *info); + #endif diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c index ff3a7e405fdc..afef7bbf613c 100644 --- a/sound/soc/sdca/sdca_interrupts.c +++ b/sound/soc/sdca/sdca_interrupts.c @@ -541,3 +541,79 @@ struct sdca_interrupt_info *sdca_irq_allocate(struct device *sdev, return info; } EXPORT_SYMBOL_NS_GPL(sdca_irq_allocate, "SND_SOC_SDCA"); + +static void irq_enable_flags(struct sdca_function_data *function, + struct sdca_interrupt_info *info, bool early) +{ + struct sdca_interrupt *interrupt; + int i; + + for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) { + interrupt = &info->irqs[i]; + + if (!interrupt || interrupt->function != function) + continue; + + switch (SDCA_CTL_TYPE(interrupt->entity->type, + interrupt->control->sel)) { + case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER): + if (early) + enable_irq(interrupt->irq); + break; + default: + if (!early) + enable_irq(interrupt->irq); + break; + } + } +} + +/** + * sdca_irq_enable_early - Re-enable early SDCA IRQs for a given function + * @function: Pointer to the SDCA Function. + * @info: Pointer to the SDCA interrupt info for this device. + * + * The early version of the IRQ enable allows enabling IRQs which may be + * necessary to bootstrap functionality for other IRQs, such as the FDL + * process. + */ +void sdca_irq_enable_early(struct sdca_function_data *function, + struct sdca_interrupt_info *info) +{ + irq_enable_flags(function, info, true); +} +EXPORT_SYMBOL_NS_GPL(sdca_irq_enable_early, "SND_SOC_SDCA"); + +/** + * sdca_irq_enable - Re-enable SDCA IRQs for a given function + * @function: Pointer to the SDCA Function. + * @info: Pointer to the SDCA interrupt info for this device. + */ +void sdca_irq_enable(struct sdca_function_data *function, + struct sdca_interrupt_info *info) +{ + irq_enable_flags(function, info, false); +} +EXPORT_SYMBOL_NS_GPL(sdca_irq_enable, "SND_SOC_SDCA"); + +/** + * sdca_irq_disable - Disable SDCA IRQs for a given function + * @function: Pointer to the SDCA Function. + * @info: Pointer to the SDCA interrupt info for this device. + */ +void sdca_irq_disable(struct sdca_function_data *function, + struct sdca_interrupt_info *info) +{ + struct sdca_interrupt *interrupt; + int i; + + for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) { + interrupt = &info->irqs[i]; + + if (!interrupt || interrupt->function != function) + continue; + + disable_irq(interrupt->irq); + } +} +EXPORT_SYMBOL_NS_GPL(sdca_irq_disable, "SND_SOC_SDCA"); -- cgit v1.2.3 From 69c88a6a49cfe1fd6bd5c1166d02a7dd29de9569 Mon Sep 17 00:00:00 2001 From: "Anirudh Rayabharam (Microsoft)" Date: Mon, 5 Jan 2026 12:28:36 +0000 Subject: mshv: add definitions for arm64 gpa intercepts Add definitions required for handling GPA intercepts on arm64. Signed-off-by: Anirudh Rayabharam (Microsoft) Reviewed-by: Stanislav Kinsburskii Signed-off-by: Wei Liu --- include/hyperv/hvhdk.h | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'include') diff --git a/include/hyperv/hvhdk.h b/include/hyperv/hvhdk.h index 469186df7826..08965970c17d 100644 --- a/include/hyperv/hvhdk.h +++ b/include/hyperv/hvhdk.h @@ -800,6 +800,53 @@ struct hv_x64_memory_intercept_message { u8 instruction_bytes[16]; } __packed; +#if IS_ENABLED(CONFIG_ARM64) +union hv_arm64_vp_execution_state { + u16 as_uint16; + struct { + u16 cpl:2; /* Exception Level (EL) */ + u16 debug_active:1; + u16 interruption_pending:1; + u16 vtl:4; + u16 virtualization_fault_active:1; + u16 reserved:7; + } __packed; +}; + +struct hv_arm64_intercept_message_header { + u32 vp_index; + u8 instruction_length; + u8 intercept_access_type; + union hv_arm64_vp_execution_state execution_state; + u64 pc; + u64 cpsr; +} __packed; + +union hv_arm64_memory_access_info { + u8 as_uint8; + struct { + u8 gva_valid:1; + u8 gva_gpa_valid:1; + u8 hypercall_output_pending:1; + u8 reserved:5; + } __packed; +}; + +struct hv_arm64_memory_intercept_message { + struct hv_arm64_intercept_message_header header; + u32 cache_type; /* enum hv_cache_type */ + u8 instruction_byte_count; + union hv_arm64_memory_access_info memory_access_info; + u16 reserved1; + u8 instruction_bytes[4]; + u32 reserved2; + u64 guest_virtual_address; + u64 guest_physical_address; + u64 syndrome; +} __packed; + +#endif /* CONFIG_ARM64 */ + /* * Dispatch state for the VP communicated by the hypervisor to the * VP-dispatching thread in the root on return from HVCALL_DISPATCH_VP. -- cgit v1.2.3 From a995fe1a3aa78b7d06cc1cc7b6b8436c5e93b07f Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 7 Jan 2026 11:35:05 +0100 Subject: rust: driver: drop device private data post unbind Currently, the driver's device private data is allocated and initialized from driver core code called from bus abstractions after the driver's probe() callback returned the corresponding initializer. Similarly, the driver's device private data is dropped within the remove() callback of bus abstractions after calling the remove() callback of the corresponding driver. However, commit 6f61a2637abe ("rust: device: introduce Device::drvdata()") introduced an accessor for the driver's device private data for a Device, i.e. a device that is currently bound to a driver. Obviously, this is in conflict with dropping the driver's device private data in remove(), since a device can not be considered to be fully unbound after remove() has finished: We also have to consider registrations guarded by devres - such as IRQ or class device registrations - which are torn down after remove() in devres_release_all(). Thus, it can happen that, for instance, a class device or IRQ callback still calls Device::drvdata(), which then runs concurrently to remove() (which sets dev->driver_data to NULL and drops the driver's device private data), before devres_release_all() started to tear down the corresponding registration. This is because devres guarded registrations can, as expected, access the corresponding Device that defines their scope. In C it simply is the driver's responsibility to ensure that its device private data is freed after e.g. an IRQ registration is unregistered. Typically, C drivers achieve this by allocating their device private data with e.g. devm_kzalloc() before doing anything else, i.e. before e.g. registering an IRQ with devm_request_threaded_irq(), relying on the reverse order cleanup of devres. Technically, we could do something similar in Rust. However, the resulting code would be pretty messy: In Rust we have to differentiate between allocated but uninitialized memory and initialized memory in the type system. Thus, we would need to somehow keep track of whether the driver's device private data object has been initialized (i.e. probe() was successful and returned a valid initializer for this memory) and conditionally call the destructor of the corresponding object when it is freed. This is because we'd need to allocate and register the memory of the driver's device private data *before* it is initialized by the initializer returned by the driver's probe() callback, because the driver could already register devres guarded registrations within probe() outside of the driver's device private data initializer. Luckily there is a much simpler solution: Instead of dropping the driver's device private data at the end of remove(), we just drop it after the device has been fully unbound, i.e. after all devres callbacks have been processed. For this, we introduce a new post_unbind() callback private to the driver-core, i.e. the callback is neither exposed to drivers, nor to bus abstractions. This way, the driver-core code can simply continue to conditionally allocate the memory for the driver's device private data when the driver's initializer is returned from probe() - no change needed - and drop it when the driver-core code receives the post_unbind() callback. Closes: https://lore.kernel.org/all/DEZMS6Y4A7XE.XE7EUBT5SJFJ@kernel.org/ Fixes: 6f61a2637abe ("rust: device: introduce Device::drvdata()") Acked-by: Alice Ryhl Acked-by: Greg Kroah-Hartman Acked-by: Igor Korotin Link: https://patch.msgid.link/20260107103511.570525-7-dakr@kernel.org [ Remove #ifdef CONFIG_RUST, rename post_unbind() to post_unbind_rust(). - Danilo] Signed-off-by: Danilo Krummrich --- drivers/base/dd.c | 2 ++ include/linux/device/driver.h | 9 +++++++++ rust/kernel/auxiliary.rs | 4 ++-- rust/kernel/device.rs | 20 +++++++++++--------- rust/kernel/driver.rs | 36 +++++++++++++++++++++++++++++++++++- rust/kernel/i2c.rs | 4 ++-- rust/kernel/pci.rs | 4 ++-- rust/kernel/platform.rs | 4 ++-- rust/kernel/usb.rs | 4 ++-- 9 files changed, 67 insertions(+), 20 deletions(-) (limited to 'include') diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 349f31bedfa1..bea8da5f8a3a 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -548,6 +548,8 @@ static DEVICE_ATTR_RW(state_synced); static void device_unbind_cleanup(struct device *dev) { devres_release_all(dev); + if (dev->driver->p_cb.post_unbind_rust) + dev->driver->p_cb.post_unbind_rust(dev); arch_teardown_dma_ops(dev); kfree(dev->dma_range_map); dev->dma_range_map = NULL; diff --git a/include/linux/device/driver.h b/include/linux/device/driver.h index cd8e0f0a634b..bbc67ec513ed 100644 --- a/include/linux/device/driver.h +++ b/include/linux/device/driver.h @@ -85,6 +85,8 @@ enum probe_type { * uevent. * @p: Driver core's private data, no one other than the driver * core can touch this. + * @p_cb: Callbacks private to the driver core; no one other than the + * driver core is allowed to touch this. * * The device driver-model tracks all of the drivers known to the system. * The main reason for this tracking is to enable the driver core to match @@ -119,6 +121,13 @@ struct device_driver { void (*coredump) (struct device *dev); struct driver_private *p; + struct { + /* + * Called after remove() and after all devres entries have been + * processed. This is a Rust only callback. + */ + void (*post_unbind_rust)(struct device *dev); + } p_cb; }; diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index 17574aa5066f..be76f11aecb7 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -96,9 +96,9 @@ impl Adapter { // SAFETY: `remove_callback` is only ever called after a successful call to // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called // and stored a `Pin>`. - let data = unsafe { adev.as_ref().drvdata_obtain::() }; + let data = unsafe { adev.as_ref().drvdata_borrow::() }; - T::unbind(adev, data.as_ref()); + T::unbind(adev, data); } } diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 71b200df0f40..031720bf5d8c 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -232,30 +232,32 @@ impl Device { /// /// # Safety /// - /// - Must only be called once after a preceding call to [`Device::set_drvdata`]. /// - The type `T` must match the type of the `ForeignOwnable` previously stored by /// [`Device::set_drvdata`]. - pub unsafe fn drvdata_obtain(&self) -> Pin> { + pub(crate) unsafe fn drvdata_obtain(&self) -> Option>> { // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct device`. let ptr = unsafe { bindings::dev_get_drvdata(self.as_raw()) }; // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct device`. unsafe { bindings::dev_set_drvdata(self.as_raw(), core::ptr::null_mut()) }; + if ptr.is_null() { + return None; + } + // SAFETY: - // - By the safety requirements of this function, `ptr` comes from a previous call to - // `into_foreign()`. + // - If `ptr` is not NULL, it comes from a previous call to `into_foreign()`. // - `dev_get_drvdata()` guarantees to return the same pointer given to `dev_set_drvdata()` // in `into_foreign()`. - unsafe { Pin::>::from_foreign(ptr.cast()) } + Some(unsafe { Pin::>::from_foreign(ptr.cast()) }) } /// Borrow the driver's private data bound to this [`Device`]. /// /// # Safety /// - /// - Must only be called after a preceding call to [`Device::set_drvdata`] and before - /// [`Device::drvdata_obtain`]. + /// - Must only be called after a preceding call to [`Device::set_drvdata`] and before the + /// device is fully unbound. /// - The type `T` must match the type of the `ForeignOwnable` previously stored by /// [`Device::set_drvdata`]. pub unsafe fn drvdata_borrow(&self) -> Pin<&T> { @@ -271,7 +273,7 @@ impl Device { /// # Safety /// /// - Must only be called after a preceding call to [`Device::set_drvdata`] and before - /// [`Device::drvdata_obtain`]. + /// the device is fully unbound. /// - The type `T` must match the type of the `ForeignOwnable` previously stored by /// [`Device::set_drvdata`]. unsafe fn drvdata_unchecked(&self) -> Pin<&T> { @@ -320,7 +322,7 @@ impl Device { // SAFETY: // - The above check of `dev_get_drvdata()` guarantees that we are called after - // `set_drvdata()` and before `drvdata_obtain()`. + // `set_drvdata()`. // - We've just checked that the type of the driver's private data is in fact `T`. Ok(unsafe { self.drvdata_unchecked() }) } diff --git a/rust/kernel/driver.rs b/rust/kernel/driver.rs index ba1ca1f7a7e2..bee3ae21a27b 100644 --- a/rust/kernel/driver.rs +++ b/rust/kernel/driver.rs @@ -177,7 +177,39 @@ unsafe impl Sync for Registration {} // any thread, so `Registration` is `Send`. unsafe impl Send for Registration {} -impl Registration { +impl Registration { + extern "C" fn post_unbind_callback(dev: *mut bindings::device) { + // SAFETY: The driver core only ever calls the post unbind callback with a valid pointer to + // a `struct device`. + // + // INVARIANT: `dev` is valid for the duration of the `post_unbind_callback()`. + let dev = unsafe { &*dev.cast::>() }; + + // `remove()` and all devres callbacks have been completed at this point, hence drop the + // driver's device private data. + // + // SAFETY: By the safety requirements of the `Driver` trait, `T::DriverData` is the + // driver's device private data type. + drop(unsafe { dev.drvdata_obtain::() }); + } + + /// Attach generic `struct device_driver` callbacks. + fn callbacks_attach(drv: &Opaque) { + let ptr = drv.get().cast::(); + + // SAFETY: + // - `drv.get()` yields a valid pointer to `Self::DriverType`. + // - Adding `DEVICE_DRIVER_OFFSET` yields the address of the embedded `struct device_driver` + // as guaranteed by the safety requirements of the `Driver` trait. + let base = unsafe { ptr.add(T::DEVICE_DRIVER_OFFSET) }; + + // CAST: `base` points to the offset of the embedded `struct device_driver`. + let base = base.cast::(); + + // SAFETY: It is safe to set the fields of `struct device_driver` on initialization. + unsafe { (*base).p_cb.post_unbind_rust = Some(Self::post_unbind_callback) }; + } + /// Creates a new instance of the registration object. pub fn new(name: &'static CStr, module: &'static ThisModule) -> impl PinInit { try_pin_init!(Self { @@ -189,6 +221,8 @@ impl Registration { // just been initialised above, so it's also valid for read. let drv = unsafe { &*(ptr as *const Opaque) }; + Self::callbacks_attach(drv); + // SAFETY: `drv` is guaranteed to be pinned until `T::unregister`. unsafe { T::register(drv, name, module) } }), diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs index e86242227081..39b0a9a207fd 100644 --- a/rust/kernel/i2c.rs +++ b/rust/kernel/i2c.rs @@ -178,9 +178,9 @@ impl Adapter { // SAFETY: `remove_callback` is only ever called after a successful call to // `probe_callback`, hence it's guaranteed that `I2cClient::set_drvdata()` has been called // and stored a `Pin>`. - let data = unsafe { idev.as_ref().drvdata_obtain::() }; + let data = unsafe { idev.as_ref().drvdata_borrow::() }; - T::unbind(idev, data.as_ref()); + T::unbind(idev, data); } extern "C" fn shutdown_callback(idev: *mut bindings::i2c_client) { diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index 590723dcb5ae..bea76ca9c3da 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -123,9 +123,9 @@ impl Adapter { // SAFETY: `remove_callback` is only ever called after a successful call to // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called // and stored a `Pin>`. - let data = unsafe { pdev.as_ref().drvdata_obtain::() }; + let data = unsafe { pdev.as_ref().drvdata_borrow::() }; - T::unbind(pdev, data.as_ref()); + T::unbind(pdev, data); } } diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index b8a681df9ddc..35a5813ffb33 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -101,9 +101,9 @@ impl Adapter { // SAFETY: `remove_callback` is only ever called after a successful call to // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called // and stored a `Pin>`. - let data = unsafe { pdev.as_ref().drvdata_obtain::() }; + let data = unsafe { pdev.as_ref().drvdata_borrow::() }; - T::unbind(pdev, data.as_ref()); + T::unbind(pdev, data); } } diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index 4cf4bb1705b5..67ce5c85c619 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -103,9 +103,9 @@ impl Adapter { // SAFETY: `disconnect_callback` is only ever called after a successful call to // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called // and stored a `Pin>`. - let data = unsafe { dev.drvdata_obtain::() }; + let data = unsafe { dev.drvdata_borrow::() }; - T::disconnect(intf, data.as_ref()); + T::disconnect(intf, data); } } -- cgit v1.2.3 From 10d28cffb3f6ec7ad67f0a4cd32c2afa92909452 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Wed, 3 Dec 2025 16:24:38 +0000 Subject: comedi: Fix getting range information for subdevices 16 to 255 The `COMEDI_RANGEINFO` ioctl does not work properly for subdevice indices above 15. Currently, the only in-tree COMEDI drivers that support more than 16 subdevices are the "8255" driver and the "comedi_bond" driver. Making the ioctl work for subdevice indices up to 255 is achievable. It needs minor changes to the handling of the `COMEDI_RANGEINFO` and `COMEDI_CHANINFO` ioctls that should be mostly harmless to user-space, apart from making them less broken. Details follow... The `COMEDI_RANGEINFO` ioctl command gets the list of supported ranges (usually with units of volts or milliamps) for a COMEDI subdevice or channel. (Only some subdevices have per-channel range tables, indicated by the `SDF_RANGETYPE` flag in the subdevice information.) It uses a `range_type` value and a user-space pointer, both supplied by user-space, but the `range_type` value should match what was obtained using the `COMEDI_CHANINFO` ioctl (if the subdevice has per-channel range tables) or `COMEDI_SUBDINFO` ioctl (if the subdevice uses a single range table for all channels). Bits 15 to 0 of the `range_type` value contain the length of the range table, which is the only part that user-space should care about (so it can use a suitably sized buffer to fetch the range table). Bits 23 to 16 store the channel index, which is assumed to be no more than 255 if the subdevice has per-channel range tables, and is set to 0 if the subdevice has a single range table. For `range_type` values produced by the `COMEDI_SUBDINFO` ioctl, bits 31 to 24 contain the subdevice index, which is assumed to be no more than 255. But for `range_type` values produced by the `COMEDI_CHANINFO` ioctl, bits 27 to 24 contain the subdevice index, which is assumed to be no more than 15, and bits 31 to 28 contain the COMEDI device's minor device number for some unknown reason lost in the mists of time. The `COMEDI_RANGEINFO` ioctl extract the length from bits 15 to 0 of the user-supplied `range_type` value, extracts the channel index from bits 23 to 16 (only used if the subdevice has per-channel range tables), extracts the subdevice index from bits 27 to 24, and ignores bits 31 to 28. So for subdevice indices 16 to 255, the `COMEDI_SUBDINFO` or `COMEDI_CHANINFO` ioctl will report a `range_type` value that doesn't work with the `COMEDI_RANGEINFO` ioctl. It will either get the range table for the subdevice index modulo 16, or will fail with `-EINVAL`. To fix this, always use bits 31 to 24 of the `range_type` value to hold the subdevice index (assumed to be no more than 255). This affects the `COMEDI_CHANINFO` and `COMEDI_RANGEINFO` ioctls. There should not be anything in user-space that depends on the old, broken usage, although it may now see different values in bits 31 to 28 of the `range_type` values reported by the `COMEDI_CHANINFO` ioctl for subdevices that have per-channel subdevices. User-space should not be trying to decode bits 31 to 16 of the `range_type` values anyway. Fixes: ed9eccbe8970 ("Staging: add comedi core") Cc: stable@vger.kernel.org #5.17+ Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20251203162438.176841-1-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/comedi_fops.c | 2 +- drivers/comedi/range.c | 2 +- include/uapi/linux/comedi.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/comedi/comedi_fops.c b/drivers/comedi/comedi_fops.c index 657c98cd723e..2c3eb9e89571 100644 --- a/drivers/comedi/comedi_fops.c +++ b/drivers/comedi/comedi_fops.c @@ -1155,7 +1155,7 @@ static int do_chaninfo_ioctl(struct comedi_device *dev, for (i = 0; i < s->n_chan; i++) { int x; - x = (dev->minor << 28) | (it->subdev << 24) | (i << 16) | + x = (it->subdev << 24) | (i << 16) | (s->range_table_list[i]->length); if (put_user(x, it->rangelist + i)) return -EFAULT; diff --git a/drivers/comedi/range.c b/drivers/comedi/range.c index 8f43cf88d784..5b8f662365e3 100644 --- a/drivers/comedi/range.c +++ b/drivers/comedi/range.c @@ -52,7 +52,7 @@ int do_rangeinfo_ioctl(struct comedi_device *dev, const struct comedi_lrange *lr; struct comedi_subdevice *s; - subd = (it->range_type >> 24) & 0xf; + subd = (it->range_type >> 24) & 0xff; chan = (it->range_type >> 16) & 0xff; if (!dev->attached) diff --git a/include/uapi/linux/comedi.h b/include/uapi/linux/comedi.h index 7314e5ee0a1e..798ec9a39e12 100644 --- a/include/uapi/linux/comedi.h +++ b/include/uapi/linux/comedi.h @@ -640,7 +640,7 @@ struct comedi_chaninfo { /** * struct comedi_rangeinfo - used to retrieve the range table for a channel - * @range_type: Encodes subdevice index (bits 27:24), channel index + * @range_type: Encodes subdevice index (bits 31:24), channel index * (bits 23:16) and range table length (bits 15:0). * @range_ptr: Pointer to array of @struct comedi_krange to be filled * in with the range table for the channel or subdevice. -- cgit v1.2.3 From 2c28769a51deb6022d7fbd499987e237a01dd63a Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 14 Jan 2026 22:03:23 +0000 Subject: rxrpc: Fix recvmsg() unconditional requeue If rxrpc_recvmsg() fails because MSG_DONTWAIT was specified but the call at the front of the recvmsg queue already has its mutex locked, it requeues the call - whether or not the call is already queued. The call may be on the queue because MSG_PEEK was also passed and so the call was not dequeued or because the I/O thread requeued it. The unconditional requeue may then corrupt the recvmsg queue, leading to things like UAFs or refcount underruns. Fix this by only requeuing the call if it isn't already on the queue - and moving it to the front if it is already queued. If we don't queue it, we have to put the ref we obtained by dequeuing it. Also, MSG_PEEK doesn't dequeue the call so shouldn't call rxrpc_notify_socket() for the call if we didn't use up all the data on the queue, so fix that also. Fixes: 540b1c48c37a ("rxrpc: Fix deadlock between call creation and sendmsg/recvmsg") Reported-by: Faith Reported-by: Pumpkin Chang Signed-off-by: David Howells Acked-by: Marc Dionne cc: Nir Ohfeld cc: Willy Tarreau cc: Simon Horman cc: linux-afs@lists.infradead.org cc: stable@kernel.org Link: https://patch.msgid.link/95163.1768428203@warthog.procyon.org.uk Signed-off-by: Jakub Kicinski --- include/trace/events/rxrpc.h | 4 ++++ net/rxrpc/recvmsg.c | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/trace/events/rxrpc.h b/include/trace/events/rxrpc.h index de6f6d25767c..869f97c9bf73 100644 --- a/include/trace/events/rxrpc.h +++ b/include/trace/events/rxrpc.h @@ -322,6 +322,7 @@ EM(rxrpc_call_put_kernel, "PUT kernel ") \ EM(rxrpc_call_put_poke, "PUT poke ") \ EM(rxrpc_call_put_recvmsg, "PUT recvmsg ") \ + EM(rxrpc_call_put_recvmsg_peek_nowait, "PUT peek-nwt") \ EM(rxrpc_call_put_release_recvmsg_q, "PUT rls-rcmq") \ EM(rxrpc_call_put_release_sock, "PUT rls-sock") \ EM(rxrpc_call_put_release_sock_tba, "PUT rls-sk-a") \ @@ -340,6 +341,9 @@ EM(rxrpc_call_see_input, "SEE input ") \ EM(rxrpc_call_see_notify_released, "SEE nfy-rlsd") \ EM(rxrpc_call_see_recvmsg, "SEE recvmsg ") \ + EM(rxrpc_call_see_recvmsg_requeue, "SEE recv-rqu") \ + EM(rxrpc_call_see_recvmsg_requeue_first, "SEE recv-rqF") \ + EM(rxrpc_call_see_recvmsg_requeue_move, "SEE recv-rqM") \ EM(rxrpc_call_see_release, "SEE release ") \ EM(rxrpc_call_see_userid_exists, "SEE u-exists") \ EM(rxrpc_call_see_waiting_call, "SEE q-conn ") \ diff --git a/net/rxrpc/recvmsg.c b/net/rxrpc/recvmsg.c index 7fa7e77f6bb9..e1f7513a46db 100644 --- a/net/rxrpc/recvmsg.c +++ b/net/rxrpc/recvmsg.c @@ -518,7 +518,8 @@ try_again: if (rxrpc_call_has_failed(call)) goto call_failed; - if (!skb_queue_empty(&call->recvmsg_queue)) + if (!(flags & MSG_PEEK) && + !skb_queue_empty(&call->recvmsg_queue)) rxrpc_notify_socket(call); goto not_yet_complete; @@ -549,11 +550,21 @@ error_unlock_call: error_requeue_call: if (!(flags & MSG_PEEK)) { spin_lock_irq(&rx->recvmsg_lock); - list_add(&call->recvmsg_link, &rx->recvmsg_q); - spin_unlock_irq(&rx->recvmsg_lock); + if (list_empty(&call->recvmsg_link)) { + list_add(&call->recvmsg_link, &rx->recvmsg_q); + rxrpc_see_call(call, rxrpc_call_see_recvmsg_requeue); + spin_unlock_irq(&rx->recvmsg_lock); + } else if (list_is_first(&call->recvmsg_link, &rx->recvmsg_q)) { + spin_unlock_irq(&rx->recvmsg_lock); + rxrpc_put_call(call, rxrpc_call_see_recvmsg_requeue_first); + } else { + list_move(&call->recvmsg_link, &rx->recvmsg_q); + spin_unlock_irq(&rx->recvmsg_lock); + rxrpc_put_call(call, rxrpc_call_see_recvmsg_requeue_move); + } trace_rxrpc_recvmsg(call_debug_id, rxrpc_recvmsg_requeue, 0); } else { - rxrpc_put_call(call, rxrpc_call_put_recvmsg); + rxrpc_put_call(call, rxrpc_call_put_recvmsg_peek_nowait); } error_no_call: release_sock(&rx->sk); -- cgit v1.2.3 From 6ac433f8b2590b09ca00863d218665729ac985f7 Mon Sep 17 00:00:00 2001 From: Mathieu Desnoyers Date: Wed, 24 Dec 2025 12:33:57 -0500 Subject: mm: rename cpu_bitmap field to flexible_array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cpu_bitmap flexible array now contains more than just the cpu_bitmap. In preparation for changing the static mm_struct definitions to cover for the additional space required, change the cpu_bitmap type from "unsigned long" to "char", require an unsigned long alignment of the flexible array, and rename the field from "cpu_bitmap" to "flexible_array". Introduce the MM_STRUCT_FLEXIBLE_ARRAY_INIT macro to statically initialize the flexible array. This covers the init_mm and efi_mm static definitions. This is a preparation step for fixing the missing mm_cid size for static mm_struct definitions. Link: https://lkml.kernel.org/r/20251224173358.647691-3-mathieu.desnoyers@efficios.com Fixes: af7f588d8f73 ("sched: Introduce per-memory-map concurrency ID") Signed-off-by: Mathieu Desnoyers Reviewed-by: Thomas Gleixner Cc: Mark Brown Cc: Aboorva Devarajan Cc: Al Viro Cc: Baolin Wang Cc: Christan König Cc: Christian Brauner Cc: Christoph Lameter Cc: David Hildenbrand Cc: David Rientjes Cc: Dennis Zhou Cc: Johannes Weiner Cc: "Liam R . Howlett" Cc: Lorenzo Stoakes Cc: Martin Liu Cc: Masami Hiramatsu Cc: Mateusz Guzik Cc: Matthew Wilcox Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: "Paul E. McKenney" Cc: Roman Gushchin Cc: SeongJae Park Cc: Shakeel Butt Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Sweet Tea Dorminy Cc: Tejun Heo Cc: Vlastimil Babka Cc: Wei Yang Cc: Yu Zhao Cc: Peter Zijlstra (Intel) Cc: Signed-off-by: Andrew Morton --- drivers/firmware/efi/efi.c | 2 +- include/linux/mm_types.h | 13 +++++++++---- mm/init-mm.c | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index f5ff6e84a9b7..17b5f3415465 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -74,10 +74,10 @@ struct mm_struct efi_mm = { .page_table_lock = __SPIN_LOCK_UNLOCKED(efi_mm.page_table_lock), .mmlist = LIST_HEAD_INIT(efi_mm.mmlist), .user_ns = &init_user_ns, - .cpu_bitmap = { [BITS_TO_LONGS(NR_CPUS)] = 0}, #ifdef CONFIG_SCHED_MM_CID .mm_cid.lock = __RAW_SPIN_LOCK_UNLOCKED(efi_mm.mm_cid.lock), #endif + .flexible_array = MM_STRUCT_FLEXIBLE_ARRAY_INIT, }; struct workqueue_struct *efi_rts_wq; diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index 42af2292951d..110b319a2ffb 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -1329,7 +1329,7 @@ struct mm_struct { * The mm_cpumask needs to be at the end of mm_struct, because it * is dynamically sized based on nr_cpu_ids. */ - unsigned long cpu_bitmap[]; + char flexible_array[] __aligned(__alignof__(unsigned long)); }; /* Copy value to the first system word of mm flags, non-atomically. */ @@ -1366,19 +1366,24 @@ static inline void __mm_flags_set_mask_bits_word(struct mm_struct *mm, MT_FLAGS_USE_RCU) extern struct mm_struct init_mm; +#define MM_STRUCT_FLEXIBLE_ARRAY_INIT \ +{ \ + [0 ... sizeof(cpumask_t)-1] = 0 \ +} + /* Pointer magic because the dynamic array size confuses some compilers. */ static inline void mm_init_cpumask(struct mm_struct *mm) { unsigned long cpu_bitmap = (unsigned long)mm; - cpu_bitmap += offsetof(struct mm_struct, cpu_bitmap); + cpu_bitmap += offsetof(struct mm_struct, flexible_array); cpumask_clear((struct cpumask *)cpu_bitmap); } /* Future-safe accessor for struct mm_struct's cpu_vm_mask. */ static inline cpumask_t *mm_cpumask(struct mm_struct *mm) { - return (struct cpumask *)&mm->cpu_bitmap; + return (struct cpumask *)&mm->flexible_array; } #ifdef CONFIG_LRU_GEN @@ -1469,7 +1474,7 @@ static inline cpumask_t *mm_cpus_allowed(struct mm_struct *mm) { unsigned long bitmap = (unsigned long)mm; - bitmap += offsetof(struct mm_struct, cpu_bitmap); + bitmap += offsetof(struct mm_struct, flexible_array); /* Skip cpu_bitmap */ bitmap += cpumask_size(); return (struct cpumask *)bitmap; diff --git a/mm/init-mm.c b/mm/init-mm.c index a514f8ce47e3..c5556bb9d5f0 100644 --- a/mm/init-mm.c +++ b/mm/init-mm.c @@ -47,7 +47,7 @@ struct mm_struct init_mm = { #ifdef CONFIG_SCHED_MM_CID .mm_cid.lock = __RAW_SPIN_LOCK_UNLOCKED(init_mm.mm_cid.lock), #endif - .cpu_bitmap = CPU_BITS_NONE, + .flexible_array = MM_STRUCT_FLEXIBLE_ARRAY_INIT, INIT_MM_CONTEXT(init_mm) }; -- cgit v1.2.3 From be31340a4cc259340044b7fc4f7e97f58c74ee8e Mon Sep 17 00:00:00 2001 From: Mathieu Desnoyers Date: Wed, 24 Dec 2025 12:33:58 -0500 Subject: mm: take into account mm_cid size for mm_struct static definitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both init_mm and efi_mm static definitions need to make room for the 2 mm_cid cpumasks. This fixes possible out-of-bounds accesses to init_mm and efi_mm. Add a space between # and define for the mm_alloc_cid() definition to make it consistent with the coding style used in the rest of this header file. Link: https://lkml.kernel.org/r/20251224173358.647691-4-mathieu.desnoyers@efficios.com Fixes: af7f588d8f73 ("sched: Introduce per-memory-map concurrency ID") Signed-off-by: Mathieu Desnoyers Reviewed-by: Thomas Gleixner Cc: Mark Brown Cc: Aboorva Devarajan Cc: Al Viro Cc: Baolin Wang Cc: Christan König Cc: Christian Brauner Cc: Christoph Lameter Cc: David Hildenbrand Cc: David Rientjes Cc: Dennis Zhou Cc: Johannes Weiner Cc: "Liam R . Howlett" Cc: Lorenzo Stoakes Cc: Martin Liu Cc: Masami Hiramatsu Cc: Mateusz Guzik Cc: Matthew Wilcox Cc: Miaohe Lin Cc: Michal Hocko Cc: Mike Rapoport Cc: "Paul E. McKenney" Cc: Roman Gushchin Cc: SeongJae Park Cc: Shakeel Butt Cc: Steven Rostedt Cc: Suren Baghdasaryan Cc: Sweet Tea Dorminy Cc: Tejun Heo Cc: Vlastimil Babka Cc: Wei Yang Cc: Yu Zhao Cc: Peter Zijlstra (Intel) Cc: Signed-off-by: Andrew Morton --- include/linux/mm_types.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index 110b319a2ffb..aa4639888f89 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -1368,7 +1368,7 @@ extern struct mm_struct init_mm; #define MM_STRUCT_FLEXIBLE_ARRAY_INIT \ { \ - [0 ... sizeof(cpumask_t)-1] = 0 \ + [0 ... sizeof(cpumask_t) + MM_CID_STATIC_SIZE - 1] = 0 \ } /* Pointer magic because the dynamic array size confuses some compilers. */ @@ -1500,7 +1500,7 @@ static inline int mm_alloc_cid_noprof(struct mm_struct *mm, struct task_struct * mm_init_cid(mm, p); return 0; } -#define mm_alloc_cid(...) alloc_hooks(mm_alloc_cid_noprof(__VA_ARGS__)) +# define mm_alloc_cid(...) alloc_hooks(mm_alloc_cid_noprof(__VA_ARGS__)) static inline void mm_destroy_cid(struct mm_struct *mm) { @@ -1514,6 +1514,8 @@ static inline unsigned int mm_cid_size(void) return cpumask_size() + bitmap_size(num_possible_cpus()); } +/* Use 2 * NR_CPUS as worse case for static allocation. */ +# define MM_CID_STATIC_SIZE (2 * sizeof(cpumask_t)) #else /* CONFIG_SCHED_MM_CID */ static inline void mm_init_cid(struct mm_struct *mm, struct task_struct *p) { } static inline int mm_alloc_cid(struct mm_struct *mm, struct task_struct *p) { return 0; } @@ -1522,6 +1524,7 @@ static inline unsigned int mm_cid_size(void) { return 0; } +# define MM_CID_STATIC_SIZE 0 #endif /* CONFIG_SCHED_MM_CID */ struct mmu_gather; -- cgit v1.2.3 From f9a49aa302a05e91ca01f69031cb79a0ea33031f Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Mon, 5 Jan 2026 13:17:27 -0800 Subject: fs/writeback: skip AS_NO_DATA_INTEGRITY mappings in wait_sb_inodes() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Above the while() loop in wait_sb_inodes(), we document that we must wait for all pages under writeback for data integrity. Consequently, if a mapping, like fuse, traditionally does not have data integrity semantics, there is no need to wait at all; we can simply skip these inodes. This restores fuse back to prior behavior where syncs are no-ops. This fixes a user regression where if a system is running a faulty fuse server that does not reply to issued write requests, this causes wait_sb_inodes() to wait forever. Link: https://lkml.kernel.org/r/20260105211737.4105620-2-joannelkoong@gmail.com Fixes: 0c58a97f919c ("fuse: remove tmp folio for writebacks and internal rb tree") Signed-off-by: Joanne Koong Reported-by: Athul Krishna Reported-by: J. Neuschäfer Reviewed-by: Bernd Schubert Tested-by: J. Neuschäfer Cc: Alexander Viro Cc: Bernd Schubert Cc: Bonaccorso Salvatore Cc: Christian Brauner Cc: David Hildenbrand Cc: Jan Kara Cc: "Liam R. Howlett" Cc: Lorenzo Stoakes Cc: "Matthew Wilcox (Oracle)" Cc: Michal Hocko Cc: Mike Rapoport Cc: Miklos Szeredi Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Signed-off-by: Andrew Morton --- fs/fs-writeback.c | 7 ++++++- fs/fuse/file.c | 4 +++- include/linux/pagemap.h | 11 +++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index 6800886c4d10..baa2f2141146 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -2750,8 +2750,13 @@ static void wait_sb_inodes(struct super_block *sb) * The mapping can appear untagged while still on-list since we * do not have the mapping lock. Skip it here, wb completion * will remove it. + * + * If the mapping does not have data integrity semantics, + * there's no need to wait for the writeout to complete, as the + * mapping cannot guarantee that data is persistently stored. */ - if (!mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK)) + if (!mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK) || + mapping_no_data_integrity(mapping)) continue; spin_unlock_irq(&sb->s_inode_wblist_lock); diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 01bc894e9c2b..3b2a171e652f 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -3200,8 +3200,10 @@ void fuse_init_file_inode(struct inode *inode, unsigned int flags) inode->i_fop = &fuse_file_operations; inode->i_data.a_ops = &fuse_file_aops; - if (fc->writeback_cache) + if (fc->writeback_cache) { mapping_set_writeback_may_deadlock_on_reclaim(&inode->i_data); + mapping_set_no_data_integrity(&inode->i_data); + } INIT_LIST_HEAD(&fi->write_files); INIT_LIST_HEAD(&fi->queued_writes); diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h index 31a848485ad9..ec442af3f886 100644 --- a/include/linux/pagemap.h +++ b/include/linux/pagemap.h @@ -210,6 +210,7 @@ enum mapping_flags { AS_WRITEBACK_MAY_DEADLOCK_ON_RECLAIM = 9, AS_KERNEL_FILE = 10, /* mapping for a fake kernel file that shouldn't account usage to user cgroups */ + AS_NO_DATA_INTEGRITY = 11, /* no data integrity guarantees */ /* Bits 16-25 are used for FOLIO_ORDER */ AS_FOLIO_ORDER_BITS = 5, AS_FOLIO_ORDER_MIN = 16, @@ -345,6 +346,16 @@ static inline bool mapping_writeback_may_deadlock_on_reclaim(const struct addres return test_bit(AS_WRITEBACK_MAY_DEADLOCK_ON_RECLAIM, &mapping->flags); } +static inline void mapping_set_no_data_integrity(struct address_space *mapping) +{ + set_bit(AS_NO_DATA_INTEGRITY, &mapping->flags); +} + +static inline bool mapping_no_data_integrity(const struct address_space *mapping) +{ + return test_bit(AS_NO_DATA_INTEGRITY, &mapping->flags); +} + static inline gfp_t mapping_gfp_mask(const struct address_space *mapping) { return mapping->gfp_mask; -- cgit v1.2.3 From 50b359896fe55d0443ed550e1fabba71d242031a Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Sun, 18 Jan 2026 09:51:15 +0200 Subject: wifi: cfg80211: ignore link disabled flag from userspace When the AP has an advertised TID to Link Mapping (TTLM) it shall include the element in the association response. As such, when this element is present it needs to be used for the currently dormant links. See Draft P802.11REVmf_D1.0 section 35.3.7.2.3 ("Negotiation of TTLM") for the details. The flag is also not usable in case userspace wants to specify a negotiated TTLM during association. Note that for the link reconfiguration case, mac80211 did not use the information. Draft P802.11REVmf_D1.0 states in section 35.3.6.4 ("Link reconfiguration to the setup links) that we "shall operate with all the TIDs mapped to the newly added links ..." All this means that the flag is not needed. The implementation should parse the information from the association response. Signed-off-by: Benjamin Berg Reviewed-by: Johannes Berg Reviewed-by: Ilan Peer Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260118093904.754e057896a5.Ifd06f5ef839a93bfd54d0593dc932870f95f3242@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 3 --- include/uapi/linux/nl80211.h | 5 +++-- net/wireless/nl80211.c | 10 ---------- 3 files changed, 3 insertions(+), 15 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 899f267b7cf9..2900202588a5 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -3221,8 +3221,6 @@ struct cfg80211_auth_request { * if this is %NULL for a link, that link is not requested * @elems: extra elements for the per-STA profile for this link * @elems_len: length of the elements - * @disabled: If set this link should be included during association etc. but it - * should not be used until enabled by the AP MLD. * @error: per-link error code, must be <= 0. If there is an error, then the * operation as a whole must fail. */ @@ -3230,7 +3228,6 @@ struct cfg80211_assoc_link { struct cfg80211_bss *bss; const u8 *elems; size_t elems_len; - bool disabled; int error; }; diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 8134f10e4e6c..8433bac48112 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -2880,8 +2880,9 @@ enum nl80211_commands { * index. If the userspace includes more RNR elements than number of * MBSSID elements then these will be added in every EMA beacon. * - * @NL80211_ATTR_MLO_LINK_DISABLED: Flag attribute indicating that the link is - * disabled. + * @NL80211_ATTR_MLO_LINK_DISABLED: Unused. It was used to indicate that a link + * is disabled during association. However, the AP will send the + * information by including a TTLM in the association response. * * @NL80211_ATTR_BSS_DUMP_INCLUDE_USE_DATA: Include BSS usage data, i.e. * include BSSes that can only be used in restricted scenarios and/or diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index c961cd42a832..03efd45c007f 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -12241,9 +12241,6 @@ static int nl80211_process_links(struct cfg80211_registered_device *rdev, return -EINVAL; } } - - links[link_id].disabled = - nla_get_flag(attrs[NL80211_ATTR_MLO_LINK_DISABLED]); } return 0; @@ -12423,13 +12420,6 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info) goto free; } - if (req.links[req.link_id].disabled) { - GENL_SET_ERR_MSG(info, - "cannot have assoc link disabled"); - err = -EINVAL; - goto free; - } - if (info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS]) req.ext_mld_capa_ops = nla_get_u16(info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS]); -- cgit v1.2.3 From ca1a47cd3f5f4c46ca188b1c9a27af87d1ab2216 Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Red Hat)" Date: Tue, 23 Dec 2025 22:40:34 +0100 Subject: mm/hugetlb: fix hugetlb_pmd_shared() Patch series "mm/hugetlb: fixes for PMD table sharing (incl. using mmu_gather)", v3. One functional fix, one performance regression fix, and two related comment fixes. I cleaned up my prototype I recently shared [1] for the performance fix, deferring most of the cleanups I had in the prototype to a later point. While doing that I identified the other things. The goal of this patch set is to be backported to stable trees "fairly" easily. At least patch #1 and #4. Patch #1 fixes hugetlb_pmd_shared() not detecting any sharing Patch #2 + #3 are simple comment fixes that patch #4 interacts with. Patch #4 is a fix for the reported performance regression due to excessive IPI broadcasts during fork()+exit(). The last patch is all about TLB flushes, IPIs and mmu_gather. Read: complicated There are plenty of cleanups in the future to be had + one reasonable optimization on x86. But that's all out of scope for this series. Runtime tested, with a focus on fixing the performance regression using the original reproducer [2] on x86. This patch (of 4): We switched from (wrongly) using the page count to an independent shared count. Now, shared page tables have a refcount of 1 (excluding speculative references) and instead use ptdesc->pt_share_count to identify sharing. We didn't convert hugetlb_pmd_shared(), so right now, we would never detect a shared PMD table as such, because sharing/unsharing no longer touches the refcount of a PMD table. Page migration, like mbind() or migrate_pages() would allow for migrating folios mapped into such shared PMD tables, even though the folios are not exclusive. In smaps we would account them as "private" although they are "shared", and we would be wrongly setting the PM_MMAP_EXCLUSIVE in the pagemap interface. Fix it by properly using ptdesc_pmd_is_shared() in hugetlb_pmd_shared(). Link: https://lkml.kernel.org/r/20251223214037.580860-1-david@kernel.org Link: https://lkml.kernel.org/r/20251223214037.580860-2-david@kernel.org Link: https://lore.kernel.org/all/8cab934d-4a56-44aa-b641-bfd7e23bd673@kernel.org/ [1] Link: https://lore.kernel.org/all/8cab934d-4a56-44aa-b641-bfd7e23bd673@kernel.org/ [2] Fixes: 59d9094df3d7 ("mm: hugetlb: independent PMD page table shared count") Signed-off-by: David Hildenbrand (Red Hat) Reviewed-by: Rik van Riel Reviewed-by: Lance Yang Tested-by: Lance Yang Reviewed-by: Harry Yoo Tested-by: Laurence Oberman Reviewed-by: Lorenzo Stoakes Acked-by: Oscar Salvador Cc: Liu Shixin Cc: Uschakow, Stanislav" Cc: Signed-off-by: Andrew Morton --- include/linux/hugetlb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index 019a1c5281e4..03c8725efa28 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h @@ -1326,7 +1326,7 @@ static inline __init void hugetlb_cma_reserve(int order) #ifdef CONFIG_HUGETLB_PMD_PAGE_TABLE_SHARING static inline bool hugetlb_pmd_shared(pte_t *pte) { - return page_count(virt_to_page(pte)) > 1; + return ptdesc_pmd_is_shared(virt_to_ptdesc(pte)); } #else static inline bool hugetlb_pmd_shared(pte_t *pte) -- cgit v1.2.3 From 8ce720d5bd91e9dc16db3604aa4b1bf76770a9a1 Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Red Hat)" Date: Tue, 23 Dec 2025 22:40:37 +0100 Subject: mm/hugetlb: fix excessive IPI broadcasts when unsharing PMD tables using mmu_gather As reported, ever since commit 1013af4f585f ("mm/hugetlb: fix huge_pmd_unshare() vs GUP-fast race") we can end up in some situations where we perform so many IPI broadcasts when unsharing hugetlb PMD page tables that it severely regresses some workloads. In particular, when we fork()+exit(), or when we munmap() a large area backed by many shared PMD tables, we perform one IPI broadcast per unshared PMD table. There are two optimizations to be had: (1) When we process (unshare) multiple such PMD tables, such as during exit(), it is sufficient to send a single IPI broadcast (as long as we respect locking rules) instead of one per PMD table. Locking prevents that any of these PMD tables could get reused before we drop the lock. (2) When we are not the last sharer (> 2 users including us), there is no need to send the IPI broadcast. The shared PMD tables cannot become exclusive (fully unshared) before an IPI will be broadcasted by the last sharer. Concurrent GUP-fast could walk into a PMD table just before we unshared it. It could then succeed in grabbing a page from the shared page table even after munmap() etc succeeded (and supressed an IPI). But there is not difference compared to GUP-fast just sleeping for a while after grabbing the page and re-enabling IRQs. Most importantly, GUP-fast will never walk into page tables that are no-longer shared, because the last sharer will issue an IPI broadcast. (if ever required, checking whether the PUD changed in GUP-fast after grabbing the page like we do in the PTE case could handle this) So let's rework PMD sharing TLB flushing + IPI sync to use the mmu_gather infrastructure so we can implement these optimizations and demystify the code at least a bit. Extend the mmu_gather infrastructure to be able to deal with our special hugetlb PMD table sharing implementation. To make initialization of the mmu_gather easier when working on a single VMA (in particular, when dealing with hugetlb), provide tlb_gather_mmu_vma(). We'll consolidate the handling for (full) unsharing of PMD tables in tlb_unshare_pmd_ptdesc() and tlb_flush_unshared_tables(), and track in "struct mmu_gather" whether we had (full) unsharing of PMD tables. Because locking is very special (concurrent unsharing+reuse must be prevented), we disallow deferring flushing to tlb_finish_mmu() and instead require an explicit earlier call to tlb_flush_unshared_tables(). From hugetlb code, we call huge_pmd_unshare_flush() where we make sure that the expected lock protecting us from concurrent unsharing+reuse is still held. Check with a VM_WARN_ON_ONCE() in tlb_finish_mmu() that tlb_flush_unshared_tables() was properly called earlier. Document it all properly. Notes about tlb_remove_table_sync_one() interaction with unsharing: There are two fairly tricky things: (1) tlb_remove_table_sync_one() is a NOP on architectures without CONFIG_MMU_GATHER_RCU_TABLE_FREE. Here, the assumption is that the previous TLB flush would send an IPI to all relevant CPUs. Careful: some architectures like x86 only send IPIs to all relevant CPUs when tlb->freed_tables is set. The relevant architectures should be selecting MMU_GATHER_RCU_TABLE_FREE, but x86 might not do that in stable kernels and it might have been problematic before this patch. Also, the arch flushing behavior (independent of IPIs) is different when tlb->freed_tables is set. Do we have to enlighten them to also take care of tlb->unshared_tables? So far we didn't care, so hopefully we are fine. Of course, we could be setting tlb->freed_tables as well, but that might then unnecessarily flush too much, because the semantics of tlb->freed_tables are a bit fuzzy. This patch changes nothing in this regard. (2) tlb_remove_table_sync_one() is not a NOP on architectures with CONFIG_MMU_GATHER_RCU_TABLE_FREE that actually don't need a sync. Take x86 as an example: in the common case (!pv, !X86_FEATURE_INVLPGB) we still issue IPIs during TLB flushes and don't actually need the second tlb_remove_table_sync_one(). This optimized can be implemented on top of this, by checking e.g., in tlb_remove_table_sync_one() whether we really need IPIs. But as described in (1), it really must honor tlb->freed_tables then to send IPIs to all relevant CPUs. Notes on TLB flushing changes: (1) Flushing for non-shared PMD tables We're converting from flush_hugetlb_tlb_range() to tlb_remove_huge_tlb_entry(). Given that we properly initialize the MMU gather in tlb_gather_mmu_vma() to be hugetlb aware, similar to __unmap_hugepage_range(), that should be fine. (2) Flushing for shared PMD tables We're converting from various things (flush_hugetlb_tlb_range(), tlb_flush_pmd_range(), flush_tlb_range()) to tlb_flush_pmd_range(). tlb_flush_pmd_range() achieves the same that tlb_remove_huge_tlb_entry() would achieve in these scenarios. Note that tlb_remove_huge_tlb_entry() also calls __tlb_remove_tlb_entry(), however that is only implemented on powerpc, which does not support PMD table sharing. Similar to (1), tlb_gather_mmu_vma() should make sure that TLB flushing keeps on working as expected. Further, note that the ptdesc_pmd_pts_dec() in huge_pmd_share() is not a concern, as we are holding the i_mmap_lock the whole time, preventing concurrent unsharing. That ptdesc_pmd_pts_dec() usage will be removed separately as a cleanup later. There are plenty more cleanups to be had, but they have to wait until this is fixed. [david@kernel.org: fix kerneldoc] Link: https://lkml.kernel.org/r/f223dd74-331c-412d-93fc-69e360a5006c@kernel.org Link: https://lkml.kernel.org/r/20251223214037.580860-5-david@kernel.org Fixes: 1013af4f585f ("mm/hugetlb: fix huge_pmd_unshare() vs GUP-fast race") Signed-off-by: David Hildenbrand (Red Hat) Reported-by: Uschakow, Stanislav" Closes: https://lore.kernel.org/all/4d3878531c76479d9f8ca9789dc6485d@amazon.de/ Tested-by: Laurence Oberman Acked-by: Harry Yoo Reviewed-by: Lorenzo Stoakes Cc: Lance Yang Cc: Liu Shixin Cc: Oscar Salvador Cc: Rik van Riel Cc: Signed-off-by: Andrew Morton --- include/asm-generic/tlb.h | 77 ++++++++++++++++++++++++++++- include/linux/hugetlb.h | 15 ++++-- include/linux/mm_types.h | 1 + mm/hugetlb.c | 123 +++++++++++++++++++++++++++------------------- mm/mmu_gather.c | 33 +++++++++++++ mm/rmap.c | 25 +++++++--- 6 files changed, 208 insertions(+), 66 deletions(-) (limited to 'include') diff --git a/include/asm-generic/tlb.h b/include/asm-generic/tlb.h index 1fff717cae51..4d679d2a206b 100644 --- a/include/asm-generic/tlb.h +++ b/include/asm-generic/tlb.h @@ -46,7 +46,8 @@ * * The mmu_gather API consists of: * - * - tlb_gather_mmu() / tlb_gather_mmu_fullmm() / tlb_finish_mmu() + * - tlb_gather_mmu() / tlb_gather_mmu_fullmm() / tlb_gather_mmu_vma() / + * tlb_finish_mmu() * * start and finish a mmu_gather * @@ -364,6 +365,20 @@ struct mmu_gather { unsigned int vma_huge : 1; unsigned int vma_pfn : 1; + /* + * Did we unshare (unmap) any shared page tables? For now only + * used for hugetlb PMD table sharing. + */ + unsigned int unshared_tables : 1; + + /* + * Did we unshare any page tables such that they are now exclusive + * and could get reused+modified by the new owner? When setting this + * flag, "unshared_tables" will be set as well. For now only used + * for hugetlb PMD table sharing. + */ + unsigned int fully_unshared_tables : 1; + unsigned int batch_count; #ifndef CONFIG_MMU_GATHER_NO_GATHER @@ -400,6 +415,7 @@ static inline void __tlb_reset_range(struct mmu_gather *tlb) tlb->cleared_pmds = 0; tlb->cleared_puds = 0; tlb->cleared_p4ds = 0; + tlb->unshared_tables = 0; /* * Do not reset mmu_gather::vma_* fields here, we do not * call into tlb_start_vma() again to set them if there is an @@ -484,7 +500,7 @@ static inline void tlb_flush_mmu_tlbonly(struct mmu_gather *tlb) * these bits. */ if (!(tlb->freed_tables || tlb->cleared_ptes || tlb->cleared_pmds || - tlb->cleared_puds || tlb->cleared_p4ds)) + tlb->cleared_puds || tlb->cleared_p4ds || tlb->unshared_tables)) return; tlb_flush(tlb); @@ -773,6 +789,63 @@ static inline bool huge_pmd_needs_flush(pmd_t oldpmd, pmd_t newpmd) } #endif +#ifdef CONFIG_HUGETLB_PMD_PAGE_TABLE_SHARING +static inline void tlb_unshare_pmd_ptdesc(struct mmu_gather *tlb, struct ptdesc *pt, + unsigned long addr) +{ + /* + * The caller must make sure that concurrent unsharing + exclusive + * reuse is impossible until tlb_flush_unshared_tables() was called. + */ + VM_WARN_ON_ONCE(!ptdesc_pmd_is_shared(pt)); + ptdesc_pmd_pts_dec(pt); + + /* Clearing a PUD pointing at a PMD table with PMD leaves. */ + tlb_flush_pmd_range(tlb, addr & PUD_MASK, PUD_SIZE); + + /* + * If the page table is now exclusively owned, we fully unshared + * a page table. + */ + if (!ptdesc_pmd_is_shared(pt)) + tlb->fully_unshared_tables = true; + tlb->unshared_tables = true; +} + +static inline void tlb_flush_unshared_tables(struct mmu_gather *tlb) +{ + /* + * As soon as the caller drops locks to allow for reuse of + * previously-shared tables, these tables could get modified and + * even reused outside of hugetlb context, so we have to make sure that + * any page table walkers (incl. TLB, GUP-fast) are aware of that + * change. + * + * Even if we are not fully unsharing a PMD table, we must + * flush the TLB for the unsharer now. + */ + if (tlb->unshared_tables) + tlb_flush_mmu_tlbonly(tlb); + + /* + * Similarly, we must make sure that concurrent GUP-fast will not + * walk previously-shared page tables that are getting modified+reused + * elsewhere. So broadcast an IPI to wait for any concurrent GUP-fast. + * + * We only perform this when we are the last sharer of a page table, + * as the IPI will reach all CPUs: any GUP-fast. + * + * Note that on configs where tlb_remove_table_sync_one() is a NOP, + * the expectation is that the tlb_flush_mmu_tlbonly() would have issued + * required IPIs already for us. + */ + if (tlb->fully_unshared_tables) { + tlb_remove_table_sync_one(); + tlb->fully_unshared_tables = false; + } +} +#endif /* CONFIG_HUGETLB_PMD_PAGE_TABLE_SHARING */ + #endif /* CONFIG_MMU */ #endif /* _ASM_GENERIC__TLB_H */ diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index 03c8725efa28..e51b8ef0cebd 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h @@ -240,8 +240,9 @@ pte_t *huge_pte_alloc(struct mm_struct *mm, struct vm_area_struct *vma, pte_t *huge_pte_offset(struct mm_struct *mm, unsigned long addr, unsigned long sz); unsigned long hugetlb_mask_last_page(struct hstate *h); -int huge_pmd_unshare(struct mm_struct *mm, struct vm_area_struct *vma, - unsigned long addr, pte_t *ptep); +int huge_pmd_unshare(struct mmu_gather *tlb, struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep); +void huge_pmd_unshare_flush(struct mmu_gather *tlb, struct vm_area_struct *vma); void adjust_range_if_pmd_sharing_possible(struct vm_area_struct *vma, unsigned long *start, unsigned long *end); @@ -300,13 +301,17 @@ static inline struct address_space *hugetlb_folio_mapping_lock_write( return NULL; } -static inline int huge_pmd_unshare(struct mm_struct *mm, - struct vm_area_struct *vma, - unsigned long addr, pte_t *ptep) +static inline int huge_pmd_unshare(struct mmu_gather *tlb, + struct vm_area_struct *vma, unsigned long addr, pte_t *ptep) { return 0; } +static inline void huge_pmd_unshare_flush(struct mmu_gather *tlb, + struct vm_area_struct *vma) +{ +} + static inline void adjust_range_if_pmd_sharing_possible( struct vm_area_struct *vma, unsigned long *start, unsigned long *end) diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index aa4639888f89..78950eb8926d 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -1530,6 +1530,7 @@ static inline unsigned int mm_cid_size(void) struct mmu_gather; extern void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm); extern void tlb_gather_mmu_fullmm(struct mmu_gather *tlb, struct mm_struct *mm); +void tlb_gather_mmu_vma(struct mmu_gather *tlb, struct vm_area_struct *vma); extern void tlb_finish_mmu(struct mmu_gather *tlb); struct vm_fault; diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 67131aa24d77..a1832da0f623 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -5112,7 +5112,7 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma, unsigned long last_addr_mask; pte_t *src_pte, *dst_pte; struct mmu_notifier_range range; - bool shared_pmd = false; + struct mmu_gather tlb; mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, mm, old_addr, old_end); @@ -5122,6 +5122,7 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma, * range. */ flush_cache_range(vma, range.start, range.end); + tlb_gather_mmu_vma(&tlb, vma); mmu_notifier_invalidate_range_start(&range); last_addr_mask = hugetlb_mask_last_page(h); @@ -5138,8 +5139,7 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma, if (huge_pte_none(huge_ptep_get(mm, old_addr, src_pte))) continue; - if (huge_pmd_unshare(mm, vma, old_addr, src_pte)) { - shared_pmd = true; + if (huge_pmd_unshare(&tlb, vma, old_addr, src_pte)) { old_addr |= last_addr_mask; new_addr |= last_addr_mask; continue; @@ -5150,15 +5150,16 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma, break; move_huge_pte(vma, old_addr, new_addr, src_pte, dst_pte, sz); + tlb_remove_huge_tlb_entry(h, &tlb, src_pte, old_addr); } - if (shared_pmd) - flush_hugetlb_tlb_range(vma, range.start, range.end); - else - flush_hugetlb_tlb_range(vma, old_end - len, old_end); + tlb_flush_mmu_tlbonly(&tlb); + huge_pmd_unshare_flush(&tlb, vma); + mmu_notifier_invalidate_range_end(&range); i_mmap_unlock_write(mapping); hugetlb_vma_unlock_write(vma); + tlb_finish_mmu(&tlb); return len + old_addr - old_end; } @@ -5177,7 +5178,6 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma, unsigned long sz = huge_page_size(h); bool adjust_reservation; unsigned long last_addr_mask; - bool force_flush = false; WARN_ON(!is_vm_hugetlb_page(vma)); BUG_ON(start & ~huge_page_mask(h)); @@ -5200,10 +5200,8 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma, } ptl = huge_pte_lock(h, mm, ptep); - if (huge_pmd_unshare(mm, vma, address, ptep)) { + if (huge_pmd_unshare(tlb, vma, address, ptep)) { spin_unlock(ptl); - tlb_flush_pmd_range(tlb, address & PUD_MASK, PUD_SIZE); - force_flush = true; address |= last_addr_mask; continue; } @@ -5319,14 +5317,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma, } tlb_end_vma(tlb, vma); - /* - * There is nothing protecting a previously-shared page table that we - * unshared through huge_pmd_unshare() from getting freed after we - * release i_mmap_rwsem, so flush the TLB now. If huge_pmd_unshare() - * succeeded, flush the range corresponding to the pud. - */ - if (force_flush) - tlb_flush_mmu_tlbonly(tlb); + huge_pmd_unshare_flush(tlb, vma); } void __hugetlb_zap_begin(struct vm_area_struct *vma, @@ -6425,11 +6416,11 @@ long hugetlb_change_protection(struct vm_area_struct *vma, pte_t pte; struct hstate *h = hstate_vma(vma); long pages = 0, psize = huge_page_size(h); - bool shared_pmd = false; struct mmu_notifier_range range; unsigned long last_addr_mask; bool uffd_wp = cp_flags & MM_CP_UFFD_WP; bool uffd_wp_resolve = cp_flags & MM_CP_UFFD_WP_RESOLVE; + struct mmu_gather tlb; /* * In the case of shared PMDs, the area to flush could be beyond @@ -6442,6 +6433,7 @@ long hugetlb_change_protection(struct vm_area_struct *vma, BUG_ON(address >= end); flush_cache_range(vma, range.start, range.end); + tlb_gather_mmu_vma(&tlb, vma); mmu_notifier_invalidate_range_start(&range); hugetlb_vma_lock_write(vma); @@ -6468,7 +6460,7 @@ long hugetlb_change_protection(struct vm_area_struct *vma, } } ptl = huge_pte_lock(h, mm, ptep); - if (huge_pmd_unshare(mm, vma, address, ptep)) { + if (huge_pmd_unshare(&tlb, vma, address, ptep)) { /* * When uffd-wp is enabled on the vma, unshare * shouldn't happen at all. Warn about it if it @@ -6477,7 +6469,6 @@ long hugetlb_change_protection(struct vm_area_struct *vma, WARN_ON_ONCE(uffd_wp || uffd_wp_resolve); pages++; spin_unlock(ptl); - shared_pmd = true; address |= last_addr_mask; continue; } @@ -6538,22 +6529,16 @@ long hugetlb_change_protection(struct vm_area_struct *vma, pte = huge_pte_clear_uffd_wp(pte); huge_ptep_modify_prot_commit(vma, address, ptep, old_pte, pte); pages++; + tlb_remove_huge_tlb_entry(h, &tlb, ptep, address); } next: spin_unlock(ptl); cond_resched(); } - /* - * There is nothing protecting a previously-shared page table that we - * unshared through huge_pmd_unshare() from getting freed after we - * release i_mmap_rwsem, so flush the TLB now. If huge_pmd_unshare() - * succeeded, flush the range corresponding to the pud. - */ - if (shared_pmd) - flush_hugetlb_tlb_range(vma, range.start, range.end); - else - flush_hugetlb_tlb_range(vma, start, end); + + tlb_flush_mmu_tlbonly(&tlb); + huge_pmd_unshare_flush(&tlb, vma); /* * No need to call mmu_notifier_arch_invalidate_secondary_tlbs() we are * downgrading page table protection not changing it to point to a new @@ -6564,6 +6549,7 @@ next: i_mmap_unlock_write(vma->vm_file->f_mapping); hugetlb_vma_unlock_write(vma); mmu_notifier_invalidate_range_end(&range); + tlb_finish_mmu(&tlb); return pages > 0 ? (pages << h->order) : pages; } @@ -6920,18 +6906,27 @@ out: return pte; } -/* - * unmap huge page backed by shared pte. +/** + * huge_pmd_unshare - Unmap a pmd table if it is shared by multiple users + * @tlb: the current mmu_gather. + * @vma: the vma covering the pmd table. + * @addr: the address we are trying to unshare. + * @ptep: pointer into the (pmd) page table. + * + * Called with the page table lock held, the i_mmap_rwsem held in write mode + * and the hugetlb vma lock held in write mode. * - * Called with page table lock held. + * Note: The caller must call huge_pmd_unshare_flush() before dropping the + * i_mmap_rwsem. * - * returns: 1 successfully unmapped a shared pte page - * 0 the underlying pte page is not shared, or it is the last user + * Returns: 1 if it was a shared PMD table and it got unmapped, or 0 if it + * was not a shared PMD table. */ -int huge_pmd_unshare(struct mm_struct *mm, struct vm_area_struct *vma, - unsigned long addr, pte_t *ptep) +int huge_pmd_unshare(struct mmu_gather *tlb, struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep) { unsigned long sz = huge_page_size(hstate_vma(vma)); + struct mm_struct *mm = vma->vm_mm; pgd_t *pgd = pgd_offset(mm, addr); p4d_t *p4d = p4d_offset(pgd, addr); pud_t *pud = pud_offset(p4d, addr); @@ -6943,18 +6938,36 @@ int huge_pmd_unshare(struct mm_struct *mm, struct vm_area_struct *vma, i_mmap_assert_write_locked(vma->vm_file->f_mapping); hugetlb_vma_assert_locked(vma); pud_clear(pud); - /* - * Once our caller drops the rmap lock, some other process might be - * using this page table as a normal, non-hugetlb page table. - * Wait for pending gup_fast() in other threads to finish before letting - * that happen. - */ - tlb_remove_table_sync_one(); - ptdesc_pmd_pts_dec(virt_to_ptdesc(ptep)); + + tlb_unshare_pmd_ptdesc(tlb, virt_to_ptdesc(ptep), addr); + mm_dec_nr_pmds(mm); return 1; } +/* + * huge_pmd_unshare_flush - Complete a sequence of huge_pmd_unshare() calls + * @tlb: the current mmu_gather. + * @vma: the vma covering the pmd table. + * + * Perform necessary TLB flushes or IPI broadcasts to synchronize PMD table + * unsharing with concurrent page table walkers. + * + * This function must be called after a sequence of huge_pmd_unshare() + * calls while still holding the i_mmap_rwsem. + */ +void huge_pmd_unshare_flush(struct mmu_gather *tlb, struct vm_area_struct *vma) +{ + /* + * We must synchronize page table unsharing such that nobody will + * try reusing a previously-shared page table while it might still + * be in use by previous sharers (TLB, GUP_fast). + */ + i_mmap_assert_write_locked(vma->vm_file->f_mapping); + + tlb_flush_unshared_tables(tlb); +} + #else /* !CONFIG_HUGETLB_PMD_PAGE_TABLE_SHARING */ pte_t *huge_pmd_share(struct mm_struct *mm, struct vm_area_struct *vma, @@ -6963,12 +6976,16 @@ pte_t *huge_pmd_share(struct mm_struct *mm, struct vm_area_struct *vma, return NULL; } -int huge_pmd_unshare(struct mm_struct *mm, struct vm_area_struct *vma, - unsigned long addr, pte_t *ptep) +int huge_pmd_unshare(struct mmu_gather *tlb, struct vm_area_struct *vma, + unsigned long addr, pte_t *ptep) { return 0; } +void huge_pmd_unshare_flush(struct mmu_gather *tlb, struct vm_area_struct *vma) +{ +} + void adjust_range_if_pmd_sharing_possible(struct vm_area_struct *vma, unsigned long *start, unsigned long *end) { @@ -7235,6 +7252,7 @@ static void hugetlb_unshare_pmds(struct vm_area_struct *vma, unsigned long sz = huge_page_size(h); struct mm_struct *mm = vma->vm_mm; struct mmu_notifier_range range; + struct mmu_gather tlb; unsigned long address; spinlock_t *ptl; pte_t *ptep; @@ -7246,6 +7264,8 @@ static void hugetlb_unshare_pmds(struct vm_area_struct *vma, return; flush_cache_range(vma, start, end); + tlb_gather_mmu_vma(&tlb, vma); + /* * No need to call adjust_range_if_pmd_sharing_possible(), because * we have already done the PUD_SIZE alignment. @@ -7264,10 +7284,10 @@ static void hugetlb_unshare_pmds(struct vm_area_struct *vma, if (!ptep) continue; ptl = huge_pte_lock(h, mm, ptep); - huge_pmd_unshare(mm, vma, address, ptep); + huge_pmd_unshare(&tlb, vma, address, ptep); spin_unlock(ptl); } - flush_hugetlb_tlb_range(vma, start, end); + huge_pmd_unshare_flush(&tlb, vma); if (take_locks) { i_mmap_unlock_write(vma->vm_file->f_mapping); hugetlb_vma_unlock_write(vma); @@ -7277,6 +7297,7 @@ static void hugetlb_unshare_pmds(struct vm_area_struct *vma, * Documentation/mm/mmu_notifier.rst. */ mmu_notifier_invalidate_range_end(&range); + tlb_finish_mmu(&tlb); } /* diff --git a/mm/mmu_gather.c b/mm/mmu_gather.c index 247e3f9db6c7..7468ec388455 100644 --- a/mm/mmu_gather.c +++ b/mm/mmu_gather.c @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -426,6 +427,7 @@ static void __tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, #endif tlb->vma_pfn = 0; + tlb->fully_unshared_tables = 0; __tlb_reset_range(tlb); inc_tlb_flush_pending(tlb->mm); } @@ -459,6 +461,31 @@ void tlb_gather_mmu_fullmm(struct mmu_gather *tlb, struct mm_struct *mm) __tlb_gather_mmu(tlb, mm, true); } +/** + * tlb_gather_mmu_vma - initialize an mmu_gather structure for operating on a + * single VMA + * @tlb: the mmu_gather structure to initialize + * @vma: the vm_area_struct + * + * Called to initialize an (on-stack) mmu_gather structure for operating on + * a single VMA. In contrast to tlb_gather_mmu(), calling this function will + * not require another call to tlb_start_vma(). In contrast to tlb_start_vma(), + * this function will *not* call flush_cache_range(). + * + * For hugetlb VMAs, this function will also initialize the mmu_gather + * page_size accordingly, not requiring a separate call to + * tlb_change_page_size(). + * + */ +void tlb_gather_mmu_vma(struct mmu_gather *tlb, struct vm_area_struct *vma) +{ + tlb_gather_mmu(tlb, vma->vm_mm); + tlb_update_vma_flags(tlb, vma); + if (is_vm_hugetlb_page(vma)) + /* All entries have the same size. */ + tlb_change_page_size(tlb, huge_page_size(hstate_vma(vma))); +} + /** * tlb_finish_mmu - finish an mmu_gather structure * @tlb: the mmu_gather structure to finish @@ -468,6 +495,12 @@ void tlb_gather_mmu_fullmm(struct mmu_gather *tlb, struct mm_struct *mm) */ void tlb_finish_mmu(struct mmu_gather *tlb) { + /* + * We expect an earlier huge_pmd_unshare_flush() call to sort this out, + * due to complicated locking requirements with page table unsharing. + */ + VM_WARN_ON_ONCE(tlb->fully_unshared_tables); + /* * If there are parallel threads are doing PTE changes on same range * under non-exclusive lock (e.g., mmap_lock read-side) but defer TLB diff --git a/mm/rmap.c b/mm/rmap.c index 748f48727a16..7b9879ef442d 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -76,7 +76,7 @@ #include #include -#include +#include #define CREATE_TRACE_POINTS #include @@ -2008,13 +2008,17 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, * if unsuccessful. */ if (!anon) { + struct mmu_gather tlb; + VM_BUG_ON(!(flags & TTU_RMAP_LOCKED)); if (!hugetlb_vma_trylock_write(vma)) goto walk_abort; - if (huge_pmd_unshare(mm, vma, address, pvmw.pte)) { + + tlb_gather_mmu_vma(&tlb, vma); + if (huge_pmd_unshare(&tlb, vma, address, pvmw.pte)) { hugetlb_vma_unlock_write(vma); - flush_tlb_range(vma, - range.start, range.end); + huge_pmd_unshare_flush(&tlb, vma); + tlb_finish_mmu(&tlb); /* * The PMD table was unmapped, * consequently unmapping the folio. @@ -2022,6 +2026,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, goto walk_done; } hugetlb_vma_unlock_write(vma); + tlb_finish_mmu(&tlb); } pteval = huge_ptep_clear_flush(vma, address, pvmw.pte); if (pte_dirty(pteval)) @@ -2398,17 +2403,20 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma, * fail if unsuccessful. */ if (!anon) { + struct mmu_gather tlb; + VM_BUG_ON(!(flags & TTU_RMAP_LOCKED)); if (!hugetlb_vma_trylock_write(vma)) { page_vma_mapped_walk_done(&pvmw); ret = false; break; } - if (huge_pmd_unshare(mm, vma, address, pvmw.pte)) { - hugetlb_vma_unlock_write(vma); - flush_tlb_range(vma, - range.start, range.end); + tlb_gather_mmu_vma(&tlb, vma); + if (huge_pmd_unshare(&tlb, vma, address, pvmw.pte)) { + hugetlb_vma_unlock_write(vma); + huge_pmd_unshare_flush(&tlb, vma); + tlb_finish_mmu(&tlb); /* * The PMD table was unmapped, * consequently unmapping the folio. @@ -2417,6 +2425,7 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma, break; } hugetlb_vma_unlock_write(vma); + tlb_finish_mmu(&tlb); } /* Nuke the hugetlb page table entry */ pteval = huge_ptep_clear_flush(vma, address, pvmw.pte); -- cgit v1.2.3 From 35e247032606f06c2f19d90a6562bc315206b7a7 Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Wed, 14 Jan 2026 11:00:06 +0000 Subject: mm: do not copy page tables unnecessarily for VM_UFFD_WP Commit ab04b530e7e8 ("mm: introduce copy-on-fork VMAs and make VM_MAYBE_GUARD one") aggregates flags checks in vma_needs_copy(), including VM_UFFD_WP. However in doing so, it incorrectly performed this check against src_vma. This check was done on the assumption that all relevant flags are copied upon fork. However the userfaultfd logic is very innovative in that it implements custom logic on fork in dup_userfaultfd(), including a rather well hidden case where lacking UFFD_FEATURE_EVENT_FORK causes VM_UFFD_WP to not be propagated to the destination VMA. And indeed, vma_needs_copy(), prior to this patch, did check this property on dst_vma, not src_vma. Since all the other relevant flags are copied on fork, we can simply fix this by checking against dst_vma. While we're here, we fix a comment against VM_COPY_ON_FORK (noting that it did indeed already reference dst_vma) to make it abundantly clear that we must check against the destination VMA. Link: https://lkml.kernel.org/r/20260114110006.1047071-1-lorenzo.stoakes@oracle.com Fixes: ab04b530e7e8 ("mm: introduce copy-on-fork VMAs and make VM_MAYBE_GUARD one") Signed-off-by: Lorenzo Stoakes Reported-by: Chris Mason Closes: https://lore.kernel.org/all/20260113231257.3002271-1-clm@meta.com/ Acked-by: David Hildenbrand (Red Hat) Acked-by: Pedro Falcato Cc: Liam Howlett Cc: Michal Hocko Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Vlastimil Babka Signed-off-by: Andrew Morton --- include/linux/mm.h | 6 +++++- mm/memory.c | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/mm.h b/include/linux/mm.h index 6f959d8ca4b4..f0d5be9dc736 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -608,7 +608,11 @@ enum { /* * Flags which should result in page tables being copied on fork. These are * flags which indicate that the VMA maps page tables which cannot be - * reconsistuted upon page fault, so necessitate page table copying upon + * reconsistuted upon page fault, so necessitate page table copying upon fork. + * + * Note that these flags should be compared with the DESTINATION VMA not the + * source, as VM_UFFD_WP may not be propagated to destination, while all other + * flags will be. * * VM_PFNMAP / VM_MIXEDMAP - These contain kernel-mapped data which cannot be * reasonably reconstructed on page fault. diff --git a/mm/memory.c b/mm/memory.c index a0822b564cc0..da360a6eb8a4 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -1465,7 +1465,11 @@ copy_p4d_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma, static bool vma_needs_copy(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma) { - if (src_vma->vm_flags & VM_COPY_ON_FORK) + /* + * We check against dst_vma as while sane VMA flags will have been + * copied, VM_UFFD_WP may be set only on dst_vma. + */ + if (dst_vma->vm_flags & VM_COPY_ON_FORK) return true; /* * The presence of an anon_vma indicates an anonymous VMA has page -- cgit v1.2.3 From b190870e0e0cfb375c0d4da02761c32083f3644d Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 20 Jan 2026 21:35:04 +0200 Subject: PCI: Add Intel Nova Lake audio Device ID Add Nova Lake (NVL) audio Device ID The ID will be used by HDA legacy, SOF audio stack and the driver to determine which audio stack should be used (intel-dsp-config). Signed-off-by: Peter Ujfalusi Reviewed-by: Kai Vehmanen Reviewed-by: Liam Girdwood Reviewed-by: Ranjani Sridharan Acked-by: Bjorn Helgaas Acked-by: Takashi Iwai Link: https://patch.msgid.link/20260120193507.14019-2-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- include/linux/pci_ids.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 84b830036fb4..5ed7846639bf 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -3144,6 +3144,7 @@ #define PCI_DEVICE_ID_INTEL_HDA_CML_S 0xa3f0 #define PCI_DEVICE_ID_INTEL_HDA_LNL_P 0xa828 #define PCI_DEVICE_ID_INTEL_S21152BB 0xb152 +#define PCI_DEVICE_ID_INTEL_HDA_NVL 0xd328 #define PCI_DEVICE_ID_INTEL_HDA_BMG 0xe2f7 #define PCI_DEVICE_ID_INTEL_HDA_PTL_H 0xe328 #define PCI_DEVICE_ID_INTEL_HDA_PTL 0xe428 -- cgit v1.2.3 From 00fd40bc7acecf9f41d645aa0b35ddc7fd7679b6 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 21 Jan 2026 13:22:39 +0000 Subject: ASoC: cs-amp-lib: Support Dell SSIDExV2 UEFI variable Add a function cs_amp_devm_get_vendor_specific_variant_id() to return a vendor-specific hardware identifier string (if there is one) and use it to fetch an identifier from Dell SSIDExV2 UEFI variable content. Dell use the same PCI SSID on multiple products that might have different audio hardware and thus need different firmware for the amplifier DSP. The SSIDExV2 string contains additional system identifiers, and the second field is a 2-character audio hardware identifier. There are older Dell models with Cirrus Logic amplifiers that have the SSIDExV2 UEFI variable but do not have the 2-character audio ID in the second field. The SSIDExV2 is ignored if the second field is not 2 characters. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260121132243.1256019-2-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/cs-amp-lib.h | 3 ++ sound/soc/codecs/cs-amp-lib.c | 111 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) (limited to 'include') diff --git a/include/sound/cs-amp-lib.h b/include/sound/cs-amp-lib.h index 61e00017c9aa..e9aa86d76049 100644 --- a/include/sound/cs-amp-lib.h +++ b/include/sound/cs-amp-lib.h @@ -58,6 +58,9 @@ int cs_amp_get_efi_calibration_data(struct device *dev, u64 target_uid, int amp_ int cs_amp_set_efi_calibration_data(struct device *dev, int amp_index, int num_amps, const struct cirrus_amp_cal_data *in_data); int cs_amp_get_vendor_spkid(struct device *dev); +const char *cs_amp_devm_get_vendor_specific_variant_id(struct device *dev, + int ssid_vendor, + int ssid_device); struct dentry *cs_amp_create_debugfs(struct device *dev); static inline u64 cs_amp_cal_target_u64(const struct cirrus_amp_cal_data *data) diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c index b4d183e7501d..9f8c99dfb798 100644 --- a/sound/soc/codecs/cs-amp-lib.c +++ b/sound/soc/codecs/cs-amp-lib.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,10 @@ #define HP_CALIBRATION_EFI_GUID \ EFI_GUID(0x53559579, 0x8753, 0x4f5c, 0x91, 0x30, 0xe8, 0x2a, 0xcf, 0xb8, 0xd8, 0x93) +#define DELL_SSIDEXV2_EFI_NAME L"SSIDexV2Data" +#define DELL_SSIDEXV2_EFI_GUID \ + EFI_GUID(0x6a5f35df, 0x1432, 0x4656, 0x85, 0x97, 0x31, 0x04, 0xd5, 0xbf, 0x3a, 0xb0) + static const struct cs_amp_lib_cal_efivar { efi_char16_t *name; efi_guid_t *guid; @@ -304,6 +309,29 @@ static int cs_amp_convert_efi_status(efi_status_t status) } } +static void *cs_amp_alloc_get_efi_variable(efi_char16_t *name, + efi_guid_t *guid, + u32 *returned_attr) +{ + efi_status_t status; + unsigned long size = 0; + + status = cs_amp_get_efi_variable(name, guid, NULL, &size, NULL); + if (status != EFI_BUFFER_TOO_SMALL) + return ERR_PTR(cs_amp_convert_efi_status(status)); + + /* Over-alloc to ensure strings are always NUL-terminated */ + void *buf __free(kfree) = kzalloc(size + 1, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + status = cs_amp_get_efi_variable(name, guid, returned_attr, &size, buf); + if (status != EFI_SUCCESS) + return ERR_PTR(cs_amp_convert_efi_status(status)); + + return_ptr(buf); +} + static struct cirrus_amp_efi_data *cs_amp_get_cal_efi_buffer(struct device *dev, efi_char16_t **name, efi_guid_t **guid, @@ -705,6 +733,89 @@ int cs_amp_get_vendor_spkid(struct device *dev) } EXPORT_SYMBOL_NS_GPL(cs_amp_get_vendor_spkid, "SND_SOC_CS_AMP_LIB"); +static const char *cs_amp_devm_get_dell_ssidex(struct device *dev, + int ssid_vendor, int ssid_device) +{ + unsigned int hex_prefix; + char audio_id[4]; + char delim; + char *p; + int ret; + + if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE) && + !IS_ENABLED(CONFIG_SND_SOC_CS_AMP_LIB_TEST)) + return ERR_PTR(-ENOENT); + + char *ssidex_buf __free(kfree) = cs_amp_alloc_get_efi_variable(DELL_SSIDEXV2_EFI_NAME, + &DELL_SSIDEXV2_EFI_GUID, + NULL); + ret = PTR_ERR_OR_ZERO(ssidex_buf); + if (ret == -ENOENT) + return ERR_PTR(-ENOENT); + else if (ret < 0) + return ssidex_buf; + + /* + * SSIDExV2 string is a series of underscore delimited fields. + * First field is all or part of the SSID. Second field should be + * a 2-character audio hardware id, followed by other identifiers. + * Older models did not have the 2-character audio id, so reject + * the string if the second field is not 2 characters. + */ + ret = sscanf(ssidex_buf, "%8x_%2s%c", &hex_prefix, audio_id, &delim); + if (ret < 2) + return ERR_PTR(-ENOENT); + + if ((ret == 3) && (delim != '_')) + return ERR_PTR(-ENOENT); + + if (strlen(audio_id) != 2) + return ERR_PTR(-ENOENT); + + p = devm_kstrdup(dev, audio_id, GFP_KERNEL); + if (!p) + return ERR_PTR(-ENOMEM); + + return p; +} + +/** + * cs_amp_devm_get_vendor_specific_variant_id - get variant ID string + * @dev: pointer to struct device + * @ssid_vendor: PCI Subsystem Vendor (-1 if unknown) + * @ssid_device: PCI Subsystem Device (-1 if unknown) + * + * Known vendor-specific hardware identifiers are checked and if one is + * found its content is returned as a NUL-terminated string. The returned + * string is devm-managed. + * + * The returned string is not guaranteed to be globally unique. + * Generally it should be combined with some other qualifier, such as + * PCI SSID, to create a globally unique ID. + * + * If the caller has a PCI SSID it should pass it in @ssid_vendor and + * @ssid_device. If the vendor-spefic ID contains this SSID it will be + * stripped from the returned string to prevent duplication. + * + * If the caller does not have a PCI SSID, pass -1 for @ssid_vendor and + * @ssid_device. + * + * Return: + * * a pointer to a devm-managed string + * * ERR_PTR(-ENOENT) if no vendor-specific qualifier + * * ERR_PTR error value + */ +const char *cs_amp_devm_get_vendor_specific_variant_id(struct device *dev, + int ssid_vendor, + int ssid_device) +{ + if ((ssid_vendor == PCI_VENDOR_ID_DELL) || (ssid_vendor < 0)) + return cs_amp_devm_get_dell_ssidex(dev, ssid_vendor, ssid_device); + + return ERR_PTR(-ENOENT); +} +EXPORT_SYMBOL_NS_GPL(cs_amp_devm_get_vendor_specific_variant_id, "SND_SOC_CS_AMP_LIB"); + /** * cs_amp_create_debugfs - create a debugfs directory for a device * -- cgit v1.2.3 From f5f2bad67a45cd1ef6f5b727da104694a81b3666 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 21 Jan 2026 08:31:49 +0100 Subject: block: make the new blkzoned UAPI constants discoverable The Linux 6.19 merge window added the new BLKREPORTZONESV2 ioctl, and with it the new BLK_ZONE_REP_CACHED and BLK_ZONE_COND_ACTIVE constants. The two constants are defined as part of enums, which makes it very painful for userspace to discover if they are present in the installed system headers. Use the #define to the same name trick to make them trivially discoverable using CPP directives. Fixes: 0bf0e2e46668 ("block: track zone conditions") Fixes: b30ffcdc0c15 ("block: introduce BLKREPORTZONESV2 ioctl") Reported-by: Andrey Albershteyn Signed-off-by: Christoph Hellwig Reviewed-by: Johannes Thumshirn Signed-off-by: Jens Axboe --- include/uapi/linux/blkzoned.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/uapi/linux/blkzoned.h b/include/uapi/linux/blkzoned.h index e33f02703350..663836120966 100644 --- a/include/uapi/linux/blkzoned.h +++ b/include/uapi/linux/blkzoned.h @@ -81,7 +81,8 @@ enum blk_zone_cond { BLK_ZONE_COND_FULL = 0xE, BLK_ZONE_COND_OFFLINE = 0xF, - BLK_ZONE_COND_ACTIVE = 0xFF, + BLK_ZONE_COND_ACTIVE = 0xFF, /* added in Linux 6.19 */ +#define BLK_ZONE_COND_ACTIVE BLK_ZONE_COND_ACTIVE }; /** @@ -100,7 +101,8 @@ enum blk_zone_report_flags { BLK_ZONE_REP_CAPACITY = (1U << 0), /* Input flags */ - BLK_ZONE_REP_CACHED = (1U << 31), + BLK_ZONE_REP_CACHED = (1U << 31), /* added in Linux 6.19 */ +#define BLK_ZONE_REP_CACHED BLK_ZONE_REP_CACHED }; /** -- cgit v1.2.3 From bdcdf968be314b6fc8835b99fb4519e7619671e6 Mon Sep 17 00:00:00 2001 From: Thomas Hellström Date: Wed, 21 Jan 2026 10:10:47 +0100 Subject: drm, drm/xe: Fix xe userptr in the absence of CONFIG_DEVICE_PRIVATE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CONFIG_DEVICE_PRIVATE is not selected by default by some distros, for example Fedora, and that leads to a regression in the xe driver since userptr support gets compiled out. It turns out that DRM_GPUSVM, which is needed for xe userptr support compiles also without CONFIG_DEVICE_PRIVATE, but doesn't compile without CONFIG_ZONE_DEVICE. Exclude the drm_pagemap files from compilation with !CONFIG_ZONE_DEVICE, and remove the CONFIG_DEVICE_PRIVATE dependency from CONFIG_DRM_GPUSVM and the xe driver's selection of it, re-enabling xe userptr for those configs. v2: - Don't compile the drm_pagemap files unless CONFIG_ZONE_DEVICE is set. - Adjust the drm_pagemap.h header accordingly. Fixes: 9e9787414882 ("drm/xe/userptr: replace xe_hmm with gpusvm") Cc: Matthew Auld Cc: Himal Prasad Ghimiray Cc: Thomas Hellström Cc: Matthew Brost Cc: "Thomas Hellström" Cc: Rodrigo Vivi Cc: dri-devel@lists.freedesktop.org Cc: # v6.18+ Signed-off-by: Thomas Hellström Reviewed-by: Matthew Auld Acked-by: Maarten Lankhorst Link: https://patch.msgid.link/20260121091048.41371-2-thomas.hellstrom@linux.intel.com (cherry picked from commit 1e372b246199ca7a35f930177fea91b557dac16e) Signed-off-by: Thomas Hellström --- drivers/gpu/drm/Kconfig | 2 +- drivers/gpu/drm/Makefile | 4 +++- drivers/gpu/drm/xe/Kconfig | 2 +- include/drm/drm_pagemap.h | 19 +++++++++++++++++-- 4 files changed, 22 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 7e6bc0b3a589..ed85d0ceee3b 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -210,7 +210,7 @@ config DRM_GPUVM config DRM_GPUSVM tristate - depends on DRM && DEVICE_PRIVATE + depends on DRM select HMM_MIRROR select MMU_NOTIFIER help diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 0e1c668b46d2..d26191717428 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -108,8 +108,10 @@ obj-$(CONFIG_DRM_EXEC) += drm_exec.o obj-$(CONFIG_DRM_GPUVM) += drm_gpuvm.o drm_gpusvm_helper-y := \ - drm_gpusvm.o\ + drm_gpusvm.o +drm_gpusvm_helper-$(CONFIG_ZONE_DEVICE) += \ drm_pagemap.o + obj-$(CONFIG_DRM_GPUSVM) += drm_gpusvm_helper.o obj-$(CONFIG_DRM_BUDDY) += drm_buddy.o diff --git a/drivers/gpu/drm/xe/Kconfig b/drivers/gpu/drm/xe/Kconfig index 4b288eb3f5b0..c34be1be155b 100644 --- a/drivers/gpu/drm/xe/Kconfig +++ b/drivers/gpu/drm/xe/Kconfig @@ -39,7 +39,7 @@ config DRM_XE select DRM_TTM select DRM_TTM_HELPER select DRM_EXEC - select DRM_GPUSVM if !UML && DEVICE_PRIVATE + select DRM_GPUSVM if !UML select DRM_GPUVM select DRM_SCHED select MMU_NOTIFIER diff --git a/include/drm/drm_pagemap.h b/include/drm/drm_pagemap.h index 70a7991f784f..eb29e5309f0a 100644 --- a/include/drm/drm_pagemap.h +++ b/include/drm/drm_pagemap.h @@ -209,6 +209,19 @@ struct drm_pagemap_devmem_ops { struct dma_fence *pre_migrate_fence); }; +#if IS_ENABLED(CONFIG_ZONE_DEVICE) + +struct drm_pagemap *drm_pagemap_page_to_dpagemap(struct page *page); + +#else + +static inline struct drm_pagemap *drm_pagemap_page_to_dpagemap(struct page *page) +{ + return NULL; +} + +#endif /* IS_ENABLED(CONFIG_ZONE_DEVICE) */ + /** * struct drm_pagemap_devmem - Structure representing a GPU SVM device memory allocation * @@ -233,6 +246,8 @@ struct drm_pagemap_devmem { struct dma_fence *pre_migrate_fence; }; +#if IS_ENABLED(CONFIG_ZONE_DEVICE) + int drm_pagemap_migrate_to_devmem(struct drm_pagemap_devmem *devmem_allocation, struct mm_struct *mm, unsigned long start, unsigned long end, @@ -243,8 +258,6 @@ int drm_pagemap_evict_to_ram(struct drm_pagemap_devmem *devmem_allocation); const struct dev_pagemap_ops *drm_pagemap_pagemap_ops_get(void); -struct drm_pagemap *drm_pagemap_page_to_dpagemap(struct page *page); - void drm_pagemap_devmem_init(struct drm_pagemap_devmem *devmem_allocation, struct device *dev, struct mm_struct *mm, const struct drm_pagemap_devmem_ops *ops, @@ -256,4 +269,6 @@ int drm_pagemap_populate_mm(struct drm_pagemap *dpagemap, struct mm_struct *mm, unsigned long timeslice_ms); +#endif /* IS_ENABLED(CONFIG_ZONE_DEVICE) */ + #endif -- cgit v1.2.3 From f6c3665b6dc53c3ab7d31b585446a953a74340ef Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 22 Jan 2026 16:29:14 +0000 Subject: bonding: annotate data-races around slave->last_rx slave->last_rx and slave->target_last_arp_rx[...] can be read and written locklessly. Add READ_ONCE() and WRITE_ONCE() annotations. syzbot reported: BUG: KCSAN: data-race in bond_rcv_validate / bond_rcv_validate write to 0xffff888149f0d428 of 8 bytes by interrupt on cpu 1: bond_rcv_validate+0x202/0x7a0 drivers/net/bonding/bond_main.c:3335 bond_handle_frame+0xde/0x5e0 drivers/net/bonding/bond_main.c:1533 __netif_receive_skb_core+0x5b1/0x1950 net/core/dev.c:6039 __netif_receive_skb_one_core net/core/dev.c:6150 [inline] __netif_receive_skb+0x59/0x270 net/core/dev.c:6265 netif_receive_skb_internal net/core/dev.c:6351 [inline] netif_receive_skb+0x4b/0x2d0 net/core/dev.c:6410 ... write to 0xffff888149f0d428 of 8 bytes by interrupt on cpu 0: bond_rcv_validate+0x202/0x7a0 drivers/net/bonding/bond_main.c:3335 bond_handle_frame+0xde/0x5e0 drivers/net/bonding/bond_main.c:1533 __netif_receive_skb_core+0x5b1/0x1950 net/core/dev.c:6039 __netif_receive_skb_one_core net/core/dev.c:6150 [inline] __netif_receive_skb+0x59/0x270 net/core/dev.c:6265 netif_receive_skb_internal net/core/dev.c:6351 [inline] netif_receive_skb+0x4b/0x2d0 net/core/dev.c:6410 br_netif_receive_skb net/bridge/br_input.c:30 [inline] NF_HOOK include/linux/netfilter.h:318 [inline] ... value changed: 0x0000000100005365 -> 0x0000000100005366 Fixes: f5b2b966f032 ("[PATCH] bonding: Validate probe replies in ARP monitor") Signed-off-by: Eric Dumazet Reported-by: syzbot Link: https://patch.msgid.link/20260122162914.2299312-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_main.c | 18 ++++++++++-------- drivers/net/bonding/bond_options.c | 8 ++++---- include/net/bonding.h | 13 +++++++------ 3 files changed, 21 insertions(+), 18 deletions(-) (limited to 'include') diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index e7caf400a59c..a909ebcf1102 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -3047,8 +3047,8 @@ static void bond_validate_arp(struct bonding *bond, struct slave *slave, __be32 __func__, &sip); return; } - slave->last_rx = jiffies; - slave->target_last_arp_rx[i] = jiffies; + WRITE_ONCE(slave->last_rx, jiffies); + WRITE_ONCE(slave->target_last_arp_rx[i], jiffies); } static int bond_arp_rcv(const struct sk_buff *skb, struct bonding *bond, @@ -3267,8 +3267,8 @@ static void bond_validate_na(struct bonding *bond, struct slave *slave, __func__, saddr); return; } - slave->last_rx = jiffies; - slave->target_last_arp_rx[i] = jiffies; + WRITE_ONCE(slave->last_rx, jiffies); + WRITE_ONCE(slave->target_last_arp_rx[i], jiffies); } static int bond_na_rcv(const struct sk_buff *skb, struct bonding *bond, @@ -3338,7 +3338,7 @@ int bond_rcv_validate(const struct sk_buff *skb, struct bonding *bond, (slave_do_arp_validate_only(bond) && is_ipv6) || #endif !slave_do_arp_validate_only(bond)) - slave->last_rx = jiffies; + WRITE_ONCE(slave->last_rx, jiffies); return RX_HANDLER_ANOTHER; } else if (is_arp) { return bond_arp_rcv(skb, bond, slave); @@ -3406,7 +3406,7 @@ static void bond_loadbalance_arp_mon(struct bonding *bond) if (slave->link != BOND_LINK_UP) { if (bond_time_in_interval(bond, last_tx, 1) && - bond_time_in_interval(bond, slave->last_rx, 1)) { + bond_time_in_interval(bond, READ_ONCE(slave->last_rx), 1)) { bond_propose_link_state(slave, BOND_LINK_UP); slave_state_changed = 1; @@ -3430,8 +3430,10 @@ static void bond_loadbalance_arp_mon(struct bonding *bond) * when the source ip is 0, so don't take the link down * if we don't know our ip yet */ - if (!bond_time_in_interval(bond, last_tx, bond->params.missed_max) || - !bond_time_in_interval(bond, slave->last_rx, bond->params.missed_max)) { + if (!bond_time_in_interval(bond, last_tx, + bond->params.missed_max) || + !bond_time_in_interval(bond, READ_ONCE(slave->last_rx), + bond->params.missed_max)) { bond_propose_link_state(slave, BOND_LINK_DOWN); slave_state_changed = 1; diff --git a/drivers/net/bonding/bond_options.c b/drivers/net/bonding/bond_options.c index 384499c869b8..f1c6e9d8f616 100644 --- a/drivers/net/bonding/bond_options.c +++ b/drivers/net/bonding/bond_options.c @@ -1152,7 +1152,7 @@ static void _bond_options_arp_ip_target_set(struct bonding *bond, int slot, if (slot >= 0 && slot < BOND_MAX_ARP_TARGETS) { bond_for_each_slave(bond, slave, iter) - slave->target_last_arp_rx[slot] = last_rx; + WRITE_ONCE(slave->target_last_arp_rx[slot], last_rx); targets[slot] = target; } } @@ -1221,8 +1221,8 @@ static int bond_option_arp_ip_target_rem(struct bonding *bond, __be32 target) bond_for_each_slave(bond, slave, iter) { targets_rx = slave->target_last_arp_rx; for (i = ind; (i < BOND_MAX_ARP_TARGETS-1) && targets[i+1]; i++) - targets_rx[i] = targets_rx[i+1]; - targets_rx[i] = 0; + WRITE_ONCE(targets_rx[i], READ_ONCE(targets_rx[i+1])); + WRITE_ONCE(targets_rx[i], 0); } for (i = ind; (i < BOND_MAX_ARP_TARGETS-1) && targets[i+1]; i++) targets[i] = targets[i+1]; @@ -1377,7 +1377,7 @@ static void _bond_options_ns_ip6_target_set(struct bonding *bond, int slot, if (slot >= 0 && slot < BOND_MAX_NS_TARGETS) { bond_for_each_slave(bond, slave, iter) { - slave->target_last_arp_rx[slot] = last_rx; + WRITE_ONCE(slave->target_last_arp_rx[slot], last_rx); slave_set_ns_maddr(bond, slave, target, &targets[slot]); } targets[slot] = *target; diff --git a/include/net/bonding.h b/include/net/bonding.h index 49edc7da0586..462078403557 100644 --- a/include/net/bonding.h +++ b/include/net/bonding.h @@ -521,13 +521,14 @@ static inline int bond_is_ip6_target_ok(struct in6_addr *addr) static inline unsigned long slave_oldest_target_arp_rx(struct bonding *bond, struct slave *slave) { + unsigned long tmp, ret = READ_ONCE(slave->target_last_arp_rx[0]); int i = 1; - unsigned long ret = slave->target_last_arp_rx[0]; - - for (; (i < BOND_MAX_ARP_TARGETS) && bond->params.arp_targets[i]; i++) - if (time_before(slave->target_last_arp_rx[i], ret)) - ret = slave->target_last_arp_rx[i]; + for (; (i < BOND_MAX_ARP_TARGETS) && bond->params.arp_targets[i]; i++) { + tmp = READ_ONCE(slave->target_last_arp_rx[i]); + if (time_before(tmp, ret)) + ret = tmp; + } return ret; } @@ -537,7 +538,7 @@ static inline unsigned long slave_last_rx(struct bonding *bond, if (bond->params.arp_all_targets == BOND_ARP_TARGETS_ALL) return slave_oldest_target_arp_rx(bond, slave); - return slave->last_rx; + return READ_ONCE(slave->last_rx); } static inline void slave_update_last_tx(struct slave *slave) -- cgit v1.2.3 From 19a412b66df7cddbc1fa87e049c56bacf00adb27 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jan 2026 00:14:18 +0000 Subject: ASoC: soc-component: remove snd_soc_component_xxx() wrapper Now no one is using snd_soc_component_xxx() wrapper for dapm. Remove it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ms29qgx2.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 21 ----------- sound/soc/soc-component.c | 82 ------------------------------------------- 2 files changed, 103 deletions(-) (limited to 'include') diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index d78cda866888..8b34958395ca 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -368,27 +368,6 @@ snd_soc_component_active(struct snd_soc_component *component) return component->active; } -/* component pin */ -int snd_soc_component_enable_pin(struct snd_soc_component *component, - const char *pin); -int snd_soc_component_enable_pin_unlocked(struct snd_soc_component *component, - const char *pin); -int snd_soc_component_disable_pin(struct snd_soc_component *component, - const char *pin); -int snd_soc_component_disable_pin_unlocked(struct snd_soc_component *component, - const char *pin); -int snd_soc_component_nc_pin(struct snd_soc_component *component, - const char *pin); -int snd_soc_component_nc_pin_unlocked(struct snd_soc_component *component, - const char *pin); -int snd_soc_component_get_pin_status(struct snd_soc_component *component, - const char *pin); -int snd_soc_component_force_enable_pin(struct snd_soc_component *component, - const char *pin); -int snd_soc_component_force_enable_pin_unlocked( - struct snd_soc_component *component, - const char *pin); - /* component controls */ struct snd_kcontrol *snd_soc_component_get_kcontrol(struct snd_soc_component *component, const char * const ctl); diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index c815fd1b3fd1..89f236ab3034 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -142,88 +142,6 @@ int snd_soc_component_set_bias_level(struct snd_soc_component *component, return soc_component_ret(component, ret); } -int snd_soc_component_enable_pin(struct snd_soc_component *component, - const char *pin) -{ - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); - return snd_soc_dapm_enable_pin(dapm, pin); -} -EXPORT_SYMBOL_GPL(snd_soc_component_enable_pin); - -int snd_soc_component_enable_pin_unlocked(struct snd_soc_component *component, - const char *pin) -{ - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); - return snd_soc_dapm_enable_pin_unlocked(dapm, pin); -} -EXPORT_SYMBOL_GPL(snd_soc_component_enable_pin_unlocked); - -int snd_soc_component_disable_pin(struct snd_soc_component *component, - const char *pin) -{ - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); - return snd_soc_dapm_disable_pin(dapm, pin); -} -EXPORT_SYMBOL_GPL(snd_soc_component_disable_pin); - -int snd_soc_component_disable_pin_unlocked(struct snd_soc_component *component, - const char *pin) -{ - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); - return snd_soc_dapm_disable_pin_unlocked(dapm, pin); -} -EXPORT_SYMBOL_GPL(snd_soc_component_disable_pin_unlocked); - -int snd_soc_component_nc_pin(struct snd_soc_component *component, - const char *pin) -{ - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); - return snd_soc_dapm_nc_pin(dapm, pin); -} -EXPORT_SYMBOL_GPL(snd_soc_component_nc_pin); - -int snd_soc_component_nc_pin_unlocked(struct snd_soc_component *component, - const char *pin) -{ - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); - return snd_soc_dapm_nc_pin_unlocked(dapm, pin); -} -EXPORT_SYMBOL_GPL(snd_soc_component_nc_pin_unlocked); - -int snd_soc_component_get_pin_status(struct snd_soc_component *component, - const char *pin) -{ - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); - return snd_soc_dapm_get_pin_status(dapm, pin); -} -EXPORT_SYMBOL_GPL(snd_soc_component_get_pin_status); - -int snd_soc_component_force_enable_pin(struct snd_soc_component *component, - const char *pin) -{ - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); - return snd_soc_dapm_force_enable_pin(dapm, pin); -} -EXPORT_SYMBOL_GPL(snd_soc_component_force_enable_pin); - -int snd_soc_component_force_enable_pin_unlocked( - struct snd_soc_component *component, - const char *pin) -{ - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(component); - return snd_soc_dapm_force_enable_pin_unlocked(dapm, pin); -} -EXPORT_SYMBOL_GPL(snd_soc_component_force_enable_pin_unlocked); - static void soc_get_kcontrol_name(struct snd_soc_component *component, char *buf, int size, const char * const ctl) { -- cgit v1.2.3 From d8b795f65217dd033daac5147eba6acb73a9a489 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jan 2026 00:14:27 +0000 Subject: ASoC: soc-component: remove compatibility definition for component All drivers uses new functions. Remove comaptibility definition. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ldhtqgws.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 3 --- 1 file changed, 3 deletions(-) (limited to 'include') diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 8b34958395ca..e538784746db 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -271,9 +271,6 @@ static inline struct snd_soc_dapm_context *snd_soc_component_to_dapm( return &component->dapm; } -// FIXME -#define snd_soc_component_get_dapm snd_soc_component_to_dapm - /** * snd_soc_component_cache_sync() - Sync the register cache with the hardware * @component: COMPONENT to sync -- cgit v1.2.3 From 40ff409eacac686bda70ce7720d8b0e7c7401635 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jan 2026 00:14:36 +0000 Subject: ASoC: soc-dapm: remove compatibility definition for dapm All drivers uses new functions. Remove comaptibility definition. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87jyxdqgwk.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 19 ------------------- 1 file changed, 19 deletions(-) (limited to 'include') diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 75941324886b..7d3ba3826076 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -705,16 +705,6 @@ int snd_soc_dapm_force_enable_pin_unlocked(struct snd_soc_dapm_context *dapm, co int snd_soc_dapm_ignore_suspend(struct snd_soc_dapm_context *dapm, const char *pin); void snd_soc_dapm_mark_endpoints_dirty(struct snd_soc_card *card); -/* - * Marks the specified pin as being not connected, disabling it along - * any parent or child widgets. At present this is identical to - * snd_soc_dapm_disable_pin[_unlocked]() but in future it will be extended to do - * additional things such as disabling controls which only affect - * paths through the pin. - */ -#define snd_soc_dapm_nc_pin snd_soc_dapm_disable_pin -#define snd_soc_dapm_nc_pin_unlocked snd_soc_dapm_disable_pin_unlocked - /* dapm path query */ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, struct snd_soc_dapm_widget_list **list, @@ -730,15 +720,6 @@ int snd_soc_dapm_force_bias_level(struct snd_soc_dapm_context *dapm, enum snd_so enum snd_soc_bias_level snd_soc_dapm_get_bias_level(struct snd_soc_dapm_context *dapm); void snd_soc_dapm_init_bias_level(struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level); -// REMOVE ME !! -#define snd_soc_component_force_bias_level(c, l) snd_soc_dapm_force_bias_level(&(c)->dapm, l) -#define snd_soc_component_get_bias_level(c) snd_soc_dapm_get_bias_level(&(c)->dapm) -#define snd_soc_component_init_bias_level(c, l) snd_soc_dapm_init_bias_level(&(c)->dapm, l) -#define snd_soc_dapm_kcontrol_widget snd_soc_dapm_kcontrol_to_widget -#define snd_soc_dapm_kcontrol_dapm snd_soc_dapm_kcontrol_to_dapm -#define dapm_kcontrol_get_value snd_soc_dapm_kcontrol_get_value -#define snd_soc_dapm_kcontrol_component snd_soc_dapm_kcontrol_to_component - #define for_each_dapm_widgets(list, i, widget) \ for ((i) = 0; \ (i) < list->num_widgets && (widget = list->widgets[i]); \ -- cgit v1.2.3 From cf0e8c555b34b0ea3d2a41edf6dc214239a71c80 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jan 2026 00:14:46 +0000 Subject: ASoC: soc-dapm: remove dev from snd_soc_dapm_context() We can get dev via snd_soc_dapm_to_dev(). Remove it. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87ikcxqgw9.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 1 - sound/soc/soc-dapm.c | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) (limited to 'include') diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 7d3ba3826076..010d63db5436 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -585,7 +585,6 @@ struct snd_soc_dapm_context { bool idle_bias; /* Use BIAS_OFF instead of STANDBY when false */ - struct device *dev; /* from parent - for debug */ /* REMOVE ME */ struct snd_soc_component *component; /* parent component */ struct snd_soc_card *card; /* parent card */ diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 4d920a59da3c..4c2007c61ca1 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -4862,12 +4862,8 @@ void snd_soc_dapm_init(struct snd_soc_dapm_context *dapm, dapm->component = component; dapm->bias_level = SND_SOC_BIAS_OFF; - if (component) { - dapm->dev = component->dev; + if (component) dapm->idle_bias = component->driver->idle_bias_on; - } else { - dapm->dev = card->dev; - } INIT_LIST_HEAD(&dapm->list); /* see for_each_card_dapms */ -- cgit v1.2.3 From 13c84b4c6f218c196c9c72286645247996800427 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jan 2026 00:14:54 +0000 Subject: ASoC: soc-dapm: add snd_soc_dapm_alloc() Because struct snd_soc_dapm_context is soc-dapm framework specific, user driver don't need to access its member directly, we would like to hide them. struct snd_soc_dapm_context will be removed from header in the future. Current card/component are using dapm_context instance. But it will be moved to soc-dapm.c, and we can use will be only pointer. Makes it to pointer. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87h5shqgw1.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 5 ++--- include/sound/soc-dapm.h | 2 ++ include/sound/soc.h | 4 ++-- sound/soc/soc-core.c | 8 ++++++++ sound/soc/soc-dapm.c | 7 ++++++- 5 files changed, 20 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index e538784746db..2a2b74b24a60 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -237,8 +237,7 @@ struct snd_soc_component { * the driver will be marked as BROKEN when these fields are removed. */ - /* Don't use these, use snd_soc_component_get_dapm() */ - struct snd_soc_dapm_context dapm; + struct snd_soc_dapm_context *dapm; /* machine specific init */ int (*init)(struct snd_soc_component *component); @@ -268,7 +267,7 @@ struct snd_soc_component { static inline struct snd_soc_dapm_context *snd_soc_component_to_dapm( struct snd_soc_component *component) { - return &component->dapm; + return component->dapm; } /** diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 010d63db5436..6f3e1b57cda3 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -627,6 +627,8 @@ enum snd_soc_dapm_direction { #define SND_SOC_DAPM_EP_SOURCE SND_SOC_DAPM_DIR_TO_EP(SND_SOC_DAPM_DIR_IN) #define SND_SOC_DAPM_EP_SINK SND_SOC_DAPM_DIR_TO_EP(SND_SOC_DAPM_DIR_OUT) +struct snd_soc_dapm_context *snd_soc_dapm_alloc(struct device *dev); + int snd_soc_dapm_regulator_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); int snd_soc_dapm_clock_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); int snd_soc_dapm_pinctrl_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); diff --git a/include/sound/soc.h b/include/sound/soc.h index aa0fe6b80293..7d8376c8e1be 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1076,7 +1076,7 @@ struct snd_soc_card { struct list_head dobj_list; /* Generic DAPM context for the card */ - struct snd_soc_dapm_context dapm; + struct snd_soc_dapm_context *dapm; struct snd_soc_dapm_stats dapm_stats; #ifdef CONFIG_DEBUG_FS @@ -1136,7 +1136,7 @@ static inline int snd_soc_card_is_instantiated(struct snd_soc_card *card) static inline struct snd_soc_dapm_context *snd_soc_card_to_dapm(struct snd_soc_card *card) { - return &card->dapm; + return card->dapm; } /* SoC machine DAI configuration, glues a codec and cpu DAI together */ diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e4b21bf39e59..355ccc95f28b 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2556,6 +2556,10 @@ int snd_soc_register_card(struct snd_soc_card *card) if (!card->name || !card->dev) return -EINVAL; + card->dapm = snd_soc_dapm_alloc(card->dev); + if (!card->dapm) + return -ENOMEM; + dev_set_drvdata(card->dev, card); INIT_LIST_HEAD(&card->widgets); @@ -2840,6 +2844,10 @@ int snd_soc_component_initialize(struct snd_soc_component *component, const struct snd_soc_component_driver *driver, struct device *dev) { + component->dapm = snd_soc_dapm_alloc(dev); + if (!component->dapm) + return -ENOMEM; + INIT_LIST_HEAD(&component->dai_list); INIT_LIST_HEAD(&component->dobj_list); INIT_LIST_HEAD(&component->card_list); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 4c2007c61ca1..7aef57dcb2a7 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -165,6 +165,11 @@ static void pop_dbg(struct device *dev, u32 pop_time, const char *fmt, ...) kfree(buf); } +struct snd_soc_dapm_context *snd_soc_dapm_alloc(struct device *dev) +{ + return devm_kzalloc(dev, sizeof(struct snd_soc_dapm_context), GFP_KERNEL); +} + struct device *snd_soc_dapm_to_dev(struct snd_soc_dapm_context *dapm) { if (dapm->component) @@ -1076,7 +1081,7 @@ static int snd_soc_dapm_set_bias_level(struct snd_soc_dapm_context *dapm, if (ret != 0) goto out; - if (dapm != &card->dapm) + if (dapm != card->dapm) ret = snd_soc_dapm_force_bias_level(dapm, level); if (ret != 0) -- cgit v1.2.3 From 5b517f1a5cace3cba9a48491706e330848ecef86 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jan 2026 00:15:01 +0000 Subject: ASoC: soc-dapm: move struct snd_soc_dapm_context All drivers are now using new dapm functions. Move struct snd_soc_dapm_context to soc-dapm.c Suggested-by: Cezary Rojewski Link: https://lore.kernel.org/r/87o6x69h4y.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87fr81qgvu.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 22 +--------------------- sound/soc/soc-dapm.c | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 21 deletions(-) (limited to 'include') diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 6f3e1b57cda3..49f0fe05db01 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -20,6 +20,7 @@ struct regulator; struct soc_enum; struct snd_pcm_substream; struct snd_soc_pcm_runtime; +struct snd_soc_dapm_context; /* widget has no PM register bit */ #define SND_SOC_NOPM -1 @@ -579,27 +580,6 @@ struct snd_soc_dapm_update { bool has_second_set; }; -/* DAPM context */ -struct snd_soc_dapm_context { - enum snd_soc_bias_level bias_level; - - bool idle_bias; /* Use BIAS_OFF instead of STANDBY when false */ - - struct snd_soc_component *component; /* parent component */ - struct snd_soc_card *card; /* parent card */ - - /* used during DAPM updates */ - enum snd_soc_bias_level target_bias_level; - struct list_head list; - - struct snd_soc_dapm_widget *wcache_sink; - struct snd_soc_dapm_widget *wcache_source; - -#ifdef CONFIG_DEBUG_FS - struct dentry *debugfs_dapm; -#endif -}; - /* A list of widgets associated with an object, typically a snd_kcontrol */ struct snd_soc_dapm_widget_list { int num_widgets; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 7aef57dcb2a7..07370215ea7c 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -40,6 +40,27 @@ #include +/* DAPM context */ +struct snd_soc_dapm_context { + enum snd_soc_bias_level bias_level; + + bool idle_bias; /* Use BIAS_OFF instead of STANDBY when false */ + + struct snd_soc_component *component; /* parent component */ + struct snd_soc_card *card; /* parent card */ + + /* used during DAPM updates */ + enum snd_soc_bias_level target_bias_level; + struct list_head list; + + struct snd_soc_dapm_widget *wcache_sink; + struct snd_soc_dapm_widget *wcache_source; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_dapm; +#endif +}; + #define DAPM_UPDATE_STAT(widget, val) widget->dapm->card->dapm_stats.val++; #define SND_SOC_DAPM_DIR_REVERSE(x) ((x == SND_SOC_DAPM_DIR_IN) ? \ -- cgit v1.2.3 From 19b08fd23b20593ebe43708308dbddb02507877d Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Fri, 23 Jan 2026 16:25:01 +0800 Subject: ASoC: fsl_sai: Add AUDMIX mode support on i.MX952 One of SAI interfaces is connected to AUDMIX in the i.MX952 chip, but AUDMIX can be bypassed or not bypassed on the i.MX952 platform. There are three use cases: 1) SAI -> Codec (No AUDMIX between SAI and Codec) 2) SAI -> Codec (Has AUDMIX, but AUDMIX is bypassed) 3) SAI -> AUDMIX -> Codec (Has AUDMIX and used) So add 'fsl,sai-amix-mode' property for this feature fsl,sai-amix-mode = "none": is for case 1) fsl,sai-amix-mode = "bypass": is for case 2) fsl,sai-amix-mode = "audmix": is for case 3) Signed-off-by: Shengjiu Wang Link: https://patch.msgid.link/20260123082501.4050296-5-shengjiu.wang@nxp.com Signed-off-by: Mark Brown --- include/linux/firmware/imx/sm.h | 2 ++ sound/soc/fsl/fsl_sai.c | 21 +++++++++++++++++++++ sound/soc/fsl/fsl_sai.h | 4 ++++ 3 files changed, 27 insertions(+) (limited to 'include') diff --git a/include/linux/firmware/imx/sm.h b/include/linux/firmware/imx/sm.h index a33b45027356..ba5d93bd6158 100644 --- a/include/linux/firmware/imx/sm.h +++ b/include/linux/firmware/imx/sm.h @@ -26,6 +26,8 @@ #define SCMI_IMX94_CTRL_SAI3_MCLK 5U /*!< WAKE SAI3 MCLK */ #define SCMI_IMX94_CTRL_SAI4_MCLK 6U /*!< WAKE SAI4 MCLK */ +#define SCMI_IMX952_CTRL_BYPASS_AUDMIX 8U /* WAKE AUDMIX */ + #if IS_ENABLED(CONFIG_IMX_SCMI_MISC_DRV) int scmi_imx_misc_ctrl_get(u32 id, u32 *num, u32 *val); int scmi_imx_misc_ctrl_set(u32 id, u32 val); diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index 2fa14fbdfe1a..148e09e58dfa 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -1425,10 +1426,12 @@ static int fsl_sai_probe(struct platform_device *pdev) struct fsl_sai *sai; struct regmap *gpr; void __iomem *base; + const char *str = NULL; char tmp[8]; int irq, ret, i; int index; u32 dmas[4]; + u32 val; sai = devm_kzalloc(dev, sizeof(*sai), GFP_KERNEL); if (!sai) @@ -1598,6 +1601,24 @@ static int fsl_sai_probe(struct platform_device *pdev) if (ret < 0 && ret != -ENOSYS) goto err_pm_get_sync; + if (of_device_is_compatible(np, "fsl,imx952-sai") && + !of_property_read_string(np, "fsl,sai-amix-mode", &str)) { + if (!strcmp(str, "bypass")) + val = FSL_SAI_AMIX_BYPASS; + else if (!strcmp(str, "audmix")) + val = FSL_SAI_AMIX_AUDMIX; + else + val = FSL_SAI_AMIX_NONE; + + if (val < FSL_SAI_AMIX_NONE) { + ret = scmi_imx_misc_ctrl_set(SCMI_IMX952_CTRL_BYPASS_AUDMIX, val); + if (ret) { + dev_err_probe(dev, ret, "Error setting audmix mode\n"); + goto err_pm_get_sync; + } + } + } + /* * Register platform component before registering cpu dai for there * is not defer probe for platform component in snd_soc_add_pcm_runtime(). diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h index 6c917f79c6b0..7605cbaca3d8 100644 --- a/sound/soc/fsl/fsl_sai.h +++ b/sound/soc/fsl/fsl_sai.h @@ -230,6 +230,10 @@ #define FSL_SAI_DL_I2S BIT(0) #define FSL_SAI_DL_PDM BIT(1) +#define FSL_SAI_AMIX_BYPASS 0 +#define FSL_SAI_AMIX_AUDMIX 1 +#define FSL_SAI_AMIX_NONE 2 + struct fsl_sai_soc_data { bool use_imx_pcm; bool use_edma; -- cgit v1.2.3 From 9b47d4eea3f7c1f620e95bda1d6221660bde7d7b Mon Sep 17 00:00:00 2001 From: Andrey Ryabinin Date: Tue, 13 Jan 2026 20:15:15 +0100 Subject: mm/kasan: fix KASAN poisoning in vrealloc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A KASAN warning can be triggered when vrealloc() changes the requested size to a value that is not aligned to KASAN_GRANULE_SIZE. ------------[ cut here ]------------ WARNING: CPU: 2 PID: 1 at mm/kasan/shadow.c:174 kasan_unpoison+0x40/0x48 ... pc : kasan_unpoison+0x40/0x48 lr : __kasan_unpoison_vmalloc+0x40/0x68 Call trace: kasan_unpoison+0x40/0x48 (P) vrealloc_node_align_noprof+0x200/0x320 bpf_patch_insn_data+0x90/0x2f0 convert_ctx_accesses+0x8c0/0x1158 bpf_check+0x1488/0x1900 bpf_prog_load+0xd20/0x1258 __sys_bpf+0x96c/0xdf0 __arm64_sys_bpf+0x50/0xa0 invoke_syscall+0x90/0x160 Introduce a dedicated kasan_vrealloc() helper that centralizes KASAN handling for vmalloc reallocations. The helper accounts for KASAN granule alignment when growing or shrinking an allocation and ensures that partial granules are handled correctly. Use this helper from vrealloc_node_align_noprof() to fix poisoning logic. [ryabinin.a.a@gmail.com: move kasan_enabled() check, fix build] Link: https://lkml.kernel.org/r/20260119144509.32767-1-ryabinin.a.a@gmail.com Link: https://lkml.kernel.org/r/20260113191516.31015-1-ryabinin.a.a@gmail.com Fixes: d699440f58ce ("mm: fix vrealloc()'s KASAN poisoning logic") Signed-off-by: Andrey Ryabinin Reported-by: Maciej Żenczykowski Reported-by: Closes: https://lkml.kernel.org/r/CANP3RGeuRW53vukDy7WDO3FiVgu34-xVJYkfpm08oLO3odYFrA@mail.gmail.com Reviewed-by: Andrey Konovalov Tested-by: Maciej Wieczor-Retman Cc: Alexander Potapenko Cc: Dmitriy Vyukov Cc: Dmitry Vyukov Cc: Uladzislau Rezki Cc: Vincenzo Frascino Cc: Signed-off-by: Andrew Morton --- include/linux/kasan.h | 14 ++++++++++++++ mm/kasan/common.c | 21 +++++++++++++++++++++ mm/vmalloc.c | 7 ++----- 3 files changed, 37 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/include/linux/kasan.h b/include/linux/kasan.h index 9c6ac4b62eb9..338a1921a50a 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -641,6 +641,17 @@ kasan_unpoison_vmap_areas(struct vm_struct **vms, int nr_vms, __kasan_unpoison_vmap_areas(vms, nr_vms, flags); } +void __kasan_vrealloc(const void *start, unsigned long old_size, + unsigned long new_size); + +static __always_inline void kasan_vrealloc(const void *start, + unsigned long old_size, + unsigned long new_size) +{ + if (kasan_enabled()) + __kasan_vrealloc(start, old_size, new_size); +} + #else /* CONFIG_KASAN_VMALLOC */ static inline void kasan_populate_early_vm_area_shadow(void *start, @@ -670,6 +681,9 @@ kasan_unpoison_vmap_areas(struct vm_struct **vms, int nr_vms, kasan_vmalloc_flags_t flags) { } +static inline void kasan_vrealloc(const void *start, unsigned long old_size, + unsigned long new_size) { } + #endif /* CONFIG_KASAN_VMALLOC */ #if (defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)) && \ diff --git a/mm/kasan/common.c b/mm/kasan/common.c index ed489a14dddf..b7d05c2a6d93 100644 --- a/mm/kasan/common.c +++ b/mm/kasan/common.c @@ -606,4 +606,25 @@ void __kasan_unpoison_vmap_areas(struct vm_struct **vms, int nr_vms, __kasan_unpoison_vmalloc(addr, size, flags | KASAN_VMALLOC_KEEP_TAG); } } + +void __kasan_vrealloc(const void *addr, unsigned long old_size, + unsigned long new_size) +{ + if (new_size < old_size) { + kasan_poison_last_granule(addr, new_size); + + new_size = round_up(new_size, KASAN_GRANULE_SIZE); + old_size = round_up(old_size, KASAN_GRANULE_SIZE); + if (new_size < old_size) + __kasan_poison_vmalloc(addr + new_size, + old_size - new_size); + } else if (new_size > old_size) { + old_size = round_down(old_size, KASAN_GRANULE_SIZE); + __kasan_unpoison_vmalloc(addr + old_size, + new_size - old_size, + KASAN_VMALLOC_PROT_NORMAL | + KASAN_VMALLOC_VM_ALLOC | + KASAN_VMALLOC_KEEP_TAG); + } +} #endif diff --git a/mm/vmalloc.c b/mm/vmalloc.c index 628f96e83b11..e286c2d2068c 100644 --- a/mm/vmalloc.c +++ b/mm/vmalloc.c @@ -4322,7 +4322,7 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align if (want_init_on_free() || want_init_on_alloc(flags)) memset((void *)p + size, 0, old_size - size); vm->requested_size = size; - kasan_poison_vmalloc(p + size, old_size - size); + kasan_vrealloc(p, old_size, size); return (void *)p; } @@ -4330,16 +4330,13 @@ void *vrealloc_node_align_noprof(const void *p, size_t size, unsigned long align * We already have the bytes available in the allocation; use them. */ if (size <= alloced_size) { - kasan_unpoison_vmalloc(p + old_size, size - old_size, - KASAN_VMALLOC_PROT_NORMAL | - KASAN_VMALLOC_VM_ALLOC | - KASAN_VMALLOC_KEEP_TAG); /* * No need to zero memory here, as unused memory will have * already been zeroed at initial allocation time or during * realloc shrink time. */ vm->requested_size = size; + kasan_vrealloc(p, old_size, size); return (void *)p; } -- cgit v1.2.3 From 71e2b5eadbad43d33f0e2cf6d767395273ba5eaa Mon Sep 17 00:00:00 2001 From: "Pratyush Yadav (Google)" Date: Thu, 22 Jan 2026 16:18:39 +0100 Subject: memfd: export alloc_file() Patch series "mm: memfd_luo hotfixes". This series contains a couple of fixes for memfd preservation using LUO. This patch (of 3): The Live Update Orchestrator's (LUO) memfd preservation works by preserving all the folios of a memfd, re-creating an empty memfd on the next boot, and then inserting back the preserved folios. Currently it creates the file by directly calling shmem_file_setup(). This leaves out other work done by alloc_file() like setting up the file mode, flags, or calling the security hooks. Export alloc_file() to let memfd_luo use it. Rename it to memfd_alloc_file() since it is no longer private and thus needs a subsystem prefix. Link: https://lkml.kernel.org/r/20260122151842.4069702-1-pratyush@kernel.org Link: https://lkml.kernel.org/r/20260122151842.4069702-2-pratyush@kernel.org Signed-off-by: Pratyush Yadav (Google) Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Pasha Tatashin Cc: Baolin Wang Cc: Hugh Dickins Signed-off-by: Andrew Morton --- include/linux/memfd.h | 6 ++++++ mm/memfd.c | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/memfd.h b/include/linux/memfd.h index cc74de3dbcfe..c328a7b356d0 100644 --- a/include/linux/memfd.h +++ b/include/linux/memfd.h @@ -17,6 +17,7 @@ struct folio *memfd_alloc_folio(struct file *memfd, pgoff_t idx); * to by vm_flags_ptr. */ int memfd_check_seals_mmap(struct file *file, vm_flags_t *vm_flags_ptr); +struct file *memfd_alloc_file(const char *name, unsigned int flags); #else static inline long memfd_fcntl(struct file *f, unsigned int c, unsigned int a) { @@ -31,6 +32,11 @@ static inline int memfd_check_seals_mmap(struct file *file, { return 0; } + +static inline struct file *memfd_alloc_file(const char *name, unsigned int flags) +{ + return ERR_PTR(-EINVAL); +} #endif #endif /* __LINUX_MEMFD_H */ diff --git a/mm/memfd.c b/mm/memfd.c index ab5312aff14b..f032c6052926 100644 --- a/mm/memfd.c +++ b/mm/memfd.c @@ -456,7 +456,7 @@ err_name: return ERR_PTR(error); } -static struct file *alloc_file(const char *name, unsigned int flags) +struct file *memfd_alloc_file(const char *name, unsigned int flags) { unsigned int *file_seals; struct file *file; @@ -520,5 +520,5 @@ SYSCALL_DEFINE2(memfd_create, return PTR_ERR(name); fd_flags = (flags & MFD_CLOEXEC) ? O_CLOEXEC : 0; - return FD_ADD(fd_flags, alloc_file(name, flags)); + return FD_ADD(fd_flags, memfd_alloc_file(name, flags)); } -- cgit v1.2.3 From 12b2285bf3d14372238d36215b73af02ac3bdfc1 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Fri, 16 Jan 2026 12:10:16 +0100 Subject: mm/zone_device: reinitialize large zone device private folios MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reinitialize metadata for large zone device private folios in zone_device_page_init prior to creating a higher-order zone device private folio. This step is necessary when the folio's order changes dynamically between zone_device_page_init calls to avoid building a corrupt folio. As part of the metadata reinitialization, the dev_pagemap must be passed in from the caller because the pgmap stored in the folio page may have been overwritten with a compound head. Without this fix, individual pages could have invalid pgmap fields and flags (with PG_locked being notably problematic) due to prior different order allocations, which can, and will, result in kernel crashes. Link: https://lkml.kernel.org/r/20260116111325.1736137-2-francois.dugast@intel.com Fixes: d245f9b4ab80 ("mm/zone_device: support large zone device private folios") Signed-off-by: Matthew Brost Signed-off-by: Francois Dugast Acked-by: Felix Kuehling Reviewed-by: Balbir Singh Acked-by: Vlastimil Babka Cc: Zi Yan Cc: Alistair Popple Cc: Madhavan Srinivasan Cc: Nicholas Piggin Cc: Michael Ellerman Cc: "Christophe Leroy (CS GROUP)" Cc: Alex Deucher Cc: "Christian König" Cc: David Airlie Cc: Simona Vetter Cc: Maarten Lankhorst Cc: Maxime Ripard Cc: Thomas Zimmermann Cc: Lyude Paul Cc: Danilo Krummrich Cc: David Hildenbrand Cc: Oscar Salvador Cc: Andrew Morton Cc: Jason Gunthorpe Cc: Leon Romanovsky Cc: Lorenzo Stoakes Cc: Liam R. Howlett Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Michal Hocko Signed-off-by: Andrew Morton --- arch/powerpc/kvm/book3s_hv_uvmem.c | 2 +- drivers/gpu/drm/amd/amdkfd/kfd_migrate.c | 2 +- drivers/gpu/drm/drm_pagemap.c | 2 +- drivers/gpu/drm/nouveau/nouveau_dmem.c | 2 +- include/linux/memremap.h | 9 +++++--- lib/test_hmm.c | 4 +++- mm/memremap.c | 35 +++++++++++++++++++++++++++++++- 7 files changed, 47 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/arch/powerpc/kvm/book3s_hv_uvmem.c b/arch/powerpc/kvm/book3s_hv_uvmem.c index e5000bef90f2..7cf9310de0ec 100644 --- a/arch/powerpc/kvm/book3s_hv_uvmem.c +++ b/arch/powerpc/kvm/book3s_hv_uvmem.c @@ -723,7 +723,7 @@ static struct page *kvmppc_uvmem_get_page(unsigned long gpa, struct kvm *kvm) dpage = pfn_to_page(uvmem_pfn); dpage->zone_device_data = pvt; - zone_device_page_init(dpage, 0); + zone_device_page_init(dpage, &kvmppc_uvmem_pgmap, 0); return dpage; out_clear: spin_lock(&kvmppc_uvmem_bitmap_lock); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c index af53e796ea1b..6ada7b4af7c6 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_migrate.c @@ -217,7 +217,7 @@ svm_migrate_get_vram_page(struct svm_range *prange, unsigned long pfn) page = pfn_to_page(pfn); svm_range_bo_ref(prange->svm_bo); page->zone_device_data = prange->svm_bo; - zone_device_page_init(page, 0); + zone_device_page_init(page, page_pgmap(page), 0); } static void diff --git a/drivers/gpu/drm/drm_pagemap.c b/drivers/gpu/drm/drm_pagemap.c index 06c1bd8fc4d1..704f2f945019 100644 --- a/drivers/gpu/drm/drm_pagemap.c +++ b/drivers/gpu/drm/drm_pagemap.c @@ -197,7 +197,7 @@ static void drm_pagemap_get_devmem_page(struct page *page, struct drm_pagemap_zdd *zdd) { page->zone_device_data = drm_pagemap_zdd_get(zdd); - zone_device_page_init(page, 0); + zone_device_page_init(page, page_pgmap(page), 0); } /** diff --git a/drivers/gpu/drm/nouveau/nouveau_dmem.c b/drivers/gpu/drm/nouveau/nouveau_dmem.c index 58071652679d..3d8031296eed 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dmem.c +++ b/drivers/gpu/drm/nouveau/nouveau_dmem.c @@ -425,7 +425,7 @@ nouveau_dmem_page_alloc_locked(struct nouveau_drm *drm, bool is_large) order = ilog2(DMEM_CHUNK_NPAGES); } - zone_device_folio_init(folio, order); + zone_device_folio_init(folio, page_pgmap(folio_page(folio, 0)), order); return page; } diff --git a/include/linux/memremap.h b/include/linux/memremap.h index 713ec0435b48..e3c2ccf872a8 100644 --- a/include/linux/memremap.h +++ b/include/linux/memremap.h @@ -224,7 +224,8 @@ static inline bool is_fsdax_page(const struct page *page) } #ifdef CONFIG_ZONE_DEVICE -void zone_device_page_init(struct page *page, unsigned int order); +void zone_device_page_init(struct page *page, struct dev_pagemap *pgmap, + unsigned int order); void *memremap_pages(struct dev_pagemap *pgmap, int nid); void memunmap_pages(struct dev_pagemap *pgmap); void *devm_memremap_pages(struct device *dev, struct dev_pagemap *pgmap); @@ -234,9 +235,11 @@ bool pgmap_pfn_valid(struct dev_pagemap *pgmap, unsigned long pfn); unsigned long memremap_compat_align(void); -static inline void zone_device_folio_init(struct folio *folio, unsigned int order) +static inline void zone_device_folio_init(struct folio *folio, + struct dev_pagemap *pgmap, + unsigned int order) { - zone_device_page_init(&folio->page, order); + zone_device_page_init(&folio->page, pgmap, order); if (order) folio_set_large_rmappable(folio); } diff --git a/lib/test_hmm.c b/lib/test_hmm.c index 8af169d3873a..455a6862ae50 100644 --- a/lib/test_hmm.c +++ b/lib/test_hmm.c @@ -662,7 +662,9 @@ static struct page *dmirror_devmem_alloc_page(struct dmirror *dmirror, goto error; } - zone_device_folio_init(page_folio(dpage), order); + zone_device_folio_init(page_folio(dpage), + page_pgmap(folio_page(page_folio(dpage), 0)), + order); dpage->zone_device_data = rpage; return dpage; diff --git a/mm/memremap.c b/mm/memremap.c index 63c6ab4fdf08..ac7be07e3361 100644 --- a/mm/memremap.c +++ b/mm/memremap.c @@ -477,10 +477,43 @@ void free_zone_device_folio(struct folio *folio) } } -void zone_device_page_init(struct page *page, unsigned int order) +void zone_device_page_init(struct page *page, struct dev_pagemap *pgmap, + unsigned int order) { + struct page *new_page = page; + unsigned int i; + VM_WARN_ON_ONCE(order > MAX_ORDER_NR_PAGES); + for (i = 0; i < (1UL << order); ++i, ++new_page) { + struct folio *new_folio = (struct folio *)new_page; + + /* + * new_page could have been part of previous higher order folio + * which encodes the order, in page + 1, in the flags bits. We + * blindly clear bits which could have set my order field here, + * including page head. + */ + new_page->flags.f &= ~0xffUL; /* Clear possible order, page head */ + +#ifdef NR_PAGES_IN_LARGE_FOLIO + /* + * This pointer math looks odd, but new_page could have been + * part of a previous higher order folio, which sets _nr_pages + * in page + 1 (new_page). Therefore, we use pointer casting to + * correctly locate the _nr_pages bits within new_page which + * could have modified by previous higher order folio. + */ + ((struct folio *)(new_page - 1))->_nr_pages = 0; +#endif + + new_folio->mapping = NULL; + new_folio->pgmap = pgmap; /* Also clear compound head */ + new_folio->share = 0; /* fsdax only, unused for device private */ + VM_WARN_ON_FOLIO(folio_ref_count(new_folio), new_folio); + VM_WARN_ON_FOLIO(!folio_is_zone_device(new_folio), new_folio); + } + /* * Drivers shouldn't be allocating pages after calling * memunmap_pages(). -- cgit v1.2.3 From d7e1f9e84af460c5f1e5352eda8f036000cfcf0a Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 22 Jan 2026 20:44:57 +0800 Subject: ASoC: codec: Remove ak4641 Since commit d6df7df7ae5a0 ("ARM: pxa: remove unused board files"), there has been no in-tree user of the AK4641 codec driver. The last user (HP iPAQ hx4700) was a non-DT PXA board file that instantiated the device via I2C board data; that code was removed as part of the PXA board-file purge. The AK4641 driver was introduced ~2011 and still probes only via the I2C device-ID table ('.id_table'), without an 'of_match_table', so there are no upstream Devicetree users to retain. With no in-tree users left, remove the driver. Signed-off-by: Peng Fan Reviewed-by: Bartosz Golaszewski Acked-by: Andy Shevchenko Link: https://patch.msgid.link/20260122-sound-cleanup-v1-1-0a91901609b8@nxp.com Signed-off-by: Mark Brown --- include/sound/ak4641.h | 23 -- sound/soc/codecs/Kconfig | 6 - sound/soc/codecs/Makefile | 2 - sound/soc/codecs/ak4641.c | 641 ---------------------------------------------- 4 files changed, 672 deletions(-) delete mode 100644 include/sound/ak4641.h delete mode 100644 sound/soc/codecs/ak4641.c (limited to 'include') diff --git a/include/sound/ak4641.h b/include/sound/ak4641.h deleted file mode 100644 index 8b1941bbde52..000000000000 --- a/include/sound/ak4641.h +++ /dev/null @@ -1,23 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * AK4641 ALSA SoC Codec driver - * - * Copyright 2009 Philipp Zabel - */ - -#ifndef __AK4641_H -#define __AK4641_H - -/** - * struct ak4641_platform_data - platform specific AK4641 configuration - * @gpio_power: GPIO to control external power to AK4641 - * @gpio_npdn: GPIO connected to AK4641 nPDN pin - * - * Both GPIO parameters are optional. - */ -struct ak4641_platform_data { - int gpio_power; - int gpio_npdn; -}; - -#endif /* __AK4641_H */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 061791e61907..36eb872dd8fe 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -46,7 +46,6 @@ config SND_SOC_ALL_CODECS imply SND_SOC_AK4554 imply SND_SOC_AK4613 imply SND_SOC_AK4619 - imply SND_SOC_AK4641 imply SND_SOC_AK4642 imply SND_SOC_AK4671 imply SND_SOC_AK5386 @@ -624,11 +623,6 @@ config SND_SOC_AK4619 tristate "AKM AK4619 CODEC" depends on I2C -config SND_SOC_AK4641 - tristate - depends on I2C - depends on GPIOLIB_LEGACY - config SND_SOC_AK4642 tristate "AKM AK4642 CODEC" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index d687d4f74363..013a5fc65266 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -40,7 +40,6 @@ snd-soc-ak4535-y := ak4535.o snd-soc-ak4554-y := ak4554.o snd-soc-ak4613-y := ak4613.o snd-soc-ak4619-y := ak4619.o -snd-soc-ak4641-y := ak4641.o snd-soc-ak4642-y := ak4642.o snd-soc-ak4671-y := ak4671.o snd-soc-ak5386-y := ak5386.o @@ -472,7 +471,6 @@ obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK4554) += snd-soc-ak4554.o obj-$(CONFIG_SND_SOC_AK4613) += snd-soc-ak4613.o obj-$(CONFIG_SND_SOC_AK4619) += snd-soc-ak4619.o -obj-$(CONFIG_SND_SOC_AK4641) += snd-soc-ak4641.o obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o obj-$(CONFIG_SND_SOC_AK5386) += snd-soc-ak5386.o diff --git a/sound/soc/codecs/ak4641.c b/sound/soc/codecs/ak4641.c deleted file mode 100644 index 9db8cdb26d33..000000000000 --- a/sound/soc/codecs/ak4641.c +++ /dev/null @@ -1,641 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * ak4641.c -- AK4641 ALSA Soc Audio driver - * - * Copyright (C) 2008 Harald Welte - * Copyright (C) 2011 Dmitry Artamonow - * - * Based on ak4535.c by Richard Purdie - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* AK4641 register space */ -#define AK4641_PM1 0x00 -#define AK4641_PM2 0x01 -#define AK4641_SIG1 0x02 -#define AK4641_SIG2 0x03 -#define AK4641_MODE1 0x04 -#define AK4641_MODE2 0x05 -#define AK4641_DAC 0x06 -#define AK4641_MIC 0x07 -#define AK4641_TIMER 0x08 -#define AK4641_ALC1 0x09 -#define AK4641_ALC2 0x0a -#define AK4641_PGA 0x0b -#define AK4641_LATT 0x0c -#define AK4641_RATT 0x0d -#define AK4641_VOL 0x0e -#define AK4641_STATUS 0x0f -#define AK4641_EQLO 0x10 -#define AK4641_EQMID 0x11 -#define AK4641_EQHI 0x12 -#define AK4641_BTIF 0x13 - -/* codec private data */ -struct ak4641_priv { - struct regmap *regmap; - unsigned int sysclk; - int deemph; - int playback_fs; -}; - -/* - * ak4641 register cache - */ -static const struct reg_default ak4641_reg_defaults[] = { - { 0, 0x00 }, { 1, 0x80 }, { 2, 0x00 }, { 3, 0x80 }, - { 4, 0x02 }, { 5, 0x00 }, { 6, 0x11 }, { 7, 0x05 }, - { 8, 0x00 }, { 9, 0x00 }, { 10, 0x36 }, { 11, 0x10 }, - { 12, 0x00 }, { 13, 0x00 }, { 14, 0x57 }, { 15, 0x00 }, - { 16, 0x88 }, { 17, 0x88 }, { 18, 0x08 }, { 19, 0x08 } -}; - -static const int deemph_settings[] = {44100, 0, 48000, 32000}; - -static int ak4641_set_deemph(struct snd_soc_component *component) -{ - struct ak4641_priv *ak4641 = snd_soc_component_get_drvdata(component); - int i, best = 0; - - for (i = 0 ; i < ARRAY_SIZE(deemph_settings); i++) { - /* if deemphasis is on, select the nearest available rate */ - if (ak4641->deemph && deemph_settings[i] != 0 && - abs(deemph_settings[i] - ak4641->playback_fs) < - abs(deemph_settings[best] - ak4641->playback_fs)) - best = i; - - if (!ak4641->deemph && deemph_settings[i] == 0) - best = i; - } - - dev_dbg(component->dev, "Set deemphasis %d\n", best); - - return snd_soc_component_update_bits(component, AK4641_DAC, 0x3, best); -} - -static int ak4641_put_deemph(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct ak4641_priv *ak4641 = snd_soc_component_get_drvdata(component); - int deemph = ucontrol->value.integer.value[0]; - - if (deemph > 1) - return -EINVAL; - - ak4641->deemph = deemph; - - return ak4641_set_deemph(component); -} - -static int ak4641_get_deemph(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct ak4641_priv *ak4641 = snd_soc_component_get_drvdata(component); - - ucontrol->value.integer.value[0] = ak4641->deemph; - return 0; -}; - -static const char *ak4641_mono_out[] = {"(L + R)/2", "Hi-Z"}; -static const char *ak4641_hp_out[] = {"Stereo", "Mono"}; -static const char *ak4641_mic_select[] = {"Internal", "External"}; -static const char *ak4641_mic_or_dac[] = {"Microphone", "Voice DAC"}; - - -static const DECLARE_TLV_DB_SCALE(mono_gain_tlv, -1700, 2300, 0); -static const DECLARE_TLV_DB_SCALE(mic_boost_tlv, 0, 2000, 0); -static const DECLARE_TLV_DB_SCALE(eq_tlv, -1050, 150, 0); -static const DECLARE_TLV_DB_SCALE(master_tlv, -12750, 50, 0); -static const DECLARE_TLV_DB_SCALE(mic_stereo_sidetone_tlv, -2700, 300, 0); -static const DECLARE_TLV_DB_SCALE(mic_mono_sidetone_tlv, -400, 400, 0); -static const DECLARE_TLV_DB_SCALE(capture_tlv, -800, 50, 0); -static const DECLARE_TLV_DB_SCALE(alc_tlv, -800, 50, 0); -static const DECLARE_TLV_DB_SCALE(aux_in_tlv, -2100, 300, 0); - - -static SOC_ENUM_SINGLE_DECL(ak4641_mono_out_enum, - AK4641_SIG1, 6, ak4641_mono_out); -static SOC_ENUM_SINGLE_DECL(ak4641_hp_out_enum, - AK4641_MODE2, 2, ak4641_hp_out); -static SOC_ENUM_SINGLE_DECL(ak4641_mic_select_enum, - AK4641_MIC, 1, ak4641_mic_select); -static SOC_ENUM_SINGLE_DECL(ak4641_mic_or_dac_enum, - AK4641_BTIF, 4, ak4641_mic_or_dac); - -static const struct snd_kcontrol_new ak4641_snd_controls[] = { - SOC_ENUM("Mono 1 Output", ak4641_mono_out_enum), - SOC_SINGLE_TLV("Mono 1 Gain Volume", AK4641_SIG1, 7, 1, 1, - mono_gain_tlv), - SOC_ENUM("Headphone Output", ak4641_hp_out_enum), - SOC_SINGLE_BOOL_EXT("Playback Deemphasis Switch", 0, - ak4641_get_deemph, ak4641_put_deemph), - - SOC_SINGLE_TLV("Mic Boost Volume", AK4641_MIC, 0, 1, 0, mic_boost_tlv), - - SOC_SINGLE("ALC Operation Time", AK4641_TIMER, 0, 3, 0), - SOC_SINGLE("ALC Recovery Time", AK4641_TIMER, 2, 3, 0), - SOC_SINGLE("ALC ZC Time", AK4641_TIMER, 4, 3, 0), - - SOC_SINGLE("ALC 1 Switch", AK4641_ALC1, 5, 1, 0), - - SOC_SINGLE_TLV("ALC Volume", AK4641_ALC2, 0, 71, 0, alc_tlv), - SOC_SINGLE("Left Out Enable Switch", AK4641_SIG2, 1, 1, 0), - SOC_SINGLE("Right Out Enable Switch", AK4641_SIG2, 0, 1, 0), - - SOC_SINGLE_TLV("Capture Volume", AK4641_PGA, 0, 71, 0, capture_tlv), - - SOC_DOUBLE_R_TLV("Master Playback Volume", AK4641_LATT, - AK4641_RATT, 0, 255, 1, master_tlv), - - SOC_SINGLE_TLV("AUX In Volume", AK4641_VOL, 0, 15, 0, aux_in_tlv), - - SOC_SINGLE("Equalizer Switch", AK4641_DAC, 2, 1, 0), - SOC_SINGLE_TLV("EQ1 100 Hz Volume", AK4641_EQLO, 0, 15, 1, eq_tlv), - SOC_SINGLE_TLV("EQ2 250 Hz Volume", AK4641_EQLO, 4, 15, 1, eq_tlv), - SOC_SINGLE_TLV("EQ3 1 kHz Volume", AK4641_EQMID, 0, 15, 1, eq_tlv), - SOC_SINGLE_TLV("EQ4 3.5 kHz Volume", AK4641_EQMID, 4, 15, 1, eq_tlv), - SOC_SINGLE_TLV("EQ5 10 kHz Volume", AK4641_EQHI, 0, 15, 1, eq_tlv), -}; - -/* Mono 1 Mixer */ -static const struct snd_kcontrol_new ak4641_mono1_mixer_controls[] = { - SOC_DAPM_SINGLE_TLV("Mic Mono Sidetone Volume", AK4641_VOL, 7, 1, 0, - mic_mono_sidetone_tlv), - SOC_DAPM_SINGLE("Mic Mono Sidetone Switch", AK4641_SIG1, 4, 1, 0), - SOC_DAPM_SINGLE("Mono Playback Switch", AK4641_SIG1, 5, 1, 0), -}; - -/* Stereo Mixer */ -static const struct snd_kcontrol_new ak4641_stereo_mixer_controls[] = { - SOC_DAPM_SINGLE_TLV("Mic Sidetone Volume", AK4641_VOL, 4, 7, 0, - mic_stereo_sidetone_tlv), - SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4641_SIG2, 4, 1, 0), - SOC_DAPM_SINGLE("Playback Switch", AK4641_SIG2, 7, 1, 0), - SOC_DAPM_SINGLE("Aux Bypass Switch", AK4641_SIG2, 5, 1, 0), -}; - -/* Input Mixer */ -static const struct snd_kcontrol_new ak4641_input_mixer_controls[] = { - SOC_DAPM_SINGLE("Mic Capture Switch", AK4641_MIC, 2, 1, 0), - SOC_DAPM_SINGLE("Aux Capture Switch", AK4641_MIC, 5, 1, 0), -}; - -/* Mic mux */ -static const struct snd_kcontrol_new ak4641_mic_mux_control = - SOC_DAPM_ENUM("Mic Select", ak4641_mic_select_enum); - -/* Input mux */ -static const struct snd_kcontrol_new ak4641_input_mux_control = - SOC_DAPM_ENUM("Input Select", ak4641_mic_or_dac_enum); - -/* mono 2 switch */ -static const struct snd_kcontrol_new ak4641_mono2_control = - SOC_DAPM_SINGLE("Switch", AK4641_SIG1, 0, 1, 0); - -/* ak4641 dapm widgets */ -static const struct snd_soc_dapm_widget ak4641_dapm_widgets[] = { - SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0, - &ak4641_stereo_mixer_controls[0], - ARRAY_SIZE(ak4641_stereo_mixer_controls)), - SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0, - &ak4641_mono1_mixer_controls[0], - ARRAY_SIZE(ak4641_mono1_mixer_controls)), - SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, - &ak4641_input_mixer_controls[0], - ARRAY_SIZE(ak4641_input_mixer_controls)), - SND_SOC_DAPM_MUX("Mic Mux", SND_SOC_NOPM, 0, 0, - &ak4641_mic_mux_control), - SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, - &ak4641_input_mux_control), - SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0, - &ak4641_mono2_control), - - SND_SOC_DAPM_OUTPUT("LOUT"), - SND_SOC_DAPM_OUTPUT("ROUT"), - SND_SOC_DAPM_OUTPUT("MOUT1"), - SND_SOC_DAPM_OUTPUT("MOUT2"), - SND_SOC_DAPM_OUTPUT("MICOUT"), - - SND_SOC_DAPM_ADC("ADC", "HiFi Capture", AK4641_PM1, 0, 0), - SND_SOC_DAPM_PGA("Mic", AK4641_PM1, 1, 0, NULL, 0), - SND_SOC_DAPM_PGA("AUX In", AK4641_PM1, 2, 0, NULL, 0), - SND_SOC_DAPM_PGA("Mono Out", AK4641_PM1, 3, 0, NULL, 0), - SND_SOC_DAPM_PGA("Line Out", AK4641_PM1, 4, 0, NULL, 0), - - SND_SOC_DAPM_DAC("DAC", "HiFi Playback", AK4641_PM2, 0, 0), - SND_SOC_DAPM_PGA("Mono Out 2", AK4641_PM2, 3, 0, NULL, 0), - - SND_SOC_DAPM_ADC("Voice ADC", "Voice Capture", AK4641_BTIF, 0, 0), - SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", AK4641_BTIF, 1, 0), - - SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4641_MIC, 3, 0), - SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4641_MIC, 4, 0), - - SND_SOC_DAPM_INPUT("MICIN"), - SND_SOC_DAPM_INPUT("MICEXT"), - SND_SOC_DAPM_INPUT("AUX"), - SND_SOC_DAPM_INPUT("AIN"), -}; - -static const struct snd_soc_dapm_route ak4641_audio_map[] = { - /* Stereo Mixer */ - {"Stereo Mixer", "Playback Switch", "DAC"}, - {"Stereo Mixer", "Mic Sidetone Switch", "Input Mux"}, - {"Stereo Mixer", "Aux Bypass Switch", "AUX In"}, - - /* Mono 1 Mixer */ - {"Mono1 Mixer", "Mic Mono Sidetone Switch", "Input Mux"}, - {"Mono1 Mixer", "Mono Playback Switch", "DAC"}, - - /* Mic */ - {"Mic", NULL, "AIN"}, - {"Mic Mux", "Internal", "Mic Int Bias"}, - {"Mic Mux", "External", "Mic Ext Bias"}, - {"Mic Int Bias", NULL, "MICIN"}, - {"Mic Ext Bias", NULL, "MICEXT"}, - {"MICOUT", NULL, "Mic Mux"}, - - /* Input Mux */ - {"Input Mux", "Microphone", "Mic"}, - {"Input Mux", "Voice DAC", "Voice DAC"}, - - /* Line Out */ - {"LOUT", NULL, "Line Out"}, - {"ROUT", NULL, "Line Out"}, - {"Line Out", NULL, "Stereo Mixer"}, - - /* Mono 1 Out */ - {"MOUT1", NULL, "Mono Out"}, - {"Mono Out", NULL, "Mono1 Mixer"}, - - /* Mono 2 Out */ - {"MOUT2", NULL, "Mono 2 Enable"}, - {"Mono 2 Enable", "Switch", "Mono Out 2"}, - {"Mono Out 2", NULL, "Stereo Mixer"}, - - {"Voice ADC", NULL, "Mono 2 Enable"}, - - /* Aux In */ - {"AUX In", NULL, "AUX"}, - - /* ADC */ - {"ADC", NULL, "Input Mixer"}, - {"Input Mixer", "Mic Capture Switch", "Mic"}, - {"Input Mixer", "Aux Capture Switch", "AUX In"}, -}; - -static int ak4641_set_dai_sysclk(struct snd_soc_dai *codec_dai, - int clk_id, unsigned int freq, int dir) -{ - struct snd_soc_component *component = codec_dai->component; - struct ak4641_priv *ak4641 = snd_soc_component_get_drvdata(component); - - ak4641->sysclk = freq; - return 0; -} - -static int ak4641_i2s_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) -{ - struct snd_soc_component *component = dai->component; - struct ak4641_priv *ak4641 = snd_soc_component_get_drvdata(component); - int rate = params_rate(params), fs = 256; - u8 mode2; - - if (rate) - fs = ak4641->sysclk / rate; - else - return -EINVAL; - - /* set fs */ - switch (fs) { - case 1024: - mode2 = (0x2 << 5); - break; - case 512: - mode2 = (0x1 << 5); - break; - case 256: - mode2 = (0x0 << 5); - break; - default: - dev_err(component->dev, "Error: unsupported fs=%d\n", fs); - return -EINVAL; - } - - snd_soc_component_update_bits(component, AK4641_MODE2, (0x3 << 5), mode2); - - /* Update de-emphasis filter for the new rate */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - ak4641->playback_fs = rate; - ak4641_set_deemph(component); - } - - return 0; -} - -static int ak4641_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai, - unsigned int fmt) -{ - struct snd_soc_component *component = codec_dai->component; - u8 btif; - int ret; - - /* interface format */ - switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { - case SND_SOC_DAIFMT_I2S: - btif = (0x3 << 5); - break; - case SND_SOC_DAIFMT_LEFT_J: - btif = (0x2 << 5); - break; - case SND_SOC_DAIFMT_DSP_A: /* MSB after FRM */ - btif = (0x0 << 5); - break; - case SND_SOC_DAIFMT_DSP_B: /* MSB during FRM */ - btif = (0x1 << 5); - break; - default: - return -EINVAL; - } - - ret = snd_soc_component_update_bits(component, AK4641_BTIF, (0x3 << 5), btif); - if (ret < 0) - return ret; - - return 0; -} - -static int ak4641_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, - unsigned int fmt) -{ - struct snd_soc_component *component = codec_dai->component; - u8 mode1 = 0; - - /* interface format */ - switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { - case SND_SOC_DAIFMT_I2S: - mode1 = 0x02; - break; - case SND_SOC_DAIFMT_LEFT_J: - mode1 = 0x01; - break; - default: - return -EINVAL; - } - - return snd_soc_component_write(component, AK4641_MODE1, mode1); -} - -static int ak4641_mute(struct snd_soc_dai *dai, int mute, int direction) -{ - struct snd_soc_component *component = dai->component; - - return snd_soc_component_update_bits(component, AK4641_DAC, 0x20, mute ? 0x20 : 0); -} - -static int ak4641_set_bias_level(struct snd_soc_component *component, - enum snd_soc_bias_level level) -{ - struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); - struct ak4641_priv *ak4641 = snd_soc_component_get_drvdata(component); - struct ak4641_platform_data *pdata = component->dev->platform_data; - int ret; - - switch (level) { - case SND_SOC_BIAS_ON: - /* unmute */ - snd_soc_component_update_bits(component, AK4641_DAC, 0x20, 0); - break; - case SND_SOC_BIAS_PREPARE: - /* mute */ - snd_soc_component_update_bits(component, AK4641_DAC, 0x20, 0x20); - break; - case SND_SOC_BIAS_STANDBY: - if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_OFF) { - if (pdata && gpio_is_valid(pdata->gpio_power)) - gpio_set_value(pdata->gpio_power, 1); - mdelay(1); - if (pdata && gpio_is_valid(pdata->gpio_npdn)) - gpio_set_value(pdata->gpio_npdn, 1); - mdelay(1); - - ret = regcache_sync(ak4641->regmap); - if (ret) { - dev_err(component->dev, - "Failed to sync cache: %d\n", ret); - return ret; - } - } - snd_soc_component_update_bits(component, AK4641_PM1, 0x80, 0x80); - snd_soc_component_update_bits(component, AK4641_PM2, 0x80, 0); - break; - case SND_SOC_BIAS_OFF: - snd_soc_component_update_bits(component, AK4641_PM1, 0x80, 0); - if (pdata && gpio_is_valid(pdata->gpio_npdn)) - gpio_set_value(pdata->gpio_npdn, 0); - if (pdata && gpio_is_valid(pdata->gpio_power)) - gpio_set_value(pdata->gpio_power, 0); - regcache_mark_dirty(ak4641->regmap); - break; - } - return 0; -} - -#define AK4641_RATES (SNDRV_PCM_RATE_8000_48000) -#define AK4641_RATES_BT (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ - SNDRV_PCM_RATE_16000) -#define AK4641_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) - -static const struct snd_soc_dai_ops ak4641_i2s_dai_ops = { - .hw_params = ak4641_i2s_hw_params, - .set_fmt = ak4641_i2s_set_dai_fmt, - .mute_stream = ak4641_mute, - .set_sysclk = ak4641_set_dai_sysclk, - .no_capture_mute = 1, -}; - -static const struct snd_soc_dai_ops ak4641_pcm_dai_ops = { - .hw_params = NULL, /* rates are controlled by BT chip */ - .set_fmt = ak4641_pcm_set_dai_fmt, - .mute_stream = ak4641_mute, - .set_sysclk = ak4641_set_dai_sysclk, - .no_capture_mute = 1, -}; - -static struct snd_soc_dai_driver ak4641_dai[] = { -{ - .name = "ak4641-hifi", - .id = 1, - .playback = { - .stream_name = "HiFi Playback", - .channels_min = 1, - .channels_max = 2, - .rates = AK4641_RATES, - .formats = AK4641_FORMATS, - }, - .capture = { - .stream_name = "HiFi Capture", - .channels_min = 1, - .channels_max = 2, - .rates = AK4641_RATES, - .formats = AK4641_FORMATS, - }, - .ops = &ak4641_i2s_dai_ops, - .symmetric_rate = 1, -}, -{ - .name = "ak4641-voice", - .id = 1, - .playback = { - .stream_name = "Voice Playback", - .channels_min = 1, - .channels_max = 1, - .rates = AK4641_RATES_BT, - .formats = AK4641_FORMATS, - }, - .capture = { - .stream_name = "Voice Capture", - .channels_min = 1, - .channels_max = 1, - .rates = AK4641_RATES_BT, - .formats = AK4641_FORMATS, - }, - .ops = &ak4641_pcm_dai_ops, - .symmetric_rate = 1, -}, -}; - -static const struct snd_soc_component_driver soc_component_dev_ak4641 = { - .controls = ak4641_snd_controls, - .num_controls = ARRAY_SIZE(ak4641_snd_controls), - .dapm_widgets = ak4641_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(ak4641_dapm_widgets), - .dapm_routes = ak4641_audio_map, - .num_dapm_routes = ARRAY_SIZE(ak4641_audio_map), - .set_bias_level = ak4641_set_bias_level, - .suspend_bias_off = 1, - .idle_bias_on = 1, - .use_pmdown_time = 1, - .endianness = 1, -}; - -static const struct regmap_config ak4641_regmap = { - .reg_bits = 8, - .val_bits = 8, - - .max_register = AK4641_BTIF, - .reg_defaults = ak4641_reg_defaults, - .num_reg_defaults = ARRAY_SIZE(ak4641_reg_defaults), - .cache_type = REGCACHE_RBTREE, -}; - -static int ak4641_i2c_probe(struct i2c_client *i2c) -{ - struct ak4641_platform_data *pdata = i2c->dev.platform_data; - struct ak4641_priv *ak4641; - int ret; - - ak4641 = devm_kzalloc(&i2c->dev, sizeof(struct ak4641_priv), - GFP_KERNEL); - if (!ak4641) - return -ENOMEM; - - ak4641->regmap = devm_regmap_init_i2c(i2c, &ak4641_regmap); - if (IS_ERR(ak4641->regmap)) - return PTR_ERR(ak4641->regmap); - - if (pdata) { - if (gpio_is_valid(pdata->gpio_power)) { - ret = gpio_request_one(pdata->gpio_power, - GPIOF_OUT_INIT_LOW, "ak4641 power"); - if (ret) - goto err_out; - } - if (gpio_is_valid(pdata->gpio_npdn)) { - ret = gpio_request_one(pdata->gpio_npdn, - GPIOF_OUT_INIT_LOW, "ak4641 npdn"); - if (ret) - goto err_gpio; - - udelay(1); /* > 150 ns */ - gpio_set_value(pdata->gpio_npdn, 1); - } - } - - i2c_set_clientdata(i2c, ak4641); - - ret = devm_snd_soc_register_component(&i2c->dev, - &soc_component_dev_ak4641, - ak4641_dai, ARRAY_SIZE(ak4641_dai)); - if (ret != 0) - goto err_gpio2; - - return 0; - -err_gpio2: - if (pdata) { - if (gpio_is_valid(pdata->gpio_power)) - gpio_set_value(pdata->gpio_power, 0); - if (gpio_is_valid(pdata->gpio_npdn)) - gpio_free(pdata->gpio_npdn); - } -err_gpio: - if (pdata && gpio_is_valid(pdata->gpio_power)) - gpio_free(pdata->gpio_power); -err_out: - return ret; -} - -static void ak4641_i2c_remove(struct i2c_client *i2c) -{ - struct ak4641_platform_data *pdata = i2c->dev.platform_data; - - if (pdata) { - if (gpio_is_valid(pdata->gpio_power)) { - gpio_set_value(pdata->gpio_power, 0); - gpio_free(pdata->gpio_power); - } - if (gpio_is_valid(pdata->gpio_npdn)) - gpio_free(pdata->gpio_npdn); - } -} - -static const struct i2c_device_id ak4641_i2c_id[] = { - { "ak4641" }, - { } -}; -MODULE_DEVICE_TABLE(i2c, ak4641_i2c_id); - -static struct i2c_driver ak4641_i2c_driver = { - .driver = { - .name = "ak4641", - }, - .probe = ak4641_i2c_probe, - .remove = ak4641_i2c_remove, - .id_table = ak4641_i2c_id, -}; - -module_i2c_driver(ak4641_i2c_driver); - -MODULE_DESCRIPTION("SoC AK4641 driver"); -MODULE_AUTHOR("Harald Welte "); -MODULE_LICENSE("GPL"); -- cgit v1.2.3 From dc65b1ed4bb34ab6235ff2cc6a917b9295c04c2c Mon Sep 17 00:00:00 2001 From: Sheetal Date: Fri, 23 Jan 2026 15:23:44 +0530 Subject: regmap: Add reg_default_cb callback for flat cache defaults Commit e062bdfdd6ad ("regmap: warn users about uninitialized flat cache") warns when REGCACHE_FLAT is used without full defaults. This causes false positives on hardware where many registers reset to zero but are not listed in reg_defaults, forcing drivers to maintain large tables just to silence the warning. Add a reg_default_cb() hook so drivers can supply defaults for registers not present in reg_defaults when populating REGCACHE_FLAT. This keeps the warning quiet for known zero-reset registers without bloating tables. Provide a generic regmap_default_zero_cb() helper for drivers that need zero defaults. The hook is only used for REGCACHE_FLAT; the core does not check readable/writeable access, so drivers must provide readable_reg/ writeable_reg callbacks and handle holes in the register map. Signed-off-by: Sheetal Link: https://patch.msgid.link/20260123095346.1258556-3-sheetal@nvidia.com Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 3 +++ drivers/base/regmap/regcache-flat.c | 19 +++++++++++++++++++ drivers/base/regmap/regcache.c | 3 ++- drivers/base/regmap/regmap.c | 2 ++ include/linux/regmap.h | 14 ++++++++++++++ 5 files changed, 40 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 1477329410ec..5bf993165438 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -117,6 +117,9 @@ struct regmap { void *val_buf, size_t val_size); int (*write)(void *context, const void *data, size_t count); + int (*reg_default_cb)(struct device *dev, unsigned int reg, + unsigned int *val); + unsigned long read_flag_mask; unsigned long write_flag_mask; diff --git a/drivers/base/regmap/regcache-flat.c b/drivers/base/regmap/regcache-flat.c index 53cc59c84e2f..c924817e19b1 100644 --- a/drivers/base/regmap/regcache-flat.c +++ b/drivers/base/regmap/regcache-flat.c @@ -79,6 +79,25 @@ static int regcache_flat_populate(struct regmap *map) __set_bit(index, cache->valid); } + if (map->reg_default_cb) { + dev_dbg(map->dev, + "Populating regcache_flat using reg_default_cb callback\n"); + + for (i = 0; i <= map->max_register; i += map->reg_stride) { + unsigned int index = regcache_flat_get_index(map, i); + unsigned int value; + + if (test_bit(index, cache->valid)) + continue; + + if (map->reg_default_cb(map->dev, i, &value)) + continue; + + cache->data[index] = value; + __set_bit(index, cache->valid); + } + } + return 0; } diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 319c342bf5a0..31bdbf37dbed 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -223,7 +223,8 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) goto err_free; } - if (map->num_reg_defaults && map->cache_ops->populate) { + if (map->cache_ops->populate && + (map->num_reg_defaults || map->reg_default_cb)) { dev_dbg(map->dev, "Populating %s cache\n", map->cache_ops->name); map->lock(map->lock_arg); ret = map->cache_ops->populate(map); diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index ae2215d4e61c..4231e9d4b8ff 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -813,6 +813,7 @@ struct regmap *__regmap_init(struct device *dev, map->precious_reg = config->precious_reg; map->writeable_noinc_reg = config->writeable_noinc_reg; map->readable_noinc_reg = config->readable_noinc_reg; + map->reg_default_cb = config->reg_default_cb; map->cache_type = config->cache_type; spin_lock_init(&map->async_lock); @@ -1435,6 +1436,7 @@ int regmap_reinit_cache(struct regmap *map, const struct regmap_config *config) map->precious_reg = config->precious_reg; map->writeable_noinc_reg = config->writeable_noinc_reg; map->readable_noinc_reg = config->readable_noinc_reg; + map->reg_default_cb = config->reg_default_cb; map->cache_type = config->cache_type; ret = regmap_set_name(map, config); diff --git a/include/linux/regmap.h b/include/linux/regmap.h index b0b9be750d93..caff2240bdab 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -359,6 +359,10 @@ typedef void (*regmap_unlock)(void *); * @reg_defaults: Power on reset values for registers (for use with * register cache support). * @num_reg_defaults: Number of elements in reg_defaults. + * @reg_default_cb: Optional callback to return default values for registers + * not listed in reg_defaults. This is only used for + * REGCACHE_FLAT population; drivers must ensure the readable_reg/ + * writeable_reg callbacks are defined to handle holes. * * @read_flag_mask: Mask to be set in the top bytes of the register when doing * a read. @@ -449,6 +453,8 @@ struct regmap_config { const struct regmap_access_table *rd_noinc_table; const struct reg_default *reg_defaults; unsigned int num_reg_defaults; + int (*reg_default_cb)(struct device *dev, unsigned int reg, + unsigned int *def); enum regcache_type cache_type; const void *reg_defaults_raw; unsigned int num_reg_defaults_raw; @@ -1349,6 +1355,14 @@ static inline int regmap_write_bits(struct regmap *map, unsigned int reg, return regmap_update_bits_base(map, reg, mask, val, NULL, false, true); } +static inline int regmap_default_zero_cb(struct device *dev, + unsigned int reg, + unsigned int *def) +{ + *def = 0; + return 0; +} + int regmap_get_val_bytes(struct regmap *map); int regmap_get_max_register(struct regmap *map); int regmap_get_reg_stride(struct regmap *map); -- cgit v1.2.3 From 0fd17e5983337231dc655e9ca0095d2ca3f47405 Mon Sep 17 00:00:00 2001 From: Oreoluwa Babatunde Date: Mon, 26 Jan 2026 18:13:27 +0100 Subject: of: reserved_mem: Allow reserved_mem framework detect "cma=" kernel param When initializing the default cma region, the "cma=" kernel parameter takes priority over a DT defined linux,cma-default region. Hence, give the reserved_mem framework the ability to detect this so that the DT defined cma region can skip initialization accordingly. Signed-off-by: Oreoluwa Babatunde Tested-by: Joy Zou Acked-by: Rob Herring (Arm) Fixes: 8a6e02d0c00e ("of: reserved_mem: Restructure how the reserved memory regions are processed") Fixes: 2c223f7239f3 ("of: reserved_mem: Restructure call site for dma_contiguous_early_fixup()") Link: https://lore.kernel.org/r/20251210002027.1171519-1-oreoluwa.babatunde@oss.qualcomm.com [mszyprow: rebased onto v6.19-rc1, added fixes tags, added a stub for cma_skip_dt_default_reserved_mem() if no CONFIG_DMA_CMA is set] Signed-off-by: Marek Szyprowski --- drivers/of/of_reserved_mem.c | 19 +++++++++++++++++-- include/linux/cma.h | 9 +++++++++ kernel/dma/contiguous.c | 16 ++++++++++------ 3 files changed, 36 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c index 5619ec917858..a2a13617c6f4 100644 --- a/drivers/of/of_reserved_mem.c +++ b/drivers/of/of_reserved_mem.c @@ -157,13 +157,19 @@ static int __init __reserved_mem_reserve_reg(unsigned long node, phys_addr_t base, size; int i, len; const __be32 *prop; - bool nomap; + bool nomap, default_cma; prop = of_flat_dt_get_addr_size_prop(node, "reg", &len); if (!prop) return -ENOENT; nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL; + default_cma = of_get_flat_dt_prop(node, "linux,cma-default", NULL); + + if (default_cma && cma_skip_dt_default_reserved_mem()) { + pr_err("Skipping dt linux,cma-default for \"cma=\" kernel param.\n"); + return -EINVAL; + } for (i = 0; i < len; i++) { u64 b, s; @@ -248,10 +254,13 @@ void __init fdt_scan_reserved_mem_reg_nodes(void) fdt_for_each_subnode(child, fdt, node) { const char *uname; + bool default_cma = of_get_flat_dt_prop(child, "linux,cma-default", NULL); u64 b, s; if (!of_fdt_device_is_available(fdt, child)) continue; + if (default_cma && cma_skip_dt_default_reserved_mem()) + continue; if (!of_flat_dt_get_addr_size(child, "reg", &b, &s)) continue; @@ -389,7 +398,7 @@ static int __init __reserved_mem_alloc_size(unsigned long node, const char *unam phys_addr_t base = 0, align = 0, size; int i, len; const __be32 *prop; - bool nomap; + bool nomap, default_cma; int ret; prop = of_get_flat_dt_prop(node, "size", &len); @@ -413,6 +422,12 @@ static int __init __reserved_mem_alloc_size(unsigned long node, const char *unam } nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL; + default_cma = of_get_flat_dt_prop(node, "linux,cma-default", NULL); + + if (default_cma && cma_skip_dt_default_reserved_mem()) { + pr_err("Skipping dt linux,cma-default for \"cma=\" kernel param.\n"); + return -EINVAL; + } /* Need adjust the alignment to satisfy the CMA requirement */ if (IS_ENABLED(CONFIG_CMA) diff --git a/include/linux/cma.h b/include/linux/cma.h index 62d9c1cf6326..2e6931735880 100644 --- a/include/linux/cma.h +++ b/include/linux/cma.h @@ -57,6 +57,15 @@ extern bool cma_intersects(struct cma *cma, unsigned long start, unsigned long e extern void cma_reserve_pages_on_error(struct cma *cma); +#ifdef CONFIG_DMA_CMA +extern bool cma_skip_dt_default_reserved_mem(void); +#else +static inline bool cma_skip_dt_default_reserved_mem(void) +{ + return false; +} +#endif + #ifdef CONFIG_CMA struct folio *cma_alloc_folio(struct cma *cma, int order, gfp_t gfp); bool cma_free_folio(struct cma *cma, const struct folio *folio); diff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c index d8fd6f779f79..0e266979728b 100644 --- a/kernel/dma/contiguous.c +++ b/kernel/dma/contiguous.c @@ -91,6 +91,16 @@ static int __init early_cma(char *p) } early_param("cma", early_cma); +/* + * cma_skip_dt_default_reserved_mem - This is called from the + * reserved_mem framework to detect if the default cma region is being + * set by the "cma=" kernel parameter. + */ +bool __init cma_skip_dt_default_reserved_mem(void) +{ + return size_cmdline != -1; +} + #ifdef CONFIG_DMA_NUMA_CMA static struct cma *dma_contiguous_numa_area[MAX_NUMNODES]; @@ -470,12 +480,6 @@ static int __init rmem_cma_setup(struct reserved_mem *rmem) struct cma *cma; int err; - if (size_cmdline != -1 && default_cma) { - pr_info("Reserved memory: bypass %s node, using cmdline CMA params instead\n", - rmem->name); - return -EBUSY; - } - if (!of_get_flat_dt_prop(node, "reusable", NULL) || of_get_flat_dt_prop(node, "no-map", NULL)) return -EINVAL; -- cgit v1.2.3 From d2492688bb9fed6ab6e313682c387ae71a66ebae Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Tue, 27 Jan 2026 04:03:59 +0000 Subject: nfc: nci: Fix race between rfkill and nci_unregister_device(). syzbot reported the splat below [0] without a repro. It indicates that struct nci_dev.cmd_wq had been destroyed before nci_close_device() was called via rfkill. nci_dev.cmd_wq is only destroyed in nci_unregister_device(), which (I think) was called from virtual_ncidev_close() when syzbot close()d an fd of virtual_ncidev. The problem is that nci_unregister_device() destroys nci_dev.cmd_wq first and then calls nfc_unregister_device(), which removes the device from rfkill by rfkill_unregister(). So, the device is still visible via rfkill even after nci_dev.cmd_wq is destroyed. Let's unregister the device from rfkill first in nci_unregister_device(). Note that we cannot call nfc_unregister_device() before nci_close_device() because 1) nfc_unregister_device() calls device_del() which frees all memory allocated by devm_kzalloc() and linked to ndev->conn_info_list 2) nci_rx_work() could try to queue nci_conn_info to ndev->conn_info_list which could be leaked Thus, nfc_unregister_device() is split into two functions so we can remove rfkill interfaces only before nci_close_device(). [0]: DEBUG_LOCKS_WARN_ON(1) WARNING: kernel/locking/lockdep.c:238 at hlock_class kernel/locking/lockdep.c:238 [inline], CPU#0: syz.0.8675/6349 WARNING: kernel/locking/lockdep.c:238 at check_wait_context kernel/locking/lockdep.c:4854 [inline], CPU#0: syz.0.8675/6349 WARNING: kernel/locking/lockdep.c:238 at __lock_acquire+0x39d/0x2cf0 kernel/locking/lockdep.c:5187, CPU#0: syz.0.8675/6349 Modules linked in: CPU: 0 UID: 0 PID: 6349 Comm: syz.0.8675 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/13/2026 RIP: 0010:hlock_class kernel/locking/lockdep.c:238 [inline] RIP: 0010:check_wait_context kernel/locking/lockdep.c:4854 [inline] RIP: 0010:__lock_acquire+0x3a4/0x2cf0 kernel/locking/lockdep.c:5187 Code: 18 00 4c 8b 74 24 08 75 27 90 e8 17 f2 fc 02 85 c0 74 1c 83 3d 50 e0 4e 0e 00 75 13 48 8d 3d 43 f7 51 0e 48 c7 c6 8b 3a de 8d <67> 48 0f b9 3a 90 31 c0 0f b6 98 c4 00 00 00 41 8b 45 20 25 ff 1f RSP: 0018:ffffc9000c767680 EFLAGS: 00010046 RAX: 0000000000000001 RBX: 0000000000040000 RCX: 0000000000080000 RDX: ffffc90013080000 RSI: ffffffff8dde3a8b RDI: ffffffff8ff24ca0 RBP: 0000000000000003 R08: ffffffff8fef35a3 R09: 1ffffffff1fde6b4 R10: dffffc0000000000 R11: fffffbfff1fde6b5 R12: 00000000000012a2 R13: ffff888030338ba8 R14: ffff888030338000 R15: ffff888030338b30 FS: 00007fa5995f66c0(0000) GS:ffff8881256f8000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f7e72f842d0 CR3: 00000000485a0000 CR4: 00000000003526f0 Call Trace: lock_acquire+0x106/0x330 kernel/locking/lockdep.c:5868 touch_wq_lockdep_map+0xcb/0x180 kernel/workqueue.c:3940 __flush_workqueue+0x14b/0x14f0 kernel/workqueue.c:3982 nci_close_device+0x302/0x630 net/nfc/nci/core.c:567 nci_dev_down+0x3b/0x50 net/nfc/nci/core.c:639 nfc_dev_down+0x152/0x290 net/nfc/core.c:161 nfc_rfkill_set_block+0x2d/0x100 net/nfc/core.c:179 rfkill_set_block+0x1d2/0x440 net/rfkill/core.c:346 rfkill_fop_write+0x461/0x5a0 net/rfkill/core.c:1301 vfs_write+0x29a/0xb90 fs/read_write.c:684 ksys_write+0x150/0x270 fs/read_write.c:738 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xe2/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7fa59b39acb9 Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007fa5995f6028 EFLAGS: 00000246 ORIG_RAX: 0000000000000001 RAX: ffffffffffffffda RBX: 00007fa59b615fa0 RCX: 00007fa59b39acb9 RDX: 0000000000000008 RSI: 0000200000000080 RDI: 0000000000000007 RBP: 00007fa59b408bf7 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007fa59b616038 R14: 00007fa59b615fa0 R15: 00007ffc82218788 Fixes: 6a2968aaf50c ("NFC: basic NCI protocol implementation") Reported-by: syzbot+f9c5fd1a0874f9069dce@syzkaller.appspotmail.com Closes: https://lore.kernel.org/all/695e7f56.050a0220.1c677c.036c.GAE@google.com/ Signed-off-by: Kuniyuki Iwashima Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260127040411.494931-1-kuniyu@google.com Signed-off-by: Jakub Kicinski --- include/net/nfc/nfc.h | 2 ++ net/nfc/core.c | 27 ++++++++++++++++++++++++--- net/nfc/nci/core.c | 4 +++- 3 files changed, 29 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/net/nfc/nfc.h b/include/net/nfc/nfc.h index 127e6c7d910d..c54df042db6b 100644 --- a/include/net/nfc/nfc.h +++ b/include/net/nfc/nfc.h @@ -219,6 +219,8 @@ static inline void nfc_free_device(struct nfc_dev *dev) int nfc_register_device(struct nfc_dev *dev); +void nfc_unregister_rfkill(struct nfc_dev *dev); +void nfc_remove_device(struct nfc_dev *dev); void nfc_unregister_device(struct nfc_dev *dev); /** diff --git a/net/nfc/core.c b/net/nfc/core.c index 82f023f37754..f50e5bab35d8 100644 --- a/net/nfc/core.c +++ b/net/nfc/core.c @@ -1147,14 +1147,14 @@ int nfc_register_device(struct nfc_dev *dev) EXPORT_SYMBOL(nfc_register_device); /** - * nfc_unregister_device - unregister a nfc device in the nfc subsystem + * nfc_unregister_rfkill - unregister a nfc device in the rfkill subsystem * * @dev: The nfc device to unregister */ -void nfc_unregister_device(struct nfc_dev *dev) +void nfc_unregister_rfkill(struct nfc_dev *dev) { - int rc; struct rfkill *rfk = NULL; + int rc; pr_debug("dev_name=%s\n", dev_name(&dev->dev)); @@ -1175,7 +1175,16 @@ void nfc_unregister_device(struct nfc_dev *dev) rfkill_unregister(rfk); rfkill_destroy(rfk); } +} +EXPORT_SYMBOL(nfc_unregister_rfkill); +/** + * nfc_remove_device - remove a nfc device in the nfc subsystem + * + * @dev: The nfc device to remove + */ +void nfc_remove_device(struct nfc_dev *dev) +{ if (dev->ops->check_presence) { timer_delete_sync(&dev->check_pres_timer); cancel_work_sync(&dev->check_pres_work); @@ -1188,6 +1197,18 @@ void nfc_unregister_device(struct nfc_dev *dev) device_del(&dev->dev); mutex_unlock(&nfc_devlist_mutex); } +EXPORT_SYMBOL(nfc_remove_device); + +/** + * nfc_unregister_device - unregister a nfc device in the nfc subsystem + * + * @dev: The nfc device to unregister + */ +void nfc_unregister_device(struct nfc_dev *dev) +{ + nfc_unregister_rfkill(dev); + nfc_remove_device(dev); +} EXPORT_SYMBOL(nfc_unregister_device); static int __init nfc_init(void) diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c index fc921cd2cdff..e419e020a70a 100644 --- a/net/nfc/nci/core.c +++ b/net/nfc/nci/core.c @@ -1303,6 +1303,8 @@ void nci_unregister_device(struct nci_dev *ndev) { struct nci_conn_info *conn_info, *n; + nfc_unregister_rfkill(ndev->nfc_dev); + /* This set_bit is not protected with specialized barrier, * However, it is fine because the mutex_lock(&ndev->req_lock); * in nci_close_device() will help to emit one. @@ -1320,7 +1322,7 @@ void nci_unregister_device(struct nci_dev *ndev) /* conn_info is allocated with devm_kzalloc */ } - nfc_unregister_device(ndev->nfc_dev); + nfc_remove_device(ndev->nfc_dev); } EXPORT_SYMBOL(nci_unregister_device); -- cgit v1.2.3 From 76ed27608f7dd235b727ebbb12163438c2fbb617 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Thu, 29 Jan 2026 10:28:21 -0500 Subject: perf: sched: Fix perf crash with new is_user_task() helper In order to do a user space stacktrace the current task needs to be a user task that has executed in user space. It use to be possible to test if a task is a user task or not by simply checking the task_struct mm field. If it was non NULL, it was a user task and if not it was a kernel task. But things have changed over time, and some kernel tasks now have their own mm field. An idea was made to instead test PF_KTHREAD and two functions were used to wrap this check in case it became more complex to test if a task was a user task or not[1]. But this was rejected and the C code simply checked the PF_KTHREAD directly. It was later found that not all kernel threads set PF_KTHREAD. The io-uring helpers instead set PF_USER_WORKER and this needed to be added as well. But checking the flags is still not enough. There's a very small window when a task exits that it frees its mm field and it is set back to NULL. If perf were to trigger at this moment, the flags test would say its a user space task but when perf would read the mm field it would crash with at NULL pointer dereference. Now there are flags that can be used to test if a task is exiting, but they are set in areas that perf may still want to profile the user space task (to see where it exited). The only real test is to check both the flags and the mm field. Instead of making this modification in every location, create a new is_user_task() helper function that does all the tests needed to know if it is safe to read the user space memory or not. [1] https://lore.kernel.org/all/20250425204120.639530125@goodmis.org/ Fixes: 90942f9fac05 ("perf: Use current->flags & PF_KTHREAD|PF_USER_WORKER instead of current->mm == NULL") Closes: https://lore.kernel.org/all/0d877e6f-41a7-4724-875d-0b0a27b8a545@roeck-us.net/ Reported-by: Guenter Roeck Signed-off-by: Steven Rostedt (Google) Signed-off-by: Peter Zijlstra (Intel) Tested-by: Guenter Roeck Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260129102821.46484722@gandalf.local.home --- include/linux/sched.h | 5 +++++ kernel/events/callchain.c | 2 +- kernel/events/core.c | 6 +++--- 3 files changed, 9 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/linux/sched.h b/include/linux/sched.h index da0133524d08..5f00b5ed0f3b 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1776,6 +1776,11 @@ static __always_inline bool is_percpu_thread(void) (current->nr_cpus_allowed == 1); } +static __always_inline bool is_user_task(struct task_struct *task) +{ + return task->mm && !(task->flags & (PF_KTHREAD | PF_USER_WORKER)); +} + /* Per-process atomic flags. */ #define PFA_NO_NEW_PRIVS 0 /* May not gain new privileges. */ #define PFA_SPREAD_PAGE 1 /* Spread page cache over cpuset */ diff --git a/kernel/events/callchain.c b/kernel/events/callchain.c index 1f6589578703..9d24b6e0c91f 100644 --- a/kernel/events/callchain.c +++ b/kernel/events/callchain.c @@ -246,7 +246,7 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, if (user && !crosstask) { if (!user_mode(regs)) { - if (current->flags & (PF_KTHREAD | PF_USER_WORKER)) + if (!is_user_task(current)) goto exit_put; regs = task_pt_regs(current); } diff --git a/kernel/events/core.c b/kernel/events/core.c index a0fa488bce84..8cca80094624 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -7460,7 +7460,7 @@ static void perf_sample_regs_user(struct perf_regs *regs_user, if (user_mode(regs)) { regs_user->abi = perf_reg_abi(current); regs_user->regs = regs; - } else if (!(current->flags & (PF_KTHREAD | PF_USER_WORKER))) { + } else if (is_user_task(current)) { perf_get_regs_user(regs_user, regs); } else { regs_user->abi = PERF_SAMPLE_REGS_ABI_NONE; @@ -8100,7 +8100,7 @@ static u64 perf_virt_to_phys(u64 virt) * Try IRQ-safe get_user_page_fast_only first. * If failed, leave phys_addr as 0. */ - if (!(current->flags & (PF_KTHREAD | PF_USER_WORKER))) { + if (is_user_task(current)) { struct page *p; pagefault_disable(); @@ -8215,7 +8215,7 @@ perf_callchain(struct perf_event *event, struct pt_regs *regs) { bool kernel = !event->attr.exclude_callchain_kernel; bool user = !event->attr.exclude_callchain_user && - !(current->flags & (PF_KTHREAD | PF_USER_WORKER)); + is_user_task(current); /* Disallow cross-task user callchains. */ bool crosstask = event->ctx->task && event->ctx->task != current; bool defer_user = IS_ENABLED(CONFIG_UNWIND_USER) && user && -- cgit v1.2.3 From af0bc3ac9a9e830cb52b718ecb237c4e76a466be Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 4 Feb 2026 10:18:26 +0200 Subject: uapi: sound: sof: tokens: Add missing token for KCPS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align with the firmware and add the missing token for pipeline kcps. Signed-off-by: Ranjani Sridharan Reviewed-by: Péter Ujfalusi Reviewed-by: Bard Liao Signed-off-by: Peter Ujfalusi Link: https://patch.msgid.link/20260204081833.16630-4-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- include/uapi/sound/sof/tokens.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h index 5fa8ab5088e0..a68381a263eb 100644 --- a/include/uapi/sound/sof/tokens.h +++ b/include/uapi/sound/sof/tokens.h @@ -56,6 +56,7 @@ #define SOF_TKN_SCHED_LP_MODE 207 #define SOF_TKN_SCHED_MEM_USAGE 208 #define SOF_TKN_SCHED_USE_CHAIN_DMA 209 +#define SOF_TKN_SCHED_KCPS 210 /* volume */ #define SOF_TKN_VOLUME_RAMP_STEP_TYPE 250 -- cgit v1.2.3 From 15a55ec2f8b956d6aa0dd948c907e13db7978c6e Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Wed, 4 Feb 2026 10:18:28 +0200 Subject: ASoC: SOF: ipc4-topology: Add new tokens for pipeline direction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parse the pipeline direction from topology. The direction_valid token is required for backward-compatibility with older topologies that may not have the direction set for pipelines. This will be used when setting up pipelines to check if a pipeline is in the same direction as the requested params and skip those in the opposite direction like in the case of echo reference capture pipelines during playback. Signed-off-by: Ranjani Sridharan Reviewed-by: Péter Ujfalusi Reviewed-by: Bard Liao Signed-off-by: Peter Ujfalusi Link: https://patch.msgid.link/20260204081833.16630-6-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- include/uapi/sound/sof/tokens.h | 2 ++ sound/soc/sof/ipc4-topology.c | 12 ++++++++++-- sound/soc/sof/ipc4-topology.h | 4 ++++ sound/soc/sof/sof-audio.h | 5 +++++ 4 files changed, 21 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h index a68381a263eb..f4a7baadb44d 100644 --- a/include/uapi/sound/sof/tokens.h +++ b/include/uapi/sound/sof/tokens.h @@ -57,6 +57,8 @@ #define SOF_TKN_SCHED_MEM_USAGE 208 #define SOF_TKN_SCHED_USE_CHAIN_DMA 209 #define SOF_TKN_SCHED_KCPS 210 +#define SOF_TKN_SCHED_DIRECTION 211 +#define SOF_TKN_SCHED_DIRECTION_VALID 212 /* volume */ #define SOF_TKN_VOLUME_RAMP_STEP_TYPE 250 diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index d28aad71c7ed..09aca52e020a 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -76,6 +76,10 @@ static const struct sof_topology_token ipc4_sched_tokens[] = { offsetof(struct sof_ipc4_pipeline, core_id)}, {SOF_TKN_SCHED_PRIORITY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_pipeline, priority)}, + {SOF_TKN_SCHED_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_pipeline, direction)}, + {SOF_TKN_SCHED_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, + offsetof(struct sof_ipc4_pipeline, direction_valid)}, }; static const struct sof_topology_token pipeline_tokens[] = { @@ -939,6 +943,10 @@ static int sof_ipc4_widget_setup_comp_pipeline(struct snd_sof_widget *swidget) swidget->core = pipeline->core_id; spipe->core_mask |= BIT(pipeline->core_id); + if (pipeline->direction_valid) { + spipe->direction = pipeline->direction; + spipe->direction_valid = true; + } if (pipeline->use_chain_dma) { dev_dbg(scomp->dev, "Set up chain DMA for %s\n", swidget->widget->name); @@ -954,9 +962,9 @@ static int sof_ipc4_widget_setup_comp_pipeline(struct snd_sof_widget *swidget) goto err; } - dev_dbg(scomp->dev, "pipeline '%s': id %d, pri %d, core_id %u, lp mode %d\n", + dev_dbg(scomp->dev, "pipeline '%s': id %d, pri %d, core_id %u, lp mode %d direction %d\n", swidget->widget->name, swidget->pipeline_id, - pipeline->priority, pipeline->core_id, pipeline->lp_mode); + pipeline->priority, pipeline->core_id, pipeline->lp_mode, pipeline->direction); swidget->private = pipeline; diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index 9a028a59c553..a289c1d8f3ff 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -150,6 +150,8 @@ struct sof_ipc4_copier_config_set_sink_format { * @use_chain_dma: flag to indicate if the firmware shall use chained DMA * @msg: message structure for pipeline * @skip_during_fe_trigger: skip triggering this pipeline during the FE DAI trigger + * @direction_valid: flag indicating if valid direction is set in topology + * @direction: pipeline direction set in topology if direction_valid is true */ struct sof_ipc4_pipeline { uint32_t priority; @@ -160,6 +162,8 @@ struct sof_ipc4_pipeline { bool use_chain_dma; struct sof_ipc4_msg msg; bool skip_during_fe_trigger; + bool direction_valid; + u32 direction; }; /** diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 5f62a34582da..36082e764bf9 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -512,6 +512,9 @@ struct snd_sof_widget { * @complete: flag used to indicate that pipeline set up is complete. * @core_mask: Mask containing target cores for all modules in the pipeline * @list: List item in sdev pipeline_list + * @direction_valid: flag indicating if the direction is set in topology + * @direction: pipeline direction set in topology, valid is direction_valid is true + * */ struct snd_sof_pipeline { struct snd_sof_widget *pipe_widget; @@ -520,6 +523,8 @@ struct snd_sof_pipeline { int complete; unsigned long core_mask; struct list_head list; + bool direction_valid; + u32 direction; }; /* ASoC SOF DAPM route */ -- cgit v1.2.3 From 55137f5a68b5e888504ad36d07221cd749bb8956 Mon Sep 17 00:00:00 2001 From: Shenghao Ding Date: Mon, 2 Feb 2026 18:27:56 +0800 Subject: ASoC: tas2781: Put three different calibrated data solution into the same data structure TAS2781 driver supports three solutions of calibrated data. The first is from the driver itself: driver reads the calibrated files directly during probe; The second is from user space: during init of audio hal, the audio hal will pass the calibrated data via kcontrol interface. Driver will store this data in "struct calidata" for use. The third is from UEFI, mainly used in hda device. These three solutions save the calibrated data into different data structures. It is time to put them together into "struct calidata" for use. Signed-off-by: Shenghao Ding Link: https://patch.msgid.link/20260202102757.532-1-shenghao-ding@ti.com Signed-off-by: Mark Brown --- include/sound/tas2781.h | 3 +- sound/hda/codecs/side-codecs/tas2781_hda.c | 9 +- sound/hda/codecs/side-codecs/tas2781_hda_i2c.c | 13 --- sound/soc/codecs/tas2781-fmwlib.c | 138 +++++++++++++++++++------ sound/soc/codecs/tas2781-i2c.c | 11 +- 5 files changed, 121 insertions(+), 53 deletions(-) (limited to 'include') diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index 9d3c54cb8223..7c03bdc951bb 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -2,7 +2,7 @@ // // ALSA SoC Texas Instruments TAS2563/TAS2781 Audio Smart Amplifier // -// Copyright (C) 2022 - 2025 Texas Instruments Incorporated +// Copyright (C) 2022 - 2026 Texas Instruments Incorporated // https://www.ti.com // // The TAS2563/TAS2781 driver implements a flexible and configurable @@ -233,7 +233,6 @@ struct tasdevice_priv { bool playback_started; bool isacpi; bool isspi; - bool is_user_space_calidata; unsigned int global_addr; int (*fw_parse_variable_header)(struct tasdevice_priv *tas_priv, diff --git a/sound/hda/codecs/side-codecs/tas2781_hda.c b/sound/hda/codecs/side-codecs/tas2781_hda.c index 96e6d82dc69e..b22f93424c62 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda.c @@ -2,7 +2,7 @@ // // TAS2781 HDA Shared Lib for I2C&SPI driver // -// Copyright 2025 Texas Instruments, Inc. +// Copyright 2025 - 2026 Texas Instruments, Inc. // // Author: Shenghao Ding @@ -159,7 +159,6 @@ static void tas2781_apply_calib(struct tasdevice_priv *p) r->tlimit_reg = cali_reg[4]; } - p->is_user_space_calidata = true; cali_data->total_sz = p->ndev * (cali_data->cali_dat_sz_per_dev + 1); } @@ -216,6 +215,12 @@ int tas2781_save_calibration(struct tas2781_hda *hda) status = -ENOMEM; continue; } + /* + * Set to an invalid value before the calibrated data + * is stored into it, for the default value is 0, which + * means the first device. + */ + data[0] = 0xff; /* Get variable contents into buffer */ status = efi.get_variable(efi_name[i], &efi_guid, &attr, &cali_data->total_sz, data); diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index 624a822341bb..62ddaceaa8a5 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -393,19 +393,6 @@ static int tas2563_save_calibration(struct tas2781_hda *h) r->pow_reg = TAS2563_CAL_POWER; r->tlimit_reg = TAS2563_CAL_TLIM; - /* - * TAS2781_FMWLIB supports two solutions of calibrated data. One is - * from the driver itself: driver reads the calibrated files directly - * during probe; The other from user space: during init of audio hal, - * the audio hal will pass the calibrated data via kcontrol interface. - * Driver will store this data in "struct calidata" for use. For hda - * device, calibrated data are usunally saved into UEFI. So Hda side - * codec driver use the mixture of these two solutions, driver reads - * the data from UEFI, then store this data in "struct calidata" for - * use. - */ - p->is_user_space_calidata = true; - return 0; } diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c index 78fd0a5dc6f2..0e084c3a162d 100644 --- a/sound/soc/codecs/tas2781-fmwlib.c +++ b/sound/soc/codecs/tas2781-fmwlib.c @@ -2,7 +2,7 @@ // // tas2781-fmwlib.c -- TASDEVICE firmware support // -// Copyright 2023 - 2025 Texas Instruments, Inc. +// Copyright 2023 - 2026 Texas Instruments, Inc. // // Author: Shenghao Ding // Author: Baojun Xu @@ -80,6 +80,14 @@ #define POST_SOFTWARE_RESET_DEVICE_C 0x47 #define POST_SOFTWARE_RESET_DEVICE_D 0x48 +#define COPY_CAL_DATA(i) \ + do { \ + calbin_data[i + 1] = data[7]; \ + calbin_data[i + 2] = data[8]; \ + calbin_data[i + 3] = data[9]; \ + calbin_data[i + 4] = data[10]; \ + } while (0) + struct tas_crc { unsigned char offset; unsigned char len; @@ -1952,23 +1960,6 @@ static int dspfw_default_callback(struct tasdevice_priv *tas_priv, return rc; } -static int load_calib_data(struct tasdevice_priv *tas_priv, - struct tasdevice_data *dev_data) -{ - struct tasdev_blk *block; - unsigned int i; - int ret = 0; - - for (i = 0; i < dev_data->nr_blk; i++) { - block = &(dev_data->dev_blks[i]); - ret = tasdevice_load_block(tas_priv, block); - if (ret < 0) - break; - } - - return ret; -} - static int fw_parse_header(struct tasdevice_priv *tas_priv, struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset) { @@ -2029,6 +2020,103 @@ out: return offset; } +static inline int check_cal_bin_data(struct device *dev, + const unsigned char *data, const char *name) +{ + if (data[2] != 0x85 || data[1] != 4) { + dev_err(dev, "Invalid cal bin file in %s\n", name); + return -1; + } + return 0; +} + +static void calbin_conversion(struct tasdevice_priv *priv, + struct tasdevice_fw *tas_fmw) +{ + struct calidata *cali_data = &priv->cali_data; + unsigned char *calbin_data = cali_data->data; + struct cali_reg *p = &cali_data->cali_reg_array; + struct tasdevice_calibration *calibration; + struct tasdevice_data *img_data; + struct tasdev_blk *blk; + unsigned char *data; + int chn, k; + + if (cali_data->total_sz != priv->ndev * + (cali_data->cali_dat_sz_per_dev + 1)) { + dev_err(priv->dev, "%s: cali_data size err\n", + __func__); + return; + } + calibration = &(tas_fmw->calibrations[0]); + img_data = &(calibration->dev_data); + + if (img_data->nr_blk != 1) { + dev_err(priv->dev, "%s: Invalid nr_blk, wrong cal bin\n", + __func__); + return; + } + + blk = &(img_data->dev_blks[0]); + if (blk->nr_cmds != 15) { + dev_err(priv->dev, "%s: Invalid nr_cmds, wrong cal bin\n", + __func__); + return; + } + + switch (blk->type) { + case COEFF_DEVICE_A: + chn = 0; + break; + case COEFF_DEVICE_B: + chn = 1; + break; + case COEFF_DEVICE_C: + chn = 2; + break; + case COEFF_DEVICE_D: + chn = 3; + break; + default: + dev_err(priv->dev, "%s: Other Type = 0x%02x\n", + __func__, blk->type); + return; + } + k = chn * (cali_data->cali_dat_sz_per_dev + 1); + + data = blk->data; + if (check_cal_bin_data(priv->dev, data, "r0_reg") < 0) + return; + p->r0_reg = TASDEVICE_REG(data[4], data[5], data[6]); + COPY_CAL_DATA(k); + + data = blk->data + 12; + if (check_cal_bin_data(priv->dev, data, "r0_low_reg") < 0) + return; + p->r0_low_reg = TASDEVICE_REG(data[4], data[5], data[6]); + COPY_CAL_DATA(k + 4); + + data = blk->data + 24; + if (check_cal_bin_data(priv->dev, data, "invr0_reg") < 0) + return; + p->invr0_reg = TASDEVICE_REG(data[4], data[5], data[6]); + COPY_CAL_DATA(k + 8); + + data = blk->data + 36; + if (check_cal_bin_data(priv->dev, data, "pow_reg") < 0) + return; + p->pow_reg = TASDEVICE_REG(data[4], data[5], data[6]); + COPY_CAL_DATA(k + 12); + + data = blk->data + 48; + if (check_cal_bin_data(priv->dev, data, "tlimit_reg") < 0) + return; + p->tlimit_reg = TASDEVICE_REG(data[4], data[5], data[6]); + COPY_CAL_DATA(k + 16); + + calbin_data[k] = chn; +} + /* When calibrated data parsing error occurs, DSP can still work with default * calibrated data, memory resource related to calibrated data will be * released in the tasdevice_codec_remove. @@ -2086,6 +2174,7 @@ static int fw_parse_calibration_data(struct tasdevice_priv *tas_priv, goto out; } + calbin_conversion(tas_priv, tas_fmw); out: return offset; } @@ -2371,25 +2460,12 @@ static int tasdevice_load_data(struct tasdevice_priv *tas_priv, static void tasdev_load_calibrated_data(struct tasdevice_priv *priv, int i) { - struct tasdevice_fw *cal_fmw = priv->tasdevice[i].cali_data_fmw; struct calidata *cali_data = &priv->cali_data; struct cali_reg *p = &cali_data->cali_reg_array; unsigned char *data = cali_data->data; - struct tasdevice_calibration *cal; int k = i * (cali_data->cali_dat_sz_per_dev + 1); int rc; - /* Load the calibrated data from cal bin file */ - if (!priv->is_user_space_calidata && cal_fmw) { - cal = cal_fmw->calibrations; - - if (cal) - load_calib_data(priv, &cal->dev_data); - return; - } - if (!priv->is_user_space_calidata) - return; - /* load calibrated data from user space */ if (data[k] != i) { dev_err(priv->dev, "%s: no cal-data for dev %d from usr-spc\n", __func__, i); diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c index d1c76ab0144d..41b89fcc69c3 100644 --- a/sound/soc/codecs/tas2781-i2c.c +++ b/sound/soc/codecs/tas2781-i2c.c @@ -2,7 +2,7 @@ // // ALSA SoC Texas Instruments TAS2563/TAS2781 Audio Smart Amplifier // -// Copyright (C) 2022 - 2025 Texas Instruments Incorporated +// Copyright (C) 2022 - 2026 Texas Instruments Incorporated // https://www.ti.com // // The TAS2563/TAS2781 driver implements a flexible and configurable @@ -255,8 +255,6 @@ static int tasdev_cali_data_get(struct snd_kcontrol *kcontrol, int rc; guard(mutex)(&priv->codec_lock); - if (!priv->is_user_space_calidata) - return -1; if (!p->r0_reg) return -1; @@ -654,7 +652,6 @@ static int tasdev_cali_data_put(struct snd_kcontrol *kcontrol, } } i += 2; - priv->is_user_space_calidata = true; if (priv->dspbin_typ == TASDEV_BASIC) { p->r0_reg = TASDEVICE_REG(src[i], src[i + 1], src[i + 2]); @@ -1444,7 +1441,11 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) GFP_KERNEL); if (!cali_data->data) return -ENOMEM; - + /* + * Set to an invalid value before the calibrated data is stored into + * it, for the default value is 0, which means the first device. + */ + cali_data->data[0] = 0xff; if (priv->chip_id == TAS2781) { struct soc_bytes_ext *ext_cali_start; char *cali_start_name; -- cgit v1.2.3 From 02d851b46b366b69dfe0842ab45313d8a4d6c960 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 4 Feb 2026 12:59:42 +0000 Subject: ASoC: SDCA: Add regmap defaults for specification defined values Some of the SDCA Controls have a defined reset value in the specification. Update the parsing to add these specification defined values into the regmap defaults array. This will reduce the number of registers that are synchronised on a cache sync. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260204125944.1134011-6-ckeepax@opensource.cirrus.com Reviewed-by: Pierre-Louis Bossart Signed-off-by: Mark Brown --- include/sound/sdca_function.h | 4 ++++ sound/soc/sdca/sdca_functions.c | 36 ++++++++++++++++++++++++++++++++++++ sound/soc/sdca/sdca_regmap.c | 14 +++++++++++--- 3 files changed, 51 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/sound/sdca_function.h b/include/sound/sdca_function.h index 6e9391b3816c..79bd5a7a0f88 100644 --- a/include/sound/sdca_function.h +++ b/include/sound/sdca_function.h @@ -798,6 +798,7 @@ struct sdca_control_range { * @sel: Identifier used for addressing. * @nbits: Number of bits used in the Control. * @values: Holds the Control value for constants and defaults. + * @reset: Defined reset value for the Control. * @cn_list: A bitmask showing the valid Control Numbers within this Control, * Control Numbers typically represent channels. * @interrupt_position: SCDA interrupt line that will alert to changes on this @@ -808,6 +809,7 @@ struct sdca_control_range { * @layers: Bitmask of access layers of the Control. * @deferrable: Indicates if the access to the Control can be deferred. * @has_default: Indicates the Control has a default value to be written. + * @has_reset: Indicates the Control has a defined reset value. * @has_fixed: Indicates the Control only supports a single value. */ struct sdca_control { @@ -816,6 +818,7 @@ struct sdca_control { int nbits; int *values; + int reset; u64 cn_list; int interrupt_position; @@ -827,6 +830,7 @@ struct sdca_control { bool deferrable; bool is_volatile; bool has_default; + bool has_reset; bool has_fixed; }; diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index f38791eab4f1..95b67bb904c3 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -911,6 +911,38 @@ static int find_sdca_control_value(struct device *dev, struct sdca_entity *entit return 0; } +static int find_sdca_control_reset(const struct sdca_entity *entity, + struct sdca_control *control) +{ + switch (SDCA_CTL_TYPE(entity->type, control->sel)) { + case SDCA_CTL_TYPE_S(FU, AGC): + case SDCA_CTL_TYPE_S(FU, BASS_BOOST): + case SDCA_CTL_TYPE_S(FU, LOUDNESS): + case SDCA_CTL_TYPE_S(SMPU, TRIGGER_ENABLE): + case SDCA_CTL_TYPE_S(GE, SELECTED_MODE): + case SDCA_CTL_TYPE_S(TG, TONE_DIVIDER): + case SDCA_CTL_TYPE_S(ENTITY_0, COMMIT_GROUP_MASK): + control->has_reset = true; + control->reset = 0; + break; + case SDCA_CTL_TYPE_S(XU, BYPASS): + case SDCA_CTL_TYPE_S(MFPU, BYPASS): + case SDCA_CTL_TYPE_S(FU, MUTE): + case SDCA_CTL_TYPE_S(CX, CLOCK_SELECT): + control->has_reset = true; + control->reset = 1; + break; + case SDCA_CTL_TYPE_S(PDE, REQUESTED_PS): + control->has_reset = true; + control->reset = 3; + break; + default: + break; + } + + return 0; +} + static int find_sdca_entity_control(struct device *dev, struct sdca_entity *entity, struct fwnode_handle *control_node, struct sdca_control *control) @@ -986,6 +1018,10 @@ static int find_sdca_entity_control(struct device *dev, struct sdca_entity *enti control->is_volatile = find_sdca_control_volatile(entity, control); + ret = find_sdca_control_reset(entity, control); + if (ret) + return ret; + ret = find_sdca_control_range(dev, control_node, &control->range); if (ret) { dev_err(dev, "%s: control %#x: range missing: %d\n", diff --git a/sound/soc/sdca/sdca_regmap.c b/sound/soc/sdca/sdca_regmap.c index 2cca9a9c71ea..4f8a685dc43d 100644 --- a/sound/soc/sdca/sdca_regmap.c +++ b/sound/soc/sdca/sdca_regmap.c @@ -218,7 +218,8 @@ int sdca_regmap_count_constants(struct device *dev, struct sdca_entity *entity = &function->entities[i]; for (j = 0; j < entity->num_controls; j++) { - if (entity->controls[j].mode == SDCA_ACCESS_MODE_DC) + if (entity->controls[j].mode == SDCA_ACCESS_MODE_DC || + entity->controls[j].has_reset) nconsts += hweight64(entity->controls[j].cn_list); } } @@ -255,7 +256,8 @@ int sdca_regmap_populate_constants(struct device *dev, struct sdca_control *control = &entity->controls[j]; int cn; - if (control->mode != SDCA_ACCESS_MODE_DC) + if (control->mode != SDCA_ACCESS_MODE_DC && + !control->has_reset) continue; l = 0; @@ -264,7 +266,10 @@ int sdca_regmap_populate_constants(struct device *dev, consts[k].reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, cn); - consts[k].def = control->values[l]; + if (control->mode == SDCA_ACCESS_MODE_DC) + consts[k].def = control->values[l]; + else + consts[k].def = control->reset; k++; l++; } @@ -306,6 +311,9 @@ static int populate_control_defaults(struct device *dev, struct regmap *regmap, i++; } else if (!control->is_volatile) { + if (control->has_reset) + regcache_drop_region(regmap, reg, reg); + ret = regmap_read(regmap, reg, &val); if (ret) { dev_err(dev, "Failed to read initial %#x: %d\n", -- cgit v1.2.3 From 9db327083f7e0da702e2ec0169f8a34f3576f371 Mon Sep 17 00:00:00 2001 From: Sen Wang Date: Mon, 2 Feb 2026 18:37:03 -0600 Subject: ASoC: ti: davinci-mcasp: Add asynchronous mode support McASP has dedicated clock & frame sync registers for both transmit and receive. Currently McASP driver only supports synchronous behavior and couples both TX & RX settings. Add logic that enables asynchronous mode via ti,async-mode property. In async mode, playback & record can be done simultaneously with different audio configurations (tdm slots, tdm width, audio bit depth). Note the ability to have different tx/rx DSP formats (i2s, dsp_a, etc.), while possible in hardware, remains to be a gap as it require changes to the corresponding machine driver interface. Existing IIS (sync mode) and DIT mode logic remains mostly unchanged. Exceptions are IIS mode logic that previously assumed sync mode, which has now been made aware of the distinction. And shared logic across all modes also now checks for McASP tx/rx-specific driver attributes. Those attributes have been populated according to the original extent, ensuring no divergence in functionality. Constraints no longer applicable for async mode are skipped. Clock selection options have also been added to include rx/tx-only clk_ids, exposing independent configuration via the machine driver as well. Note that asynchronous mode is not applicable for McASP in DIT mode, which is a transmitter-only mode to interface w/ self-clocking formats. Signed-off-by: Sen Wang Acked-by: Peter Ujfalusi Tested-by: Paresh Bhagat Link: https://patch.msgid.link/20260203003703.2334443-5-sen@ti.com Signed-off-by: Mark Brown --- include/linux/platform_data/davinci_asp.h | 3 +- sound/soc/ti/davinci-mcasp.c | 489 ++++++++++++++++++++++++------ sound/soc/ti/davinci-mcasp.h | 10 + 3 files changed, 401 insertions(+), 101 deletions(-) (limited to 'include') diff --git a/include/linux/platform_data/davinci_asp.h b/include/linux/platform_data/davinci_asp.h index b9c8520b4bd3..509c5592aab0 100644 --- a/include/linux/platform_data/davinci_asp.h +++ b/include/linux/platform_data/davinci_asp.h @@ -59,7 +59,8 @@ struct davinci_mcasp_pdata { bool i2s_accurate_sck; /* McASP specific fields */ - int tdm_slots; + int tdm_slots_tx; + int tdm_slots_rx; u8 op_mode; u8 dismod; u8 num_serializer; diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c index 12b7d0c65624..2d260fbc9b83 100644 --- a/sound/soc/ti/davinci-mcasp.c +++ b/sound/soc/ti/davinci-mcasp.c @@ -71,6 +71,7 @@ struct davinci_mcasp_context { struct davinci_mcasp_ruledata { struct davinci_mcasp *mcasp; int serializers; + int stream; }; struct davinci_mcasp { @@ -88,21 +89,27 @@ struct davinci_mcasp { bool missing_audio_param; /* McASP specific data */ - int tdm_slots; + int tdm_slots_tx; + int tdm_slots_rx; u32 tdm_mask[2]; - int slot_width; + int slot_width_tx; + int slot_width_rx; u8 op_mode; u8 dismod; u8 num_serializer; u8 *serial_dir; u8 version; - u8 bclk_div; + u8 bclk_div_tx; + u8 bclk_div_rx; int streams; u32 irq_request[2]; - int sysclk_freq; + unsigned int sysclk_freq_tx; + unsigned int sysclk_freq_rx; bool bclk_master; - u32 auxclk_fs_ratio; + bool async_mode; + u32 auxclk_fs_ratio_tx; + u32 auxclk_fs_ratio_rx; unsigned long pdir; /* Pin direction bitfield */ @@ -204,6 +211,27 @@ static inline void mcasp_set_clk_pdir(struct davinci_mcasp *mcasp, bool enable) } } +static inline void mcasp_set_clk_pdir_stream(struct davinci_mcasp *mcasp, + int stream, bool enable) +{ + u32 bit, bit_end; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + bit = PIN_BIT_ACLKX; + bit_end = PIN_BIT_AFSX + 1; + } else { + bit = PIN_BIT_ACLKR; + bit_end = PIN_BIT_AFSR + 1; + } + + for_each_set_bit_from(bit, &mcasp->pdir, bit_end) { + if (enable) + mcasp_set_bits(mcasp, DAVINCI_MCASP_PDIR_REG, BIT(bit)); + else + mcasp_clr_bits(mcasp, DAVINCI_MCASP_PDIR_REG, BIT(bit)); + } +} + static inline void mcasp_set_axr_pdir(struct davinci_mcasp *mcasp, bool enable) { u32 bit; @@ -216,6 +244,36 @@ static inline void mcasp_set_axr_pdir(struct davinci_mcasp *mcasp, bool enable) } } +static inline int mcasp_get_tdm_slots(struct davinci_mcasp *mcasp, int stream) +{ + return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? + mcasp->tdm_slots_tx : mcasp->tdm_slots_rx; +} + +static inline int mcasp_get_slot_width(struct davinci_mcasp *mcasp, int stream) +{ + return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? + mcasp->slot_width_tx : mcasp->slot_width_rx; +} + +static inline unsigned int mcasp_get_sysclk_freq(struct davinci_mcasp *mcasp, int stream) +{ + return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? + mcasp->sysclk_freq_tx : mcasp->sysclk_freq_rx; +} + +static inline unsigned int mcasp_get_bclk_div(struct davinci_mcasp *mcasp, int stream) +{ + return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? + mcasp->bclk_div_tx : mcasp->bclk_div_rx; +} + +static inline unsigned int mcasp_get_auxclk_fs_ratio(struct davinci_mcasp *mcasp, int stream) +{ + return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? + mcasp->auxclk_fs_ratio_tx : mcasp->auxclk_fs_ratio_rx; +} + static void mcasp_start_rx(struct davinci_mcasp *mcasp) { if (mcasp->rxnumevt) { /* enable FIFO */ @@ -231,13 +289,17 @@ static void mcasp_start_rx(struct davinci_mcasp *mcasp) /* * When ASYNC == 0 the transmit and receive sections operate * synchronously from the transmit clock and frame sync. We need to make - * sure that the TX signlas are enabled when starting reception. + * sure that the TX signals are enabled when starting reception, + * when the McASP is the producer. */ if (mcasp_is_frame_producer(mcasp) && mcasp_is_synchronous(mcasp)) { mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXHCLKRST); mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST); } - mcasp_set_clk_pdir(mcasp, true); + if (mcasp_is_synchronous(mcasp)) + mcasp_set_clk_pdir(mcasp, true); + else + mcasp_set_clk_pdir_stream(mcasp, SNDRV_PCM_STREAM_CAPTURE, true); /* Activate serializer(s) */ mcasp_set_reg(mcasp, DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF); @@ -268,7 +330,10 @@ static void mcasp_start_tx(struct davinci_mcasp *mcasp) /* Start clocks */ mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXHCLKRST); mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST); - mcasp_set_clk_pdir(mcasp, true); + if (mcasp_is_synchronous(mcasp)) + mcasp_set_clk_pdir(mcasp, true); + else + mcasp_set_clk_pdir_stream(mcasp, SNDRV_PCM_STREAM_PLAYBACK, true); /* Activate serializer(s) */ mcasp_set_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF); @@ -311,9 +376,17 @@ static void mcasp_stop_rx(struct davinci_mcasp *mcasp) /* * In synchronous mode stop the TX clocks if no other stream is * running + * Otherwise in async mode only stop RX clocks */ - if (!mcasp->streams) + if (mcasp_is_synchronous(mcasp) && !mcasp->streams) mcasp_set_clk_pdir(mcasp, false); + else if (!mcasp_is_synchronous(mcasp)) + mcasp_set_clk_pdir_stream(mcasp, SNDRV_PCM_STREAM_CAPTURE, false); + /* + * When McASP is the producer and operating in synchronous mode, + * stop the transmit clocks if no other stream is running. As + * tx & rx operate synchronously from the transmit clock. + */ if (mcasp_is_frame_producer(mcasp) && mcasp_is_synchronous(mcasp) && !mcasp->streams) mcasp_set_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, 0); @@ -338,11 +411,14 @@ static void mcasp_stop_tx(struct davinci_mcasp *mcasp) /* * In synchronous mode keep TX clocks running if the capture stream is * still running. + * Otherwise in async mode only stop TX clocks */ if (mcasp_is_frame_producer(mcasp) && mcasp_is_synchronous(mcasp) && mcasp->streams) val = TXHCLKRST | TXCLKRST | TXFSRST; - if (!mcasp->streams) + if (mcasp_is_synchronous(mcasp) && !mcasp->streams) mcasp_set_clk_pdir(mcasp, false); + else if (!mcasp_is_synchronous(mcasp)) + mcasp_set_clk_pdir_stream(mcasp, SNDRV_PCM_STREAM_PLAYBACK, false); mcasp_set_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, val); @@ -626,13 +702,39 @@ static int __davinci_mcasp_set_clkdiv(struct davinci_mcasp *mcasp, int div_id, AHCLKRDIV(div - 1), AHCLKRDIV_MASK); break; + case MCASP_CLKDIV_AUXCLK_TXONLY: /* MCLK divider for TX only */ + mcasp_mod_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, + AHCLKXDIV(div - 1), AHCLKXDIV_MASK); + break; + + case MCASP_CLKDIV_AUXCLK_RXONLY: /* MCLK divider for RX only */ + mcasp_mod_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, + AHCLKRDIV(div - 1), AHCLKRDIV_MASK); + break; + case MCASP_CLKDIV_BCLK: /* BCLK divider */ mcasp_mod_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXDIV(div - 1), ACLKXDIV_MASK); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, + ACLKRDIV(div - 1), ACLKRDIV_MASK); + if (explicit) { + mcasp->bclk_div_tx = div; + mcasp->bclk_div_rx = div; + } + break; + + case MCASP_CLKDIV_BCLK_TXONLY: /* BCLK divider for TX only */ + mcasp_mod_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, + ACLKXDIV(div - 1), ACLKXDIV_MASK); + if (explicit) + mcasp->bclk_div_tx = div; + break; + + case MCASP_CLKDIV_BCLK_RXONLY: /* BCLK divider for RX only */ mcasp_mod_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRDIV(div - 1), ACLKRDIV_MASK); if (explicit) - mcasp->bclk_div = div; + mcasp->bclk_div_rx = div; break; case MCASP_CLKDIV_BCLK_FS_RATIO: @@ -646,11 +748,33 @@ static int __davinci_mcasp_set_clkdiv(struct davinci_mcasp *mcasp, int div_id, * tdm_slot width by dividing the ratio by the * number of configured tdm slots. */ - mcasp->slot_width = div / mcasp->tdm_slots; - if (div % mcasp->tdm_slots) + mcasp->slot_width_tx = div / mcasp->tdm_slots_tx; + if (div % mcasp->tdm_slots_tx) dev_warn(mcasp->dev, - "%s(): BCLK/LRCLK %d is not divisible by %d tdm slots", - __func__, div, mcasp->tdm_slots); + "%s(): BCLK/LRCLK %d is not divisible by %d tx tdm slots", + __func__, div, mcasp->tdm_slots_tx); + + mcasp->slot_width_rx = div / mcasp->tdm_slots_rx; + if (div % mcasp->tdm_slots_rx) + dev_warn(mcasp->dev, + "%s(): BCLK/LRCLK %d is not divisible by %d rx tdm slots", + __func__, div, mcasp->tdm_slots_rx); + break; + + case MCASP_CLKDIV_BCLK_FS_RATIO_TXONLY: + mcasp->slot_width_tx = div / mcasp->tdm_slots_tx; + if (div % mcasp->tdm_slots_tx) + dev_warn(mcasp->dev, + "%s(): BCLK/LRCLK %d is not divisible by %d tx tdm slots", + __func__, div, mcasp->tdm_slots_tx); + break; + + case MCASP_CLKDIV_BCLK_FS_RATIO_RXONLY: + mcasp->slot_width_rx = div / mcasp->tdm_slots_rx; + if (div % mcasp->tdm_slots_rx) + dev_warn(mcasp->dev, + "%s(): BCLK/LRCLK %d is not divisible by %d rx tdm slots", + __func__, div, mcasp->tdm_slots_rx); break; default: @@ -684,6 +808,20 @@ static int davinci_mcasp_set_sysclk(struct snd_soc_dai *dai, int clk_id, mcasp_clr_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, AHCLKRE); clear_bit(PIN_BIT_AHCLKX, &mcasp->pdir); + mcasp->sysclk_freq_tx = freq; + mcasp->sysclk_freq_rx = freq; + break; + case MCASP_CLK_HCLK_AHCLK_TXONLY: + mcasp_clr_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, + AHCLKXE); + clear_bit(PIN_BIT_AHCLKX, &mcasp->pdir); + mcasp->sysclk_freq_tx = freq; + break; + case MCASP_CLK_HCLK_AHCLK_RXONLY: + mcasp_clr_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, + AHCLKRE); + clear_bit(PIN_BIT_AHCLKR, &mcasp->pdir); + mcasp->sysclk_freq_rx = freq; break; case MCASP_CLK_HCLK_AUXCLK: mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, @@ -691,22 +829,56 @@ static int davinci_mcasp_set_sysclk(struct snd_soc_dai *dai, int clk_id, mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, AHCLKRE); set_bit(PIN_BIT_AHCLKX, &mcasp->pdir); + mcasp->sysclk_freq_tx = freq; + mcasp->sysclk_freq_rx = freq; + break; + case MCASP_CLK_HCLK_AUXCLK_TXONLY: + mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, + AHCLKXE); + set_bit(PIN_BIT_AHCLKX, &mcasp->pdir); + mcasp->sysclk_freq_tx = freq; + break; + case MCASP_CLK_HCLK_AUXCLK_RXONLY: + mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, + AHCLKRE); + set_bit(PIN_BIT_AHCLKR, &mcasp->pdir); + mcasp->sysclk_freq_rx = freq; break; default: dev_err(mcasp->dev, "Invalid clk id: %d\n", clk_id); goto out; } } else { - /* Select AUXCLK as HCLK */ - mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, AHCLKXE); - mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, AHCLKRE); - set_bit(PIN_BIT_AHCLKX, &mcasp->pdir); + /* McASP is clock master, select AUXCLK as HCLK */ + switch (clk_id) { + case MCASP_CLK_HCLK_AUXCLK_TXONLY: + mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, + AHCLKXE); + set_bit(PIN_BIT_AHCLKX, &mcasp->pdir); + mcasp->sysclk_freq_tx = freq; + break; + case MCASP_CLK_HCLK_AUXCLK_RXONLY: + mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, + AHCLKRE); + set_bit(PIN_BIT_AHCLKR, &mcasp->pdir); + mcasp->sysclk_freq_rx = freq; + break; + default: + mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, + AHCLKXE); + mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, + AHCLKRE); + set_bit(PIN_BIT_AHCLKX, &mcasp->pdir); + set_bit(PIN_BIT_AHCLKR, &mcasp->pdir); + mcasp->sysclk_freq_tx = freq; + mcasp->sysclk_freq_rx = freq; + break; + } } /* * When AHCLK X/R is selected to be output it means that the HCLK is * the same clock - coming via AUXCLK. */ - mcasp->sysclk_freq = freq; out: pm_runtime_put(mcasp->dev); return 0; @@ -718,9 +890,11 @@ static int davinci_mcasp_ch_constraint(struct davinci_mcasp *mcasp, int stream, { struct snd_pcm_hw_constraint_list *cl = &mcasp->chconstr[stream]; unsigned int *list = (unsigned int *) cl->list; - int slots = mcasp->tdm_slots; + int slots; int i, count = 0; + slots = mcasp_get_tdm_slots(mcasp, stream); + if (mcasp->tdm_mask[stream]) slots = hweight32(mcasp->tdm_mask[stream]); @@ -785,27 +959,42 @@ static int davinci_mcasp_set_tdm_slot(struct snd_soc_dai *dai, return -EINVAL; } - mcasp->tdm_slots = slots; + if (mcasp->async_mode) { + if (tx_mask) { + mcasp->tdm_slots_tx = slots; + mcasp->slot_width_tx = slot_width; + } + if (rx_mask) { + mcasp->tdm_slots_rx = slots; + mcasp->slot_width_rx = slot_width; + } + } else { + mcasp->tdm_slots_tx = slots; + mcasp->tdm_slots_rx = slots; + mcasp->slot_width_tx = slot_width; + mcasp->slot_width_rx = slot_width; + } + mcasp->tdm_mask[SNDRV_PCM_STREAM_PLAYBACK] = tx_mask; mcasp->tdm_mask[SNDRV_PCM_STREAM_CAPTURE] = rx_mask; - mcasp->slot_width = slot_width; return davinci_mcasp_set_ch_constraints(mcasp); } static int davinci_config_channel_size(struct davinci_mcasp *mcasp, - int sample_width) + int sample_width, int stream) { u32 fmt; u32 tx_rotate, rx_rotate, slot_width; u32 mask = (1ULL << sample_width) - 1; - if (mcasp->slot_width) - slot_width = mcasp->slot_width; - else if (mcasp->max_format_width) - slot_width = mcasp->max_format_width; - else - slot_width = sample_width; + slot_width = mcasp_get_slot_width(mcasp, stream); + if (!slot_width) { + if (mcasp->max_format_width) + slot_width = mcasp->max_format_width; + else + slot_width = sample_width; + } /* * TX rotation: * right aligned formats: rotate w/ slot_width @@ -828,17 +1017,23 @@ static int davinci_config_channel_size(struct davinci_mcasp *mcasp, fmt = (slot_width >> 1) - 1; if (mcasp->op_mode != DAVINCI_MCASP_DIT_MODE) { - mcasp_mod_bits(mcasp, DAVINCI_MCASP_RXFMT_REG, RXSSZ(fmt), - RXSSZ(0x0F)); - mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, TXSSZ(fmt), - TXSSZ(0x0F)); - mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, TXROT(tx_rotate), - TXROT(7)); - mcasp_mod_bits(mcasp, DAVINCI_MCASP_RXFMT_REG, RXROT(rx_rotate), - RXROT(7)); - mcasp_set_reg(mcasp, DAVINCI_MCASP_RXMASK_REG, mask); + if (!mcasp->async_mode || stream == SNDRV_PCM_STREAM_PLAYBACK) { + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, TXSSZ(fmt), + TXSSZ(0x0F)); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, TXROT(tx_rotate), + TXROT(7)); + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXMASK_REG, mask); + } + if (!mcasp->async_mode || stream == SNDRV_PCM_STREAM_CAPTURE) { + mcasp_mod_bits(mcasp, DAVINCI_MCASP_RXFMT_REG, RXSSZ(fmt), + RXSSZ(0x0F)); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_RXFMT_REG, RXROT(rx_rotate), + RXROT(7)); + mcasp_set_reg(mcasp, DAVINCI_MCASP_RXMASK_REG, mask); + } } else { /* + * DIT mode only use TX serializers * according to the TRM it should be TXROT=0, this one works: * 16 bit to 23-8 (TXROT=6, rotate 24 bits) * 24 bit to 23-0 (TXROT=0, rotate 0 bits) @@ -851,10 +1046,9 @@ static int davinci_config_channel_size(struct davinci_mcasp *mcasp, TXROT(7)); mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, TXSSZ(15), TXSSZ(0x0F)); + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXMASK_REG, mask); } - mcasp_set_reg(mcasp, DAVINCI_MCASP_TXMASK_REG, mask); - return 0; } @@ -865,11 +1059,13 @@ static int mcasp_common_hw_param(struct davinci_mcasp *mcasp, int stream, int i; u8 tx_ser = 0; u8 rx_ser = 0; - u8 slots = mcasp->tdm_slots; + int slots; u8 max_active_serializers, max_rx_serializers, max_tx_serializers; int active_serializers, numevt; u32 reg; + slots = mcasp_get_tdm_slots(mcasp, stream); + /* In DIT mode we only allow maximum of one serializers for now */ if (mcasp->op_mode == DAVINCI_MCASP_DIT_MODE) max_active_serializers = 1; @@ -997,7 +1193,7 @@ static int mcasp_i2s_hw_param(struct davinci_mcasp *mcasp, int stream, u32 mask = 0; u32 busel = 0; - total_slots = mcasp->tdm_slots; + total_slots = mcasp_get_tdm_slots(mcasp, stream); /* * If more than one serializer is needed, then use them with @@ -1028,7 +1224,10 @@ static int mcasp_i2s_hw_param(struct davinci_mcasp *mcasp, int stream, mask |= (1 << i); } - mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, TX_ASYNC); + if (mcasp->async_mode) + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, TX_ASYNC); + else + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, TX_ASYNC); if (!mcasp->dat_port) busel = TXSEL; @@ -1127,16 +1326,33 @@ static int mcasp_dit_hw_param(struct davinci_mcasp *mcasp, static int davinci_mcasp_calc_clk_div(struct davinci_mcasp *mcasp, unsigned int sysclk_freq, - unsigned int bclk_freq, bool set) + unsigned int bclk_freq, + int stream, + bool set) { - u32 reg = mcasp_get_reg(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG); int div = sysclk_freq / bclk_freq; int rem = sysclk_freq % bclk_freq; int error_ppm; int aux_div = 1; + int bclk_div_id, auxclk_div_id; + bool auxclk_enabled; + + if (mcasp->async_mode && stream == SNDRV_PCM_STREAM_CAPTURE) { + auxclk_enabled = mcasp_get_reg(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG) & AHCLKRE; + bclk_div_id = MCASP_CLKDIV_BCLK_RXONLY; + auxclk_div_id = MCASP_CLKDIV_AUXCLK_RXONLY; + } else if (mcasp->async_mode && stream == SNDRV_PCM_STREAM_PLAYBACK) { + auxclk_enabled = mcasp_get_reg(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG) & AHCLKXE; + bclk_div_id = MCASP_CLKDIV_BCLK_TXONLY; + auxclk_div_id = MCASP_CLKDIV_AUXCLK_TXONLY; + } else { + auxclk_enabled = mcasp_get_reg(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG) & AHCLKXE; + bclk_div_id = MCASP_CLKDIV_BCLK; + auxclk_div_id = MCASP_CLKDIV_AUXCLK; + } if (div > (ACLKXDIV_MASK + 1)) { - if (reg & AHCLKXE) { + if (auxclk_enabled) { aux_div = div / (ACLKXDIV_MASK + 1); if (div % (ACLKXDIV_MASK + 1)) aux_div++; @@ -1166,10 +1382,10 @@ static int davinci_mcasp_calc_clk_div(struct davinci_mcasp *mcasp, dev_info(mcasp->dev, "Sample-rate is off by %d PPM\n", error_ppm); - __davinci_mcasp_set_clkdiv(mcasp, MCASP_CLKDIV_BCLK, div, 0); - if (reg & AHCLKXE) - __davinci_mcasp_set_clkdiv(mcasp, MCASP_CLKDIV_AUXCLK, - aux_div, 0); + __davinci_mcasp_set_clkdiv(mcasp, bclk_div_id, div, false); + if (auxclk_enabled) + __davinci_mcasp_set_clkdiv(mcasp, auxclk_div_id, + aux_div, false); } return error_ppm; @@ -1220,6 +1436,7 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, int channels = params_channels(params); int period_size = params_period_size(params); int ret; + unsigned int sysclk_freq = mcasp_get_sysclk_freq(mcasp, substream->stream); switch (params_format(params)) { case SNDRV_PCM_FORMAT_U8: @@ -1260,22 +1477,26 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, * If mcasp is BCLK master, and a BCLK divider was not provided by * the machine driver, we need to calculate the ratio. */ - if (mcasp->bclk_master && mcasp->bclk_div == 0 && mcasp->sysclk_freq) { - int slots = mcasp->tdm_slots; + if (mcasp->bclk_master && mcasp_get_bclk_div(mcasp, substream->stream) == 0 && + sysclk_freq) { + int slots, slot_width; int rate = params_rate(params); int sbits = params_width(params); unsigned int bclk_target; - if (mcasp->slot_width) - sbits = mcasp->slot_width; + slots = mcasp_get_tdm_slots(mcasp, substream->stream); + + slot_width = mcasp_get_slot_width(mcasp, substream->stream); + if (slot_width) + sbits = slot_width; if (mcasp->op_mode == DAVINCI_MCASP_IIS_MODE) bclk_target = rate * sbits * slots; else bclk_target = rate * 128; - davinci_mcasp_calc_clk_div(mcasp, mcasp->sysclk_freq, - bclk_target, true); + davinci_mcasp_calc_clk_div(mcasp, sysclk_freq, + bclk_target, substream->stream, true); } ret = mcasp_common_hw_param(mcasp, substream->stream, @@ -1292,9 +1513,10 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, if (ret) return ret; - davinci_config_channel_size(mcasp, word_length); + davinci_config_channel_size(mcasp, word_length, substream->stream); - if (mcasp->op_mode == DAVINCI_MCASP_IIS_MODE) { + /* Channel constraints are disabled for async mode */ + if (mcasp->op_mode == DAVINCI_MCASP_IIS_MODE && !mcasp->async_mode) { mcasp->channels = channels; if (!mcasp->max_format_width) mcasp->max_format_width = word_length; @@ -1338,7 +1560,7 @@ static int davinci_mcasp_hw_rule_slot_width(struct snd_pcm_hw_params *params, snd_pcm_format_t i; snd_mask_none(&nfmt); - slot_width = rd->mcasp->slot_width; + slot_width = mcasp_get_slot_width(rd->mcasp, rd->stream); pcm_for_each_format(i) { if (snd_mask_test_format(fmt, i)) { @@ -1388,12 +1610,15 @@ static int davinci_mcasp_hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_interval *ri = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); int sbits = params_width(params); - int slots = rd->mcasp->tdm_slots; + int slots, slot_width; struct snd_interval range; int i; - if (rd->mcasp->slot_width) - sbits = rd->mcasp->slot_width; + slots = mcasp_get_tdm_slots(rd->mcasp, rd->stream); + + slot_width = mcasp_get_slot_width(rd->mcasp, rd->stream); + if (slot_width) + sbits = slot_width; snd_interval_any(&range); range.empty = 1; @@ -1403,16 +1628,17 @@ static int davinci_mcasp_hw_rule_rate(struct snd_pcm_hw_params *params, uint bclk_freq = sbits * slots * davinci_mcasp_dai_rates[i]; unsigned int sysclk_freq; + unsigned int ratio; int ppm; - if (rd->mcasp->auxclk_fs_ratio) - sysclk_freq = davinci_mcasp_dai_rates[i] * - rd->mcasp->auxclk_fs_ratio; + ratio = mcasp_get_auxclk_fs_ratio(rd->mcasp, rd->stream); + if (ratio) + sysclk_freq = davinci_mcasp_dai_rates[i] * ratio; else - sysclk_freq = rd->mcasp->sysclk_freq; + sysclk_freq = mcasp_get_sysclk_freq(rd->mcasp, rd->stream); ppm = davinci_mcasp_calc_clk_div(rd->mcasp, sysclk_freq, - bclk_freq, false); + bclk_freq, rd->stream, false); if (abs(ppm) < DAVINCI_MAX_RATE_ERROR_PPM) { if (range.empty) { range.min = davinci_mcasp_dai_rates[i]; @@ -1438,30 +1664,34 @@ static int davinci_mcasp_hw_rule_format(struct snd_pcm_hw_params *params, struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); struct snd_mask nfmt; int rate = params_rate(params); - int slots = rd->mcasp->tdm_slots; + int slots; int count = 0; snd_pcm_format_t i; + slots = mcasp_get_tdm_slots(rd->mcasp, rd->stream); + snd_mask_none(&nfmt); pcm_for_each_format(i) { if (snd_mask_test_format(fmt, i)) { uint sbits = snd_pcm_format_width(i); unsigned int sysclk_freq; - int ppm; + unsigned int ratio; + int ppm, slot_width; - if (rd->mcasp->auxclk_fs_ratio) - sysclk_freq = rate * - rd->mcasp->auxclk_fs_ratio; + ratio = mcasp_get_auxclk_fs_ratio(rd->mcasp, rd->stream); + if (ratio) + sysclk_freq = rate * ratio; else - sysclk_freq = rd->mcasp->sysclk_freq; + sysclk_freq = mcasp_get_sysclk_freq(rd->mcasp, rd->stream); - if (rd->mcasp->slot_width) - sbits = rd->mcasp->slot_width; + slot_width = mcasp_get_slot_width(rd->mcasp, rd->stream); + if (slot_width) + sbits = slot_width; ppm = davinci_mcasp_calc_clk_div(rd->mcasp, sysclk_freq, sbits * slots * rate, - false); + rd->stream, false); if (abs(ppm) < DAVINCI_MAX_RATE_ERROR_PPM) { snd_mask_set_format(&nfmt, i); count++; @@ -1498,7 +1728,7 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, &mcasp->ruledata[substream->stream]; u32 max_channels = 0; int i, dir, ret; - int tdm_slots = mcasp->tdm_slots; + int tdm_slots; u8 *numevt; /* Do not allow more then one stream per direction */ @@ -1507,6 +1737,8 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, mcasp->substreams[substream->stream] = substream; + tdm_slots = mcasp_get_tdm_slots(mcasp, substream->stream); + if (mcasp->tdm_mask[substream->stream]) tdm_slots = hweight32(mcasp->tdm_mask[substream->stream]); @@ -1528,6 +1760,7 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, } ruledata->serializers = max_channels; ruledata->mcasp = mcasp; + ruledata->stream = substream->stream; max_channels *= tdm_slots; /* * If the already active stream has less channels than the calculated @@ -1535,9 +1768,13 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, * is in use we need to use that as a constraint for the second stream. * Otherwise (first stream or less allowed channels or more than one * serializer in use) we use the calculated constraint. + * + * However, in async mode, TX and RX have independent clocks and can + * use different configurations, so don't apply the constraint. */ if (mcasp->channels && mcasp->channels < max_channels && - ruledata->serializers == 1) + ruledata->serializers == 1 && + !mcasp->async_mode) max_channels = mcasp->channels; /* * But we can always allow channels upto the amount of @@ -1554,10 +1791,10 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &mcasp->chconstr[substream->stream]); - if (mcasp->max_format_width) { + if (mcasp->max_format_width && !mcasp->async_mode) { /* * Only allow formats which require same amount of bits on the - * bus as the currently running stream + * bus as the currently running stream to ensure sync mode */ ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, @@ -1566,8 +1803,7 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, SNDRV_PCM_HW_PARAM_FORMAT, -1); if (ret) return ret; - } - else if (mcasp->slot_width) { + } else if (mcasp_get_slot_width(mcasp, substream->stream)) { /* Only allow formats require <= slot_width bits on the bus */ ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, @@ -1582,7 +1818,8 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, * If we rely on implicit BCLK divider setting we should * set constraints based on what we can provide. */ - if (mcasp->bclk_master && mcasp->bclk_div == 0 && mcasp->sysclk_freq) { + if (mcasp->bclk_master && mcasp_get_bclk_div(mcasp, substream->stream) == 0 && + mcasp_get_sysclk_freq(mcasp, substream->stream)) { ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, davinci_mcasp_hw_rule_rate, @@ -1759,8 +1996,6 @@ static struct snd_soc_dai_driver davinci_mcasp_dai[] = { .formats = DAVINCI_MCASP_PCM_FMTS, }, .ops = &davinci_mcasp_dai_ops, - - .symmetric_rate = 1, }, { .name = "davinci-mcasp.1", @@ -1918,18 +2153,33 @@ static int davinci_mcasp_get_config(struct davinci_mcasp *mcasp, goto out; } + /* Parse TX-specific TDM slot and use it as default for RX */ if (of_property_read_u32(np, "tdm-slots", &val) == 0) { if (val < 2 || val > 32) { - dev_err(&pdev->dev, "tdm-slots must be in rage [2-32]\n"); + dev_err(&pdev->dev, "tdm-slots must be in range [2-32]\n"); return -EINVAL; } - pdata->tdm_slots = val; + pdata->tdm_slots_tx = val; + pdata->tdm_slots_rx = val; } else if (pdata->op_mode == DAVINCI_MCASP_IIS_MODE) { mcasp->missing_audio_param = true; goto out; } + /* Parse RX-specific TDM slot count if provided */ + if (of_property_read_u32(np, "tdm-slots-rx", &val) == 0) { + if (val < 2 || val > 32) { + dev_err(&pdev->dev, "tdm-slots-rx must be in range [2-32]\n"); + return -EINVAL; + } + + pdata->tdm_slots_rx = val; + } + + if (pdata->op_mode != DAVINCI_MCASP_DIT_MODE) + mcasp->async_mode = of_property_read_bool(np, "ti,async-mode"); + of_serial_dir32 = of_get_property(np, "serial-dir", &val); val /= sizeof(u32); if (of_serial_dir32) { @@ -1955,8 +2205,15 @@ static int davinci_mcasp_get_config(struct davinci_mcasp *mcasp, if (of_property_read_u32(np, "rx-num-evt", &val) == 0) pdata->rxnumevt = val; - if (of_property_read_u32(np, "auxclk-fs-ratio", &val) == 0) - mcasp->auxclk_fs_ratio = val; + /* Parse TX-specific auxclk/fs ratio and use it as default for RX */ + if (of_property_read_u32(np, "auxclk-fs-ratio", &val) == 0) { + mcasp->auxclk_fs_ratio_tx = val; + mcasp->auxclk_fs_ratio_rx = val; + } + + /* Parse RX-specific auxclk/fs ratio if provided */ + if (of_property_read_u32(np, "auxclk-fs-ratio-rx", &val) == 0) + mcasp->auxclk_fs_ratio_rx = val; if (of_property_read_u32(np, "dismod", &val) == 0) { if (val == 0 || val == 2 || val == 3) { @@ -1985,19 +2242,51 @@ out: mcasp->op_mode = pdata->op_mode; /* sanity check for tdm slots parameter */ if (mcasp->op_mode == DAVINCI_MCASP_IIS_MODE) { - if (pdata->tdm_slots < 2) { - dev_warn(&pdev->dev, "invalid tdm slots: %d\n", - pdata->tdm_slots); - mcasp->tdm_slots = 2; - } else if (pdata->tdm_slots > 32) { - dev_warn(&pdev->dev, "invalid tdm slots: %d\n", - pdata->tdm_slots); - mcasp->tdm_slots = 32; + if (pdata->tdm_slots_tx < 2) { + dev_warn(&pdev->dev, "invalid tdm tx slots: %d\n", + pdata->tdm_slots_tx); + mcasp->tdm_slots_tx = 2; + } else if (pdata->tdm_slots_tx > 32) { + dev_warn(&pdev->dev, "invalid tdm tx slots: %d\n", + pdata->tdm_slots_tx); + mcasp->tdm_slots_tx = 32; + } else { + mcasp->tdm_slots_tx = pdata->tdm_slots_tx; + } + + if (pdata->tdm_slots_rx < 2) { + dev_warn(&pdev->dev, "invalid tdm rx slots: %d\n", + pdata->tdm_slots_rx); + mcasp->tdm_slots_rx = 2; + } else if (pdata->tdm_slots_rx > 32) { + dev_warn(&pdev->dev, "invalid tdm rx slots: %d\n", + pdata->tdm_slots_rx); + mcasp->tdm_slots_rx = 32; } else { - mcasp->tdm_slots = pdata->tdm_slots; + mcasp->tdm_slots_rx = pdata->tdm_slots_rx; } } else { - mcasp->tdm_slots = 32; + mcasp->tdm_slots_tx = 32; + mcasp->tdm_slots_rx = 32; + } + + /* Different TX/RX slot counts require async mode */ + if (pdata->op_mode != DAVINCI_MCASP_DIT_MODE && + mcasp->tdm_slots_tx != mcasp->tdm_slots_rx && !mcasp->async_mode) { + dev_err(&pdev->dev, + "Different TX (%d) and RX (%d) TDM slots require ti,async-mode\n", + mcasp->tdm_slots_tx, mcasp->tdm_slots_rx); + return -EINVAL; + } + + /* Different TX/RX auxclk-fs-ratio require async mode */ + if (pdata->op_mode != DAVINCI_MCASP_DIT_MODE && + mcasp->auxclk_fs_ratio_tx && mcasp->auxclk_fs_ratio_rx && + mcasp->auxclk_fs_ratio_tx != mcasp->auxclk_fs_ratio_rx && !mcasp->async_mode) { + dev_err(&pdev->dev, + "Different TX (%d) and RX (%d) auxclk-fs-ratio require ti,async-mode\n", + mcasp->auxclk_fs_ratio_tx, mcasp->auxclk_fs_ratio_rx); + return -EINVAL; } mcasp->num_serializer = pdata->num_serializer; diff --git a/sound/soc/ti/davinci-mcasp.h b/sound/soc/ti/davinci-mcasp.h index 5de2b8a31061..83b3c67f4a2b 100644 --- a/sound/soc/ti/davinci-mcasp.h +++ b/sound/soc/ti/davinci-mcasp.h @@ -298,10 +298,20 @@ /* Source of High-frequency transmit/receive clock */ #define MCASP_CLK_HCLK_AHCLK 0 /* AHCLKX/R */ #define MCASP_CLK_HCLK_AUXCLK 1 /* Internal functional clock */ +#define MCASP_CLK_HCLK_AHCLK_TXONLY 2 /* AHCLKX for TX only */ +#define MCASP_CLK_HCLK_AHCLK_RXONLY 3 /* AHCLKR for RX only */ +#define MCASP_CLK_HCLK_AUXCLK_TXONLY 4 /* AUXCLK for TX only */ +#define MCASP_CLK_HCLK_AUXCLK_RXONLY 5 /* AUXCLK for RX only */ /* clock divider IDs */ #define MCASP_CLKDIV_AUXCLK 0 /* HCLK divider from AUXCLK */ #define MCASP_CLKDIV_BCLK 1 /* BCLK divider from HCLK */ #define MCASP_CLKDIV_BCLK_FS_RATIO 2 /* to set BCLK FS ration */ +#define MCASP_CLKDIV_AUXCLK_TXONLY 3 /* AUXCLK divider for TX only */ +#define MCASP_CLKDIV_AUXCLK_RXONLY 4 /* AUXCLK divider for RX only */ +#define MCASP_CLKDIV_BCLK_TXONLY 5 /* BCLK divider for TX only */ +#define MCASP_CLKDIV_BCLK_RXONLY 6 /* BCLK divider for RX only */ +#define MCASP_CLKDIV_BCLK_FS_RATIO_TXONLY 7 /* BCLK/FS ratio for TX only */ +#define MCASP_CLKDIV_BCLK_FS_RATIO_RXONLY 8 /* BCLK/FS ratio for RX only*/ #endif /* DAVINCI_MCASP_H */ -- cgit v1.2.3 From 4d1e3e2c404dc30e039d81ba7396c8bb82ade991 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 5 Feb 2026 16:48:36 +0000 Subject: ASoC: cs35l56: Support for reading speaker ID from on-chip GPIOs Add support for using the state of pins on the amplifier to indicate the type of speaker fitted. Previously, where there were alternate speaker vendors, this was indicated using host CPU GPIOs. Some new Dell models use spare pins on the CS35L63 as GPIOs for the speaker ID detection. Cirrus-specific SDCA Disco properties provide a list of the pins to be used, and pull-up/down settings for the pads. This list is ordered, MSbit to LSbit. The code to set the firmware filename has been modified to check for using chip pins for speaker ID. The entire block of code to set firmware name has been moved out of cs35l56_component_probe() into its own function to make it easier to KUnit test. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20260205164838.1611295-2-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- include/sound/cs35l56.h | 37 +++++++++ sound/soc/codecs/cs35l56-shared.c | 167 ++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/cs35l56.c | 141 +++++++++++++++++++++++++++++++- sound/soc/codecs/cs35l56.h | 2 + 4 files changed, 343 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h index 5928af539c46..ae1e1489b671 100644 --- a/include/sound/cs35l56.h +++ b/include/sound/cs35l56.h @@ -9,6 +9,7 @@ #ifndef __CS35L56_H #define __CS35L56_H +#include #include #include #include @@ -26,6 +27,9 @@ struct snd_ctl_elem_value; #define CS35L56_GLOBAL_ENABLES 0x0002014 #define CS35L56_BLOCK_ENABLES 0x0002018 #define CS35L56_BLOCK_ENABLES2 0x000201C +#define CS35L56_SYNC_GPIO1_CFG 0x0002410 +#define CS35L56_ASP2_DIO_GPIO13_CFG 0x0002440 +#define CS35L56_UPDATE_REGS 0x0002A0C #define CS35L56_REFCLK_INPUT 0x0002C04 #define CS35L56_GLOBAL_SAMPLE_RATE 0x0002C0C #define CS35L56_OTP_MEM_53 0x00300D4 @@ -65,6 +69,9 @@ struct snd_ctl_elem_value; #define CS35L56_IRQ1_MASK_8 0x000E0AC #define CS35L56_IRQ1_MASK_18 0x000E0D4 #define CS35L56_IRQ1_MASK_20 0x000E0DC +#define CS35L56_GPIO_STATUS1 0x000F000 +#define CS35L56_GPIO1_CTRL1 0x000F008 +#define CS35L56_GPIO13_CTRL1 0x000F038 #define CS35L56_MIXER_NGATE_CH1_CFG 0x0010004 #define CS35L56_MIXER_NGATE_CH2_CFG 0x0010008 #define CS35L56_DSP_MBOX_1_RAW 0x0011000 @@ -130,6 +137,17 @@ struct snd_ctl_elem_value; #define CS35L56_MTLREVID_MASK 0x0000000F #define CS35L56_REVID_B0 0x000000B0 +/* PAD_INTF */ +#define CS35L56_PAD_GPIO_PULL_MASK GENMASK(3, 2) +#define CS35L56_PAD_GPIO_IE BIT(0) + +#define CS35L56_PAD_PULL_NONE 0 +#define CS35L56_PAD_PULL_UP 1 +#define CS35L56_PAD_PULL_DOWN 2 + +/* UPDATE_REGS */ +#define CS35L56_UPDT_GPIO_PRES BIT(6) + /* ASP_ENABLES1 */ #define CS35L56_ASP_RX2_EN_SHIFT 17 #define CS35L56_ASP_RX1_EN_SHIFT 16 @@ -185,6 +203,12 @@ struct snd_ctl_elem_value; /* MIXER_NGATE_CHn_CFG */ #define CS35L56_AUX_NGATE_CHn_EN 0x00000001 +/* GPIOn_CTRL1 */ +#define CS35L56_GPIO_DIR_MASK BIT(31) +#define CS35L56_GPIO_FN_MASK GENMASK(2, 0) + +#define CS35L56_GPIO_FN_GPIO 0x00000001 + /* Mixer input sources */ #define CS35L56_INPUT_SRC_NONE 0x00 #define CS35L56_INPUT_SRC_ASP1RX1 0x08 @@ -279,6 +303,7 @@ struct snd_ctl_elem_value; #define CS35L56_HALO_STATE_TIMEOUT_US 250000 #define CS35L56_RESET_PULSE_MIN_US 1100 #define CS35L56_WAKE_HOLD_TIME_US 1000 +#define CS35L56_PAD_PULL_SETTLE_US 10 #define CS35L56_CALIBRATION_POLL_US (100 * USEC_PER_MSEC) #define CS35L56_CALIBRATION_TIMEOUT_US (5 * USEC_PER_SEC) @@ -289,6 +314,9 @@ struct snd_ctl_elem_value; #define CS35L56_NUM_BULK_SUPPLIES 3 #define CS35L56_NUM_DSP_REGIONS 5 +#define CS35L56_MAX_GPIO 13 +#define CS35L63_MAX_GPIO 9 + /* Additional margin for SYSTEM_RESET to control port ready on SPI */ #define CS35L56_SPI_RESET_TO_PORT_READY_US (CS35L56_CONTROL_PORT_READY_US + 2500) @@ -338,6 +366,10 @@ struct cs35l56_base { const struct cirrus_amp_cal_controls *calibration_controls; struct dentry *debugfs; u64 silicon_uid; + u8 onchip_spkid_gpios[5]; + u8 num_onchip_spkid_gpios; + u8 onchip_spkid_pulls[5]; + u8 num_onchip_spkid_pulls; }; static inline bool cs35l56_is_otp_register(unsigned int reg) @@ -413,6 +445,11 @@ void cs35l56_warn_if_firmware_missing(struct cs35l56_base *cs35l56_base); void cs35l56_log_tuning(struct cs35l56_base *cs35l56_base, struct cs_dsp *cs_dsp); int cs35l56_hw_init(struct cs35l56_base *cs35l56_base); int cs35l56_get_speaker_id(struct cs35l56_base *cs35l56_base); +int cs35l56_check_and_save_onchip_spkid_gpios(struct cs35l56_base *cs35l56_base, + const u32 *gpios, int num_gpios, + const u32 *pulls, int num_pulls); +int cs35l56_configure_onchip_spkid_pads(struct cs35l56_base *cs35l56_base); +int cs35l56_read_onchip_spkid(struct cs35l56_base *cs35l56_base); int cs35l56_get_bclk_freq_id(unsigned int freq); void cs35l56_fill_supply_names(struct regulator_bulk_data *data); diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c index 60100c8f8c95..55c75b9e4172 100644 --- a/sound/soc/codecs/cs35l56-shared.c +++ b/sound/soc/codecs/cs35l56-shared.c @@ -6,12 +6,14 @@ // Cirrus Logic International Semiconductor Ltd. #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -182,6 +184,8 @@ static bool cs35l56_readable_reg(struct device *dev, unsigned int reg) case CS35L56_OTP_MEM_53: case CS35L56_OTP_MEM_54: case CS35L56_OTP_MEM_55: + case CS35L56_SYNC_GPIO1_CFG ... CS35L56_ASP2_DIO_GPIO13_CFG: + case CS35L56_UPDATE_REGS: case CS35L56_ASP1_ENABLES1: case CS35L56_ASP1_CONTROL1: case CS35L56_ASP1_CONTROL2: @@ -213,6 +217,7 @@ static bool cs35l56_readable_reg(struct device *dev, unsigned int reg) case CS35L56_IRQ1_MASK_8: case CS35L56_IRQ1_MASK_18: case CS35L56_IRQ1_MASK_20: + case CS35L56_GPIO_STATUS1 ... CS35L56_GPIO13_CTRL1: case CS35L56_MIXER_NGATE_CH1_CFG: case CS35L56_MIXER_NGATE_CH2_CFG: case CS35L56_DSP_VIRTUAL1_MBOX_1: @@ -262,6 +267,8 @@ static bool cs35l56_common_volatile_reg(unsigned int reg) case CS35L56_GLOBAL_ENABLES: /* owned by firmware */ case CS35L56_BLOCK_ENABLES: /* owned by firmware */ case CS35L56_BLOCK_ENABLES2: /* owned by firmware */ + case CS35L56_SYNC_GPIO1_CFG ... CS35L56_ASP2_DIO_GPIO13_CFG: + case CS35L56_UPDATE_REGS: case CS35L56_REFCLK_INPUT: /* owned by firmware */ case CS35L56_GLOBAL_SAMPLE_RATE: /* owned by firmware */ case CS35L56_DACPCM1_INPUT: /* owned by firmware */ @@ -272,6 +279,7 @@ static bool cs35l56_common_volatile_reg(unsigned int reg) case CS35L56_IRQ1_EINT_1 ... CS35L56_IRQ1_EINT_8: case CS35L56_IRQ1_EINT_18: case CS35L56_IRQ1_EINT_20: + case CS35L56_GPIO_STATUS1 ... CS35L56_GPIO13_CTRL1: case CS35L56_MIXER_NGATE_CH1_CFG: case CS35L56_MIXER_NGATE_CH2_CFG: case CS35L56_DSP_VIRTUAL1_MBOX_1: @@ -1552,6 +1560,165 @@ err: } EXPORT_SYMBOL_NS_GPL(cs35l56_get_speaker_id, "SND_SOC_CS35L56_SHARED"); +int cs35l56_check_and_save_onchip_spkid_gpios(struct cs35l56_base *cs35l56_base, + const u32 *gpios, int num_gpios, + const u32 *pulls, int num_pulls) +{ + int max_gpio; + int ret = 0; + int i; + + if ((num_gpios > ARRAY_SIZE(cs35l56_base->onchip_spkid_gpios)) || + (num_pulls > ARRAY_SIZE(cs35l56_base->onchip_spkid_pulls))) + return -EOVERFLOW; + + switch (cs35l56_base->type) { + case 0x54: + case 0x56: + case 0x57: + max_gpio = CS35L56_MAX_GPIO; + break; + default: + max_gpio = CS35L63_MAX_GPIO; + break; + } + + for (i = 0; i < num_gpios; i++) { + if (gpios[i] < 1 || gpios[i] > max_gpio) { + dev_err(cs35l56_base->dev, "Invalid spkid GPIO %d\n", gpios[i]); + /* Keep going so we log all bad values */ + ret = -EINVAL; + } + + /* Change to zero-based */ + cs35l56_base->onchip_spkid_gpios[i] = gpios[i] - 1; + } + + for (i = 0; i < num_pulls; i++) { + switch (pulls[i]) { + case 0: + cs35l56_base->onchip_spkid_pulls[i] = CS35L56_PAD_PULL_NONE; + break; + case 1: + cs35l56_base->onchip_spkid_pulls[i] = CS35L56_PAD_PULL_UP; + break; + case 2: + cs35l56_base->onchip_spkid_pulls[i] = CS35L56_PAD_PULL_DOWN; + break; + default: + dev_err(cs35l56_base->dev, "Invalid spkid pull %d\n", pulls[i]); + /* Keep going so we log all bad values */ + ret = -EINVAL; + break; + } + } + if (ret) + return ret; + + cs35l56_base->num_onchip_spkid_gpios = num_gpios; + cs35l56_base->num_onchip_spkid_pulls = num_pulls; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_check_and_save_onchip_spkid_gpios, "SND_SOC_CS35L56_SHARED"); + +/* Caller must pm_runtime resume before calling this function */ +int cs35l56_configure_onchip_spkid_pads(struct cs35l56_base *cs35l56_base) +{ + struct regmap *regmap = cs35l56_base->regmap; + unsigned int addr_offset, val; + int num_gpios, num_pulls; + int i, ret; + + if (cs35l56_base->num_onchip_spkid_gpios == 0) + return 0; + + num_gpios = min(cs35l56_base->num_onchip_spkid_gpios, + ARRAY_SIZE(cs35l56_base->onchip_spkid_gpios)); + num_pulls = min(cs35l56_base->num_onchip_spkid_pulls, + ARRAY_SIZE(cs35l56_base->onchip_spkid_pulls)); + + for (i = 0; i < num_gpios; i++) { + addr_offset = cs35l56_base->onchip_spkid_gpios[i] * sizeof(u32); + + /* Set unspecified pulls to NONE */ + if (i < num_pulls) { + val = FIELD_PREP(CS35L56_PAD_GPIO_PULL_MASK, + cs35l56_base->onchip_spkid_pulls[i]); + } else { + val = FIELD_PREP(CS35L56_PAD_GPIO_PULL_MASK, CS35L56_PAD_PULL_NONE); + } + + ret = regmap_update_bits(regmap, CS35L56_SYNC_GPIO1_CFG + addr_offset, + CS35L56_PAD_GPIO_PULL_MASK | CS35L56_PAD_GPIO_IE, + val | CS35L56_PAD_GPIO_IE); + if (ret) { + dev_err(cs35l56_base->dev, "GPIO%d set pad fail: %d\n", + cs35l56_base->onchip_spkid_gpios[i] + 1, ret); + return ret; + } + } + + ret = regmap_write(regmap, CS35L56_UPDATE_REGS, CS35L56_UPDT_GPIO_PRES); + if (ret) { + dev_err(cs35l56_base->dev, "UPDT_GPIO_PRES failed:%d\n", ret); + return ret; + } + + usleep_range(CS35L56_PAD_PULL_SETTLE_US, CS35L56_PAD_PULL_SETTLE_US * 2); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_configure_onchip_spkid_pads, "SND_SOC_CS35L56_SHARED"); + +/* Caller must pm_runtime resume before calling this function */ +int cs35l56_read_onchip_spkid(struct cs35l56_base *cs35l56_base) +{ + struct regmap *regmap = cs35l56_base->regmap; + unsigned int addr_offset, val; + int num_gpios; + int speaker_id = 0; + int i, ret; + + if (cs35l56_base->num_onchip_spkid_gpios == 0) + return -ENOENT; + + num_gpios = min(cs35l56_base->num_onchip_spkid_gpios, + ARRAY_SIZE(cs35l56_base->onchip_spkid_gpios)); + + for (i = 0; i < num_gpios; i++) { + addr_offset = cs35l56_base->onchip_spkid_gpios[i] * sizeof(u32); + + ret = regmap_update_bits(regmap, CS35L56_GPIO1_CTRL1 + addr_offset, + CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_MASK, + CS35L56_GPIO_DIR_MASK | CS35L56_GPIO_FN_GPIO); + if (ret) { + dev_err(cs35l56_base->dev, "GPIO%u set func fail: %d\n", + cs35l56_base->onchip_spkid_gpios[i] + 1, ret); + return ret; + } + } + + ret = regmap_read(regmap, CS35L56_GPIO_STATUS1, &val); + if (ret) { + dev_err(cs35l56_base->dev, "GPIO%d status read failed: %d\n", + cs35l56_base->onchip_spkid_gpios[i] + 1, ret); + return ret; + } + + for (i = 0; i < num_gpios; i++) { + speaker_id <<= 1; + + if (val & BIT(cs35l56_base->onchip_spkid_gpios[i])) + speaker_id |= 1; + } + + dev_dbg(cs35l56_base->dev, "Onchip GPIO Speaker ID = %d\n", speaker_id); + + return speaker_id; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_read_onchip_spkid, "SND_SOC_CS35L56_SHARED"); + static const u32 cs35l56_bclk_valid_for_pll_freq_table[] = { [0x0C] = 128000, [0x0F] = 256000, diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 31dd2f7b2858..2ff8b172b76e 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -1179,15 +1179,28 @@ VISIBLE_IF_KUNIT int cs35l56_set_fw_suffix(struct cs35l56_private *cs35l56) } EXPORT_SYMBOL_IF_KUNIT(cs35l56_set_fw_suffix); -static int cs35l56_component_probe(struct snd_soc_component *component) +VISIBLE_IF_KUNIT int cs35l56_set_fw_name(struct snd_soc_component *component) { - struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component); - struct dentry *debugfs_root = component->debugfs_root; unsigned short vendor, device; int ret; - BUILD_BUG_ON(ARRAY_SIZE(cs35l56_tx_input_texts) != ARRAY_SIZE(cs35l56_tx_input_values)); + if ((cs35l56->speaker_id < 0) && cs35l56->base.num_onchip_spkid_gpios) { + PM_RUNTIME_ACQUIRE(cs35l56->base.dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = cs35l56_configure_onchip_spkid_pads(&cs35l56->base); + if (ret) + return ret; + + ret = cs35l56_read_onchip_spkid(&cs35l56->base); + if (ret < 0) + return ret; + + cs35l56->speaker_id = ret; + } if (!cs35l56->dsp.system_name && (snd_soc_card_get_pci_ssid(component->card, &vendor, &device) == 0)) { @@ -1208,6 +1221,19 @@ static int cs35l56_component_probe(struct snd_soc_component *component) return -ENOMEM; } + return 0; +} +EXPORT_SYMBOL_IF_KUNIT(cs35l56_set_fw_name); + +static int cs35l56_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component); + struct dentry *debugfs_root = component->debugfs_root; + int ret; + + BUILD_BUG_ON(ARRAY_SIZE(cs35l56_tx_input_texts) != ARRAY_SIZE(cs35l56_tx_input_values)); + if (!wait_for_completion_timeout(&cs35l56->init_completion, msecs_to_jiffies(5000))) { dev_err(cs35l56->base.dev, "%s: init_completion timed out\n", __func__); @@ -1219,6 +1245,10 @@ static int cs35l56_component_probe(struct snd_soc_component *component) return -ENOMEM; cs35l56->component = component; + ret = cs35l56_set_fw_name(component); + if (ret) + return ret; + ret = cs35l56_set_fw_suffix(cs35l56); if (ret) return ret; @@ -1532,6 +1562,105 @@ static int cs35l56_dsp_init(struct cs35l56_private *cs35l56) return 0; } +static int cs35l56_read_fwnode_u32_array(struct device *dev, + struct fwnode_handle *parent_node, + const char *prop_name, + int max_count, + u32 *dest) +{ + int count, ret; + + count = fwnode_property_count_u32(parent_node, prop_name); + if ((count == 0) || (count == -EINVAL) || (count == -ENODATA)) { + dev_dbg(dev, "%s not found in %s\n", prop_name, fwnode_get_name(parent_node)); + return 0; + } + + if (count < 0) { + dev_err(dev, "Get %s error:%d\n", prop_name, count); + return count; + } + + if (count > max_count) { + dev_err(dev, "%s too many entries (%d)\n", prop_name, count); + return -EOVERFLOW; + } + + ret = fwnode_property_read_u32_array(parent_node, prop_name, dest, count); + if (ret) { + dev_err(dev, "Error reading %s: %d\n", prop_name, ret); + return ret; + } + + return count; +} + +static int cs35l56_process_xu_onchip_speaker_id(struct cs35l56_private *cs35l56, + struct fwnode_handle *ext_node) +{ + static const char * const gpio_name = "01fa-spk-id-gpios-onchip"; + static const char * const pull_name = "01fa-spk-id-gpios-onchip-pull"; + u32 gpios[5], pulls[5]; + int num_gpios, num_pulls; + int ret; + + static_assert(ARRAY_SIZE(gpios) == ARRAY_SIZE(cs35l56->base.onchip_spkid_gpios)); + static_assert(ARRAY_SIZE(pulls) == ARRAY_SIZE(cs35l56->base.onchip_spkid_pulls)); + + num_gpios = cs35l56_read_fwnode_u32_array(cs35l56->base.dev, ext_node, gpio_name, + ARRAY_SIZE(gpios), gpios); + if (num_gpios < 1) + return num_gpios; + + num_pulls = cs35l56_read_fwnode_u32_array(cs35l56->base.dev, ext_node, pull_name, + ARRAY_SIZE(pulls), pulls); + if (num_pulls < 0) + return num_pulls; + + if (num_pulls != num_gpios) { + dev_warn(cs35l56->base.dev, "%s count(%d) != %s count(%d)\n", + pull_name, num_pulls, gpio_name, num_gpios); + } + + ret = cs35l56_check_and_save_onchip_spkid_gpios(&cs35l56->base, + gpios, num_gpios, + pulls, num_pulls); + if (ret) { + return dev_err_probe(cs35l56->base.dev, ret, "Error in %s/%s\n", + gpio_name, pull_name); + } + + return 0; +} + +VISIBLE_IF_KUNIT int cs35l56_process_xu_properties(struct cs35l56_private *cs35l56) +{ + struct fwnode_handle *ext_node = NULL; + struct fwnode_handle *link; + int ret; + + if (!cs35l56->sdw_peripheral) + return 0; + + fwnode_for_each_child_node(dev_fwnode(cs35l56->base.dev), link) { + ext_node = fwnode_get_named_child_node(link, + "mipi-sdca-function-expansion-subproperties"); + if (ext_node) { + fwnode_handle_put(link); + break; + } + } + + if (!ext_node) + return 0; + + ret = cs35l56_process_xu_onchip_speaker_id(cs35l56, ext_node); + fwnode_handle_put(ext_node); + + return ret; +} +EXPORT_SYMBOL_IF_KUNIT(cs35l56_process_xu_properties); + static int cs35l56_get_firmware_uid(struct cs35l56_private *cs35l56) { struct device *dev = cs35l56->base.dev; @@ -1712,6 +1841,10 @@ int cs35l56_common_probe(struct cs35l56_private *cs35l56) if (ret != 0) goto err; + ret = cs35l56_process_xu_properties(cs35l56); + if (ret) + goto err; + ret = cs35l56_dsp_init(cs35l56); if (ret < 0) { dev_err_probe(cs35l56->base.dev, ret, "DSP init failed\n"); diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h index 7187885a13c1..691f857d0bd8 100644 --- a/sound/soc/codecs/cs35l56.h +++ b/sound/soc/codecs/cs35l56.h @@ -76,6 +76,8 @@ void cs35l56_remove(struct cs35l56_private *cs35l56); #if IS_ENABLED(CONFIG_KUNIT) int cs35l56_set_fw_suffix(struct cs35l56_private *cs35l56); +int cs35l56_set_fw_name(struct snd_soc_component *component); +int cs35l56_process_xu_properties(struct cs35l56_private *cs35l56); #endif #endif /* ifndef CS35L56_H */ -- cgit v1.2.3