summaryrefslogtreecommitdiffstats
path: root/sound/soc/codecs/wcd-common.c
diff options
context:
space:
mode:
authorAli Ahmet Memis <ali@iusegentoo.com>2026-08-21 01:45:27 +0000
committerStafford Horne <shorne@gmail.com>2026-08-29 07:32:26 +0100
commit78004e9a87f240df03e2f73120d291763c32e0a7 (patch)
tree137c22dd795222931b9d2bf2501d37cd766741aa /sound/soc/codecs/wcd-common.c
downloadlinux-stable-78004e9a87f240df03e2f73120d291763c32e0a7.tar.gz
linux-stable-78004e9a87f240df03e2f73120d291763c32e0a7.zip
openrisc: fix arbitrary kernel memory access via or1k_atomic syscallgrafted
sys_or1k_atomic() (syscall 244 in the "or1k" ABI) takes two user pointers, v1 and v2, and swaps the words they point to in hand-written assembly. l.lwz r29,0(r4) l.lwz r27,0(r5) l.sw 0(r4),r27 l.sw 0(r5),r29 The pointers are not checked with access_ok(). The four memory accesses also have no exception table entries. A caller passes a kernel address as either pointer, and the syscall reads from and writes to it directly. This gives an unprivileged process a kernel read/write primitive. It overwrites kernel data such as the sys_call_table, gaining code execution in kernel context. Check both pointers before entering the critical section. Add fixups for the four memory accesses so faults on valid but unmapped user addresses return -EFAULT. [shorne@gmail.com: fix comment style] Fixes: 9d02a4283e9c ("OpenRISC: Boot code") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis <ali@iusegentoo.com> Signed-off-by: Stafford Horne <shorne@gmail.com>
Diffstat (limited to 'sound/soc/codecs/wcd-common.c')
-rw-r--r--sound/soc/codecs/wcd-common.c144
1 files changed, 144 insertions, 0 deletions
diff --git a/sound/soc/codecs/wcd-common.c b/sound/soc/codecs/wcd-common.c
new file mode 100644
index 000000000..9016e9745
--- /dev/null
+++ b/sound/soc/codecs/wcd-common.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2025, Qualcomm Technologies, Inc. and/or its subsidiaries.
+
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/printk.h>
+#include <linux/component.h>
+#include <linux/pm_runtime.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include <linux/regmap.h>
+
+#include "wcd-common.h"
+
+#define WCD_MIN_MICBIAS_MV 1000
+#define WCD_DEF_MICBIAS_MV 1800
+#define WCD_MAX_MICBIAS_MV 2850
+
+#define SWRS_SCP_HOST_CLK_DIV2_CTL_BANK(m) (0xE0 + 0x10 * (m))
+
+int wcd_get_micb_vout_ctl_val(struct device *dev, u32 micb_mv)
+{
+ /* min micbias voltage is 1V and maximum is 2.85V */
+ if (micb_mv < WCD_MIN_MICBIAS_MV || micb_mv > WCD_MAX_MICBIAS_MV) {
+ dev_err(dev, "Unsupported micbias voltage (%u mV)\n", micb_mv);
+ return -EINVAL;
+ }
+
+ return (micb_mv - WCD_MIN_MICBIAS_MV) / 50;
+}
+EXPORT_SYMBOL_GPL(wcd_get_micb_vout_ctl_val);
+
+static int wcd_get_micbias_val(struct device *dev, int micb_num, u32 *micb_mv)
+{
+ char micbias[64];
+ int mv;
+
+ sprintf(micbias, "qcom,micbias%d-microvolt", micb_num);
+
+ if (of_property_read_u32(dev->of_node, micbias, &mv)) {
+ dev_err(dev, "%s value not found, using default\n", micbias);
+ mv = WCD_DEF_MICBIAS_MV;
+ } else {
+ /* convert it to milli volts */
+ mv = mv/1000;
+ }
+ if (micb_mv)
+ *micb_mv = mv;
+
+ mv = wcd_get_micb_vout_ctl_val(dev, mv);
+ if (mv < 0) {
+ dev_err(dev, "Unsupported %s voltage (%d mV), falling back to default (%d mV)\n",
+ micbias, mv, WCD_DEF_MICBIAS_MV);
+ return wcd_get_micb_vout_ctl_val(dev, WCD_DEF_MICBIAS_MV);
+ }
+
+ return mv;
+}
+
+int wcd_dt_parse_micbias_info(struct wcd_common *common)
+{
+ int ret, i;
+
+ for (i = 0; i < common->max_bias; i++) {
+ ret = wcd_get_micbias_val(common->dev, i + 1, &common->micb_mv[i]);
+ if (ret < 0)
+ return ret;
+ common->micb_vout[i] = ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wcd_dt_parse_micbias_info);
+
+static int wcd_sdw_component_bind(struct device *dev, struct device *master, void *data)
+{
+ pm_runtime_set_autosuspend_delay(dev, 3000);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
+ return 0;
+}
+
+static void wcd_sdw_component_unbind(struct device *dev, struct device *master, void *data)
+{
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ pm_runtime_dont_use_autosuspend(dev);
+}
+
+const struct component_ops wcd_sdw_component_ops = {
+ .bind = wcd_sdw_component_bind,
+ .unbind = wcd_sdw_component_unbind,
+};
+EXPORT_SYMBOL_GPL(wcd_sdw_component_ops);
+
+int wcd_update_status(struct sdw_slave *slave, enum sdw_slave_status status)
+{
+ struct regmap *regmap = dev_get_regmap(&slave->dev, NULL);
+
+ if (regmap && status == SDW_SLAVE_ATTACHED) {
+ /* Write out any cached changes that happened between probe and attach */
+ regcache_cache_only(regmap, false);
+ return regcache_sync(regmap);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wcd_update_status);
+
+int wcd_bus_config(struct sdw_slave *slave, struct sdw_bus_params *params)
+{
+ sdw_write(slave, SWRS_SCP_HOST_CLK_DIV2_CTL_BANK(params->next_bank), 0x01);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wcd_bus_config);
+
+int wcd_interrupt_callback(struct sdw_slave *slave, struct irq_domain *slave_irq,
+ unsigned int wcd_intr_status0, unsigned int wcd_intr_status1,
+ unsigned int wcd_intr_status2)
+{
+ struct regmap *regmap = dev_get_regmap(&slave->dev, NULL);
+ u32 sts1, sts2, sts3;
+
+ do {
+ handle_nested_irq(irq_find_mapping(slave_irq, 0));
+ regmap_read(regmap, wcd_intr_status0, &sts1);
+ regmap_read(regmap, wcd_intr_status1, &sts2);
+ regmap_read(regmap, wcd_intr_status2, &sts3);
+
+ } while (sts1 || sts2 || sts3);
+
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(wcd_interrupt_callback);
+
+MODULE_DESCRIPTION("Common Qualcomm WCD Codec helpers driver");
+MODULE_LICENSE("GPL");