From 8423895d456672736a278d64f86d8b434eef2b54 Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Tue, 14 May 2024 15:08:58 +0200 Subject: [PATCH 001/573] video: Handle HAS_IOPORT dependencies In a future patch HAS_IOPORT=n will disable inb()/outb() and friends at compile time. We thus need to #ifdef functions and their callsites which unconditionally use these I/O accessors. In the include/video/vga.h these are conveniently all those functions with the vga_io_* prefix. Co-developed-by: Arnd Bergmann Signed-off-by: Arnd Bergmann Signed-off-by: Niklas Schnelle Signed-off-by: Helge Deller --- include/video/vga.h | 58 ++++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/include/video/vga.h b/include/video/vga.h index 947c0abd04ef..468764d6727a 100644 --- a/include/video/vga.h +++ b/include/video/vga.h @@ -197,9 +197,26 @@ struct vgastate { extern int save_vga(struct vgastate *state); extern int restore_vga(struct vgastate *state); +static inline unsigned char vga_mm_r (void __iomem *regbase, unsigned short port) +{ + return readb (regbase + port); +} + +static inline void vga_mm_w (void __iomem *regbase, unsigned short port, unsigned char val) +{ + writeb (val, regbase + port); +} + +static inline void vga_mm_w_fast (void __iomem *regbase, unsigned short port, + unsigned char reg, unsigned char val) +{ + writew (VGA_OUT16VAL (val, reg), regbase + port); +} + /* * generic VGA port read/write */ +#ifdef CONFIG_HAS_IOPORT static inline unsigned char vga_io_r (unsigned short port) { @@ -217,22 +234,6 @@ static inline void vga_io_w_fast (unsigned short port, unsigned char reg, outw(VGA_OUT16VAL (val, reg), port); } -static inline unsigned char vga_mm_r (void __iomem *regbase, unsigned short port) -{ - return readb (regbase + port); -} - -static inline void vga_mm_w (void __iomem *regbase, unsigned short port, unsigned char val) -{ - writeb (val, regbase + port); -} - -static inline void vga_mm_w_fast (void __iomem *regbase, unsigned short port, - unsigned char reg, unsigned char val) -{ - writew (VGA_OUT16VAL (val, reg), regbase + port); -} - static inline unsigned char vga_r (void __iomem *regbase, unsigned short port) { if (regbase) @@ -258,7 +259,24 @@ static inline void vga_w_fast (void __iomem *regbase, unsigned short port, else vga_io_w_fast (port, reg, val); } +#else /* CONFIG_HAS_IOPORT */ +static inline unsigned char vga_r (void __iomem *regbase, unsigned short port) +{ + return vga_mm_r (regbase, port); +} +static inline void vga_w (void __iomem *regbase, unsigned short port, unsigned char val) +{ + vga_mm_w (regbase, port, val); +} + + +static inline void vga_w_fast (void __iomem *regbase, unsigned short port, + unsigned char reg, unsigned char val) +{ + vga_mm_w_fast (regbase, port, reg, val); +} +#endif /* CONFIG_HAS_IOPORT */ /* * VGA CRTC register read/write @@ -280,6 +298,7 @@ static inline void vga_wcrt (void __iomem *regbase, unsigned char reg, unsigned #endif /* VGA_OUTW_WRITE */ } +#ifdef CONFIG_HAS_IOPORT static inline unsigned char vga_io_rcrt (unsigned char reg) { vga_io_w (VGA_CRT_IC, reg); @@ -295,6 +314,7 @@ static inline void vga_io_wcrt (unsigned char reg, unsigned char val) vga_io_w (VGA_CRT_DC, val); #endif /* VGA_OUTW_WRITE */ } +#endif /* CONFIG_HAS_IOPORT */ static inline unsigned char vga_mm_rcrt (void __iomem *regbase, unsigned char reg) { @@ -333,6 +353,7 @@ static inline void vga_wseq (void __iomem *regbase, unsigned char reg, unsigned #endif /* VGA_OUTW_WRITE */ } +#ifdef CONFIG_HAS_IOPORT static inline unsigned char vga_io_rseq (unsigned char reg) { vga_io_w (VGA_SEQ_I, reg); @@ -348,6 +369,7 @@ static inline void vga_io_wseq (unsigned char reg, unsigned char val) vga_io_w (VGA_SEQ_D, val); #endif /* VGA_OUTW_WRITE */ } +#endif /* CONFIG_HAS_IOPORT */ static inline unsigned char vga_mm_rseq (void __iomem *regbase, unsigned char reg) { @@ -385,6 +407,7 @@ static inline void vga_wgfx (void __iomem *regbase, unsigned char reg, unsigned #endif /* VGA_OUTW_WRITE */ } +#ifdef CONFIG_HAS_IOPORT static inline unsigned char vga_io_rgfx (unsigned char reg) { vga_io_w (VGA_GFX_I, reg); @@ -400,6 +423,7 @@ static inline void vga_io_wgfx (unsigned char reg, unsigned char val) vga_io_w (VGA_GFX_D, val); #endif /* VGA_OUTW_WRITE */ } +#endif /* CONFIG_HAS_IOPORT */ static inline unsigned char vga_mm_rgfx (void __iomem *regbase, unsigned char reg) { @@ -434,6 +458,7 @@ static inline void vga_wattr (void __iomem *regbase, unsigned char reg, unsigned vga_w (regbase, VGA_ATT_W, val); } +#ifdef CONFIG_HAS_IOPORT static inline unsigned char vga_io_rattr (unsigned char reg) { vga_io_w (VGA_ATT_IW, reg); @@ -445,6 +470,7 @@ static inline void vga_io_wattr (unsigned char reg, unsigned char val) vga_io_w (VGA_ATT_IW, reg); vga_io_w (VGA_ATT_W, val); } +#endif /* CONFIG_HAS_IOPORT */ static inline unsigned char vga_mm_rattr (void __iomem *regbase, unsigned char reg) { From c206d6be8605e9b564ef99a7fd7dcc406e3bda63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 15 Jul 2024 21:43:40 +0200 Subject: [PATCH 002/573] gpio: Drop explicit initialization of struct i2c_device_id::driver_data to 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These drivers don't use the driver_data member of struct i2c_device_id, so don't explicitly initialize this member. This prepares putting driver_data in an anonymous union which requires either no initialization or named designators. But it's also a nice cleanup on its own. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20240715194341.1755599-2-u.kleine-koenig@baylibre.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-fxl6408.c | 2 +- drivers/gpio/gpio-max7300.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-fxl6408.c b/drivers/gpio/gpio-fxl6408.c index 991549888904..86ebc66b1104 100644 --- a/drivers/gpio/gpio-fxl6408.c +++ b/drivers/gpio/gpio-fxl6408.c @@ -138,7 +138,7 @@ static const __maybe_unused struct of_device_id fxl6408_dt_ids[] = { MODULE_DEVICE_TABLE(of, fxl6408_dt_ids); static const struct i2c_device_id fxl6408_id[] = { - { "fxl6408", 0 }, + { "fxl6408" }, { } }; MODULE_DEVICE_TABLE(i2c, fxl6408_id); diff --git a/drivers/gpio/gpio-max7300.c b/drivers/gpio/gpio-max7300.c index 31c2b95321cc..621d609ece90 100644 --- a/drivers/gpio/gpio-max7300.c +++ b/drivers/gpio/gpio-max7300.c @@ -53,7 +53,7 @@ static void max7300_remove(struct i2c_client *client) } static const struct i2c_device_id max7300_id[] = { - { "max7300", 0 }, + { "max7300" }, { } }; MODULE_DEVICE_TABLE(i2c, max7300_id); From d92792a4b26e50b96ab734cbe203d8a4c932a7a9 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 15 Jul 2024 19:07:00 +0300 Subject: [PATCH 003/573] perf/x86/intel/pt: Fix sampling synchronization pt_event_snapshot_aux() uses pt->handle_nmi to determine if tracing needs to be stopped, however tracing can still be going because pt->handle_nmi is set to zero before tracing is stopped in pt_event_stop, whereas pt_event_snapshot_aux() requires that tracing must be stopped in order to copy a sample of trace from the buffer. Instead call pt_config_stop() always, which anyway checks config for RTIT_CTL_TRACEEN and does nothing if it is already clear. Note pt_event_snapshot_aux() can continue to use pt->handle_nmi to determine if the trace needs to be restarted afterwards. Fixes: 25e8920b301c ("perf/x86/intel/pt: Add sampling support") Signed-off-by: Adrian Hunter Signed-off-by: Peter Zijlstra (Intel) Cc: stable@vger.kernel.org Link: https://lkml.kernel.org/r/20240715160712.127117-2-adrian.hunter@intel.com --- arch/x86/events/intel/pt.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c index b4aa8daa4773..2959970dd10e 100644 --- a/arch/x86/events/intel/pt.c +++ b/arch/x86/events/intel/pt.c @@ -1606,6 +1606,7 @@ static void pt_event_stop(struct perf_event *event, int mode) * see comment in intel_pt_interrupt(). */ WRITE_ONCE(pt->handle_nmi, 0); + barrier(); pt_config_stop(event); @@ -1657,11 +1658,10 @@ static long pt_event_snapshot_aux(struct perf_event *event, return 0; /* - * Here, handle_nmi tells us if the tracing is on + * There is no PT interrupt in this mode, so stop the trace and it will + * remain stopped while the buffer is copied. */ - if (READ_ONCE(pt->handle_nmi)) - pt_config_stop(event); - + pt_config_stop(event); pt_read_offset(buf); pt_update_head(pt); @@ -1673,11 +1673,10 @@ static long pt_event_snapshot_aux(struct perf_event *event, ret = perf_output_copy_aux(&pt->handle, handle, from, to); /* - * If the tracing was on when we turned up, restart it. - * Compiler barrier not needed as we couldn't have been - * preempted by anything that touches pt->handle_nmi. + * Here, handle_nmi tells us if the tracing was on. + * If the tracing was on, restart it. */ - if (pt->handle_nmi) + if (READ_ONCE(pt->handle_nmi)) pt_config_start(event); return ret; From 52c3fb1a0f822fd64529ca64f3792095524de450 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Thu, 18 Jul 2024 10:21:49 +0200 Subject: [PATCH 004/573] perf/x86: Add hw_perf_event::aux_config Start a new section for AUX PMUs in hw_perf_event. Signed-off-by: Peter Zijlstra (Intel) --- arch/x86/events/intel/pt.c | 14 +++++++------- include/linux/perf_event.h | 3 +++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c index 2959970dd10e..fd4670a6694e 100644 --- a/arch/x86/events/intel/pt.c +++ b/arch/x86/events/intel/pt.c @@ -416,7 +416,7 @@ static bool pt_event_valid(struct perf_event *event) static void pt_config_start(struct perf_event *event) { struct pt *pt = this_cpu_ptr(&pt_ctx); - u64 ctl = event->hw.config; + u64 ctl = event->hw.aux_config; ctl |= RTIT_CTL_TRACEEN; if (READ_ONCE(pt->vmx_on)) @@ -424,7 +424,7 @@ static void pt_config_start(struct perf_event *event) else wrmsrl(MSR_IA32_RTIT_CTL, ctl); - WRITE_ONCE(event->hw.config, ctl); + WRITE_ONCE(event->hw.aux_config, ctl); } /* Address ranges and their corresponding msr configuration registers */ @@ -503,7 +503,7 @@ static void pt_config(struct perf_event *event) u64 reg; /* First round: clear STATUS, in particular the PSB byte counter. */ - if (!event->hw.config) { + if (!event->hw.aux_config) { perf_event_itrace_started(event); wrmsrl(MSR_IA32_RTIT_STATUS, 0); } @@ -533,14 +533,14 @@ static void pt_config(struct perf_event *event) reg |= (event->attr.config & PT_CONFIG_MASK); - event->hw.config = reg; + event->hw.aux_config = reg; pt_config_start(event); } static void pt_config_stop(struct perf_event *event) { struct pt *pt = this_cpu_ptr(&pt_ctx); - u64 ctl = READ_ONCE(event->hw.config); + u64 ctl = READ_ONCE(event->hw.aux_config); /* may be already stopped by a PMI */ if (!(ctl & RTIT_CTL_TRACEEN)) @@ -550,7 +550,7 @@ static void pt_config_stop(struct perf_event *event) if (!READ_ONCE(pt->vmx_on)) wrmsrl(MSR_IA32_RTIT_CTL, ctl); - WRITE_ONCE(event->hw.config, ctl); + WRITE_ONCE(event->hw.aux_config, ctl); /* * A wrmsr that disables trace generation serializes other PT @@ -1557,7 +1557,7 @@ void intel_pt_handle_vmx(int on) /* Turn PTs back on */ if (!on && event) - wrmsrl(MSR_IA32_RTIT_CTL, event->hw.config); + wrmsrl(MSR_IA32_RTIT_CTL, event->hw.aux_config); local_irq_restore(flags); } diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 1a8942277dda..6bb0c21d6335 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -168,6 +168,9 @@ struct hw_perf_event { struct hw_perf_event_extra extra_reg; struct hw_perf_event_extra branch_reg; }; + struct { /* aux / Intel-PT */ + u64 aux_config; + }; struct { /* software */ struct hrtimer hrtimer; }; From f7176724e7c972201ee38ba531d6c8b68cab180b Mon Sep 17 00:00:00 2001 From: Animesh Agarwal Date: Wed, 31 Jul 2024 11:14:30 +0530 Subject: [PATCH 005/573] dt-bindings: gpio: nxp,lpc3220-gpio: Convert to dtschema Convert the NXP LPC3220 SoC GPIO controller bindings to DT schema format. Signed-off-by: Animesh Agarwal Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240731054442.109732-1-animeshagarwal28@gmail.com Signed-off-by: Bartosz Golaszewski --- .../devicetree/bindings/gpio/gpio_lpc32xx.txt | 43 ---------------- .../bindings/gpio/nxp,lpc3220-gpio.yaml | 50 +++++++++++++++++++ 2 files changed, 50 insertions(+), 43 deletions(-) delete mode 100644 Documentation/devicetree/bindings/gpio/gpio_lpc32xx.txt create mode 100644 Documentation/devicetree/bindings/gpio/nxp,lpc3220-gpio.yaml diff --git a/Documentation/devicetree/bindings/gpio/gpio_lpc32xx.txt b/Documentation/devicetree/bindings/gpio/gpio_lpc32xx.txt deleted file mode 100644 index 49819367a011..000000000000 --- a/Documentation/devicetree/bindings/gpio/gpio_lpc32xx.txt +++ /dev/null @@ -1,43 +0,0 @@ -NXP LPC32xx SoC GPIO controller - -Required properties: -- compatible: must be "nxp,lpc3220-gpio" -- reg: Physical base address and length of the controller's registers. -- gpio-controller: Marks the device node as a GPIO controller. -- #gpio-cells: Should be 3: - 1) bank: - 0: GPIO P0 - 1: GPIO P1 - 2: GPIO P2 - 3: GPIO P3 - 4: GPI P3 - 5: GPO P3 - 2) pin number - 3) optional parameters: - - bit 0 specifies polarity (0 for normal, 1 for inverted) -- reg: Index of the GPIO group - -Example: - - gpio: gpio@40028000 { - compatible = "nxp,lpc3220-gpio"; - reg = <0x40028000 0x1000>; - gpio-controller; - #gpio-cells = <3>; /* bank, pin, flags */ - }; - - leds { - compatible = "gpio-leds"; - - led0 { - gpios = <&gpio 5 1 1>; /* GPO_P3 1, active low */ - linux,default-trigger = "heartbeat"; - default-state = "off"; - }; - - led1 { - gpios = <&gpio 5 14 1>; /* GPO_P3 14, active low */ - linux,default-trigger = "timer"; - default-state = "off"; - }; - }; diff --git a/Documentation/devicetree/bindings/gpio/nxp,lpc3220-gpio.yaml b/Documentation/devicetree/bindings/gpio/nxp,lpc3220-gpio.yaml new file mode 100644 index 000000000000..25b5494393cc --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/nxp,lpc3220-gpio.yaml @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/nxp,lpc3220-gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP LPC3220 SoC GPIO controller + +maintainers: + - Animesh Agarwal + +properties: + compatible: + const: nxp,lpc3220-gpio + + reg: + maxItems: 1 + + gpio-controller: true + + '#gpio-cells': + const: 3 + description: | + 1) bank: + 0: GPIO P0 + 1: GPIO P1 + 2: GPIO P2 + 3: GPIO P3 + 4: GPI P3 + 5: GPO P3 + 2) pin number + 3) flags: + - bit 0 specifies polarity (0 for normal, 1 for inverted) + +required: + - compatible + - reg + - gpio-controller + - '#gpio-cells' + +additionalProperties: false + +examples: + - | + gpio@40028000 { + compatible = "nxp,lpc3220-gpio"; + reg = <0x40028000 0x1000>; + gpio-controller; + #gpio-cells = <3>; /* bank, pin, flags */ + }; From ac93ca125b5409df56c5139648bbe10fd1ea989b Mon Sep 17 00:00:00 2001 From: Zhu Jun Date: Tue, 23 Jul 2024 19:46:36 -0700 Subject: [PATCH 006/573] tools: gpio: Fix the wrong format specifier The unsigned int should use "%u" instead of "%d". Signed-off-by: Zhu Jun Link: https://lore.kernel.org/r/20240724024636.3634-1-zhujun2@cmss.chinamobile.com Signed-off-by: Bartosz Golaszewski --- tools/gpio/gpio-hammer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/gpio/gpio-hammer.c b/tools/gpio/gpio-hammer.c index 54fdf59dd320..ba0866eb3581 100644 --- a/tools/gpio/gpio-hammer.c +++ b/tools/gpio/gpio-hammer.c @@ -54,7 +54,7 @@ int hammer_device(const char *device_name, unsigned int *lines, int num_lines, fprintf(stdout, "Hammer lines ["); for (i = 0; i < num_lines; i++) { - fprintf(stdout, "%d", lines[i]); + fprintf(stdout, "%u", lines[i]); if (i != (num_lines - 1)) fprintf(stdout, ", "); } @@ -89,7 +89,7 @@ int hammer_device(const char *device_name, unsigned int *lines, int num_lines, fprintf(stdout, "["); for (i = 0; i < num_lines; i++) { - fprintf(stdout, "%d: %d", lines[i], + fprintf(stdout, "%u: %d", lines[i], gpiotools_test_bit(values.bits, i)); if (i != (num_lines - 1)) fprintf(stdout, ", "); From 381cfd63050ba601326f89519dff3114f894f590 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 9 Jul 2024 07:16:43 -0700 Subject: [PATCH 007/573] hwmon: (adt7470) Use multi-byte regmap operations Use multi-byte regmap operations where possible to reduce code size and the need for mutex protection. No functional change. Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 517248d2994e..dbee6926fa05 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -728,30 +728,22 @@ static const int adt7470_freq_map[] = { static int pwm1_freq_get(struct device *dev) { struct adt7470_data *data = dev_get_drvdata(dev); - unsigned int cfg_reg_1, cfg_reg_2; + unsigned int regs[2] = {ADT7470_REG_CFG, ADT7470_REG_CFG_2}; + u8 cfg_reg[2]; int index; int err; - mutex_lock(&data->lock); - err = regmap_read(data->regmap, ADT7470_REG_CFG, &cfg_reg_1); - if (err < 0) - goto out; - err = regmap_read(data->regmap, ADT7470_REG_CFG_2, &cfg_reg_2); - if (err < 0) - goto out; - mutex_unlock(&data->lock); + err = regmap_multi_reg_read(data->regmap, regs, cfg_reg, 2); + if (err) + return err; - index = (cfg_reg_2 & ADT7470_FREQ_MASK) >> ADT7470_FREQ_SHIFT; - if (!(cfg_reg_1 & ADT7470_CFG_LF)) + index = (cfg_reg[1] & ADT7470_FREQ_MASK) >> ADT7470_FREQ_SHIFT; + if (!(cfg_reg[0] & ADT7470_CFG_LF)) index += 8; if (index >= ARRAY_SIZE(adt7470_freq_map)) index = ARRAY_SIZE(adt7470_freq_map) - 1; return adt7470_freq_map[index]; - -out: - mutex_unlock(&data->lock); - return err; } static int adt7470_pwm_read(struct device *dev, u32 attr, int channel, long *val) From a0a16d960f64f88d72f5b7a1155e9828d498b64f Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 9 Jul 2024 13:53:37 -0700 Subject: [PATCH 008/573] hwmon: (tmp401) Use multi-byte regmap operations Use multi-byte regmap operations where possible to reduce code size and the need for mutex protection. No functional change. Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp401.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index 853dbe708ff5..02c5a3bb1071 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -308,7 +308,9 @@ static int tmp401_temp_read(struct device *dev, u32 attr, int channel, long *val { struct tmp401_data *data = dev_get_drvdata(dev); struct regmap *regmap = data->regmap; + unsigned int regs[2] = { TMP401_TEMP_MSB[3][channel], TMP401_TEMP_CRIT_HYST }; unsigned int regval; + u16 regvals[2]; int reg, ret; switch (attr) { @@ -325,20 +327,11 @@ static int tmp401_temp_read(struct device *dev, u32 attr, int channel, long *val *val = tmp401_register_to_temp(regval, data->extended_range); break; case hwmon_temp_crit_hyst: - mutex_lock(&data->update_lock); - reg = TMP401_TEMP_MSB[3][channel]; - ret = regmap_read(regmap, reg, ®val); - if (ret < 0) - goto unlock; - *val = tmp401_register_to_temp(regval, data->extended_range); - ret = regmap_read(regmap, TMP401_TEMP_CRIT_HYST, ®val); - if (ret < 0) - goto unlock; - *val -= regval * 1000; -unlock: - mutex_unlock(&data->update_lock); + ret = regmap_multi_reg_read(regmap, regs, regvals, 2); if (ret < 0) return ret; + *val = tmp401_register_to_temp(regvals[0], data->extended_range) - + (regvals[1] * 1000); break; case hwmon_temp_fault: case hwmon_temp_min_alarm: From 717c04cf43d2d005db098bb794bf3d558cad98cf Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 9 Jul 2024 07:21:19 -0700 Subject: [PATCH 009/573] hwmon: (lm95245) Use multi-byte regmap operations Use multi-byte regmap operations where possible to reduce code size and the need for mutex protection. No functional change. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/lm95245.c | 110 ++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 65 deletions(-) diff --git a/drivers/hwmon/lm95245.c b/drivers/hwmon/lm95245.c index d293b4f15dc1..3bdc30530847 100644 --- a/drivers/hwmon/lm95245.c +++ b/drivers/hwmon/lm95245.c @@ -161,18 +161,18 @@ static int lm95245_read_temp(struct device *dev, u32 attr, int channel, { struct lm95245_data *data = dev_get_drvdata(dev); struct regmap *regmap = data->regmap; - int ret, regl, regh, regvall, regvalh; + unsigned int regs[2]; + unsigned int regval; + u8 regvals[2]; + int ret; switch (attr) { case hwmon_temp_input: - regl = channel ? LM95245_REG_R_REMOTE_TEMPL_S : - LM95245_REG_R_LOCAL_TEMPL_S; - regh = channel ? LM95245_REG_R_REMOTE_TEMPH_S : - LM95245_REG_R_LOCAL_TEMPH_S; - ret = regmap_read(regmap, regl, ®vall); - if (ret < 0) - return ret; - ret = regmap_read(regmap, regh, ®valh); + regs[0] = channel ? LM95245_REG_R_REMOTE_TEMPL_S : + LM95245_REG_R_LOCAL_TEMPL_S; + regs[1] = channel ? LM95245_REG_R_REMOTE_TEMPH_S : + LM95245_REG_R_LOCAL_TEMPH_S; + ret = regmap_multi_reg_read(regmap, regs, regvals, 2); if (ret < 0) return ret; /* @@ -181,92 +181,77 @@ static int lm95245_read_temp(struct device *dev, u32 attr, int channel, * Use signed calculation for remote if signed bit is set * or if reported temperature is below signed limit. */ - if (!channel || (regvalh & 0x80) || regvalh < 0x7f) { - *val = temp_from_reg_signed(regvalh, regvall); + if (!channel || (regvals[1] & 0x80) || regvals[1] < 0x7f) { + *val = temp_from_reg_signed(regvals[1], regvals[0]); return 0; } - ret = regmap_read(regmap, LM95245_REG_R_REMOTE_TEMPL_U, - ®vall); - if (ret < 0) + ret = regmap_bulk_read(regmap, LM95245_REG_R_REMOTE_TEMPH_U, regvals, 2); + if (ret) return ret; - ret = regmap_read(regmap, LM95245_REG_R_REMOTE_TEMPH_U, - ®valh); - if (ret < 0) - return ret; - *val = temp_from_reg_unsigned(regvalh, regvall); + *val = temp_from_reg_unsigned(regvals[0], regvals[1]); return 0; case hwmon_temp_max: ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OS_LIMIT, - ®valh); + ®val); if (ret < 0) return ret; - *val = regvalh * 1000; + *val = regval * 1000; return 0; case hwmon_temp_crit: - regh = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT : - LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT; - ret = regmap_read(regmap, regh, ®valh); + regs[0] = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT : + LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT; + ret = regmap_read(regmap, regs[0], ®val); if (ret < 0) return ret; - *val = regvalh * 1000; + *val = regval * 1000; return 0; case hwmon_temp_max_hyst: - ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OS_LIMIT, - ®valh); + regs[0] = LM95245_REG_RW_REMOTE_OS_LIMIT; + regs[1] = LM95245_REG_RW_COMMON_HYSTERESIS; + ret = regmap_multi_reg_read(regmap, regs, regvals, 2); if (ret < 0) return ret; - ret = regmap_read(regmap, LM95245_REG_RW_COMMON_HYSTERESIS, - ®vall); - if (ret < 0) - return ret; - *val = (regvalh - regvall) * 1000; + *val = (regvals[0] - regvals[1]) * 1000; return 0; case hwmon_temp_crit_hyst: - regh = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT : - LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT; - ret = regmap_read(regmap, regh, ®valh); + regs[0] = channel ? LM95245_REG_RW_REMOTE_TCRIT_LIMIT : + LM95245_REG_RW_LOCAL_OS_TCRIT_LIMIT; + regs[1] = LM95245_REG_RW_COMMON_HYSTERESIS; + + ret = regmap_multi_reg_read(regmap, regs, regvals, 2); if (ret < 0) return ret; - ret = regmap_read(regmap, LM95245_REG_RW_COMMON_HYSTERESIS, - ®vall); - if (ret < 0) - return ret; - *val = (regvalh - regvall) * 1000; + *val = (regvals[0] - regvals[1]) * 1000; return 0; case hwmon_temp_type: - ret = regmap_read(regmap, LM95245_REG_RW_CONFIG2, ®valh); + ret = regmap_read(regmap, LM95245_REG_RW_CONFIG2, ®val); if (ret < 0) return ret; - *val = (regvalh & CFG2_REMOTE_TT) ? 1 : 2; + *val = (regval & CFG2_REMOTE_TT) ? 1 : 2; return 0; case hwmon_temp_offset: - ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OFFL, - ®vall); + ret = regmap_bulk_read(regmap, LM95245_REG_RW_REMOTE_OFFH, regvals, 2); if (ret < 0) return ret; - ret = regmap_read(regmap, LM95245_REG_RW_REMOTE_OFFH, - ®valh); - if (ret < 0) - return ret; - *val = temp_from_reg_signed(regvalh, regvall); + *val = temp_from_reg_signed(regvals[0], regvals[1]); return 0; case hwmon_temp_max_alarm: - ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®valh); + ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®val); if (ret < 0) return ret; - *val = !!(regvalh & STATUS1_ROS); + *val = !!(regval & STATUS1_ROS); return 0; case hwmon_temp_crit_alarm: - ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®valh); + ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®val); if (ret < 0) return ret; - *val = !!(regvalh & (channel ? STATUS1_RTCRIT : STATUS1_LOC)); + *val = !!(regval & (channel ? STATUS1_RTCRIT : STATUS1_LOC)); return 0; case hwmon_temp_fault: - ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®valh); + ret = regmap_read(regmap, LM95245_REG_R_STATUS1, ®val); if (ret < 0) return ret; - *val = !!(regvalh & STATUS1_DIODE_FAULT); + *val = !!(regval & STATUS1_DIODE_FAULT); return 0; default: return -EOPNOTSUPP; @@ -279,6 +264,7 @@ static int lm95245_write_temp(struct device *dev, u32 attr, int channel, struct lm95245_data *data = dev_get_drvdata(dev); struct regmap *regmap = data->regmap; unsigned int regval; + u8 regvals[2]; int ret, reg; switch (attr) { @@ -311,16 +297,10 @@ static int lm95245_write_temp(struct device *dev, u32 attr, int channel, case hwmon_temp_offset: val = clamp_val(val, -128000, 127875); val = val * 256 / 1000; - mutex_lock(&data->update_lock); - ret = regmap_write(regmap, LM95245_REG_RW_REMOTE_OFFL, - val & 0xe0); - if (ret < 0) { - mutex_unlock(&data->update_lock); - return ret; - } - ret = regmap_write(regmap, LM95245_REG_RW_REMOTE_OFFH, - (val >> 8) & 0xff); - mutex_unlock(&data->update_lock); + regvals[0] = val >> 8; + regvals[1] = val & 0xe0; + + ret = regmap_bulk_write(regmap, LM95245_REG_RW_REMOTE_OFFH, regvals, 2); return ret; case hwmon_temp_type: if (val != 1 && val != 2) From 7b4882d9b80cc9723ab097510732a7cc28f56c69 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 9 Jul 2024 14:03:06 -0700 Subject: [PATCH 010/573] hwmon: (nct7802) Use multi-byte regmap operations Use multi-byte regmap operations where possible to reduce code size and the need for mutex protection. No functional changes. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/nct7802.c | 62 +++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 39 deletions(-) diff --git a/drivers/hwmon/nct7802.c b/drivers/hwmon/nct7802.c index 97e8c6424403..5e83504800a3 100644 --- a/drivers/hwmon/nct7802.c +++ b/drivers/hwmon/nct7802.c @@ -229,41 +229,34 @@ abort: static int nct7802_read_fan(struct nct7802_data *data, u8 reg_fan) { - unsigned int f1, f2; + unsigned int regs[2] = {reg_fan, REG_FANCOUNT_LOW}; + u8 f[2]; int ret; - mutex_lock(&data->access_lock); - ret = regmap_read(data->regmap, reg_fan, &f1); - if (ret < 0) - goto abort; - ret = regmap_read(data->regmap, REG_FANCOUNT_LOW, &f2); - if (ret < 0) - goto abort; - ret = (f1 << 5) | (f2 >> 3); + ret = regmap_multi_reg_read(data->regmap, regs, f, 2); + if (ret) + return ret; + ret = (f[0] << 5) | (f[1] >> 3); /* convert fan count to rpm */ if (ret == 0x1fff) /* maximum value, assume fan is stopped */ ret = 0; else if (ret) ret = DIV_ROUND_CLOSEST(1350000U, ret); -abort: - mutex_unlock(&data->access_lock); return ret; } static int nct7802_read_fan_min(struct nct7802_data *data, u8 reg_fan_low, u8 reg_fan_high) { - unsigned int f1, f2; + unsigned int regs[2] = {reg_fan_low, reg_fan_high}; + u8 f[2]; int ret; - mutex_lock(&data->access_lock); - ret = regmap_read(data->regmap, reg_fan_low, &f1); + ret = regmap_multi_reg_read(data->regmap, regs, f, 2); if (ret < 0) - goto abort; - ret = regmap_read(data->regmap, reg_fan_high, &f2); - if (ret < 0) - goto abort; - ret = f1 | ((f2 & 0xf8) << 5); + return ret; + + ret = f[0] | ((f[1] & 0xf8) << 5); /* convert fan count to rpm */ if (ret == 0x1fff) /* maximum value, assume no limit */ ret = 0; @@ -271,8 +264,6 @@ static int nct7802_read_fan_min(struct nct7802_data *data, u8 reg_fan_low, ret = DIV_ROUND_CLOSEST(1350000U, ret); else ret = 1350000U; -abort: - mutex_unlock(&data->access_lock); return ret; } @@ -302,33 +293,26 @@ static u8 nct7802_vmul[] = { 4, 2, 2, 2, 2 }; static int nct7802_read_voltage(struct nct7802_data *data, int nr, int index) { - unsigned int v1, v2; + u8 v[2]; int ret; - mutex_lock(&data->access_lock); if (index == 0) { /* voltage */ - ret = regmap_read(data->regmap, REG_VOLTAGE[nr], &v1); + unsigned int regs[2] = {REG_VOLTAGE[nr], REG_VOLTAGE_LOW}; + + ret = regmap_multi_reg_read(data->regmap, regs, v, 2); if (ret < 0) - goto abort; - ret = regmap_read(data->regmap, REG_VOLTAGE_LOW, &v2); - if (ret < 0) - goto abort; - ret = ((v1 << 2) | (v2 >> 6)) * nct7802_vmul[nr]; + return ret; + ret = ((v[0] << 2) | (v[1] >> 6)) * nct7802_vmul[nr]; } else { /* limit */ int shift = 8 - REG_VOLTAGE_LIMIT_MSB_SHIFT[index - 1][nr]; + unsigned int regs[2] = {REG_VOLTAGE_LIMIT_LSB[index - 1][nr], + REG_VOLTAGE_LIMIT_MSB[nr]}; - ret = regmap_read(data->regmap, - REG_VOLTAGE_LIMIT_LSB[index - 1][nr], &v1); + ret = regmap_multi_reg_read(data->regmap, regs, v, 2); if (ret < 0) - goto abort; - ret = regmap_read(data->regmap, REG_VOLTAGE_LIMIT_MSB[nr], - &v2); - if (ret < 0) - goto abort; - ret = (v1 | ((v2 << shift) & 0x300)) * nct7802_vmul[nr]; + return ret; + ret = (v[0] | ((v[1] << shift) & 0x300)) * nct7802_vmul[nr]; } -abort: - mutex_unlock(&data->access_lock); return ret; } From 4ce612b16e9e1d93cabde798750c0fcfd2477586 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 9 Jul 2024 14:06:17 -0700 Subject: [PATCH 011/573] hwmon: (adt7x10) Use multi-byte regmap operations Use multi-byte regmap operations where possible to reduce code size and the need for mutex protection. No functional changes. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7x10.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/drivers/hwmon/adt7x10.c b/drivers/hwmon/adt7x10.c index 6701920de17f..2d329391ed3f 100644 --- a/drivers/hwmon/adt7x10.c +++ b/drivers/hwmon/adt7x10.c @@ -170,21 +170,15 @@ static int adt7x10_temp_write(struct adt7x10_data *data, int index, long temp) static int adt7x10_hyst_read(struct adt7x10_data *data, int index, long *val) { - int hyst, temp, ret; + unsigned int regs[2] = {ADT7X10_T_HYST, ADT7X10_REG_TEMP[index]}; + int hyst, ret; + u16 regdata[2]; - mutex_lock(&data->update_lock); - ret = regmap_read(data->regmap, ADT7X10_T_HYST, &hyst); - if (ret) { - mutex_unlock(&data->update_lock); - return ret; - } - - ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[index], &temp); - mutex_unlock(&data->update_lock); + ret = regmap_multi_reg_read(data->regmap, regs, regdata, 2); if (ret) return ret; - hyst = (hyst & ADT7X10_T_HYST_MASK) * 1000; + hyst = (regdata[0] & ADT7X10_T_HYST_MASK) * 1000; /* * hysteresis is stored as a 4 bit offset in the device, convert it @@ -194,7 +188,7 @@ static int adt7x10_hyst_read(struct adt7x10_data *data, int index, long *val) if (index == adt7x10_t_alarm_low) hyst = -hyst; - *val = ADT7X10_REG_TO_TEMP(data, temp) - hyst; + *val = ADT7X10_REG_TO_TEMP(data, regdata[1]) - hyst; return 0; } From ad231314b6f8e83799b64dd59e6fea98e3b8d36f Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 9 Jul 2024 14:07:19 -0700 Subject: [PATCH 012/573] hwmon: (tmp464) Use multi-byte regmap operations Use multi-byte regmap operations where possible to reduce code size and the need for mutex protection. No functional changes. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp464.c | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/drivers/hwmon/tmp464.c b/drivers/hwmon/tmp464.c index 3ee1137533d6..0a7c0448835b 100644 --- a/drivers/hwmon/tmp464.c +++ b/drivers/hwmon/tmp464.c @@ -147,11 +147,11 @@ static int tmp464_temp_read(struct device *dev, u32 attr, int channel, long *val { struct tmp464_data *data = dev_get_drvdata(dev); struct regmap *regmap = data->regmap; - unsigned int regval, regval2; + unsigned int regs[2]; + unsigned int regval; + u16 regvals[2]; int err = 0; - mutex_lock(&data->update_lock); - switch (attr) { case hwmon_temp_max_alarm: err = regmap_read(regmap, TMP464_THERM_STATUS_REG, ®val); @@ -172,26 +172,27 @@ static int tmp464_temp_read(struct device *dev, u32 attr, int channel, long *val * complete. That means we have to cache the value internally * for one measurement cycle and report the cached value. */ + mutex_lock(&data->update_lock); if (!data->valid || time_after(jiffies, data->last_updated + msecs_to_jiffies(data->update_interval))) { err = regmap_read(regmap, TMP464_REMOTE_OPEN_REG, ®val); if (err < 0) - break; + goto unlock; data->open_reg = regval; data->last_updated = jiffies; data->valid = true; } *val = !!(data->open_reg & BIT(channel + 7)); +unlock: + mutex_unlock(&data->update_lock); break; case hwmon_temp_max_hyst: - err = regmap_read(regmap, TMP464_THERM_LIMIT[channel], ®val); + regs[0] = TMP464_THERM_LIMIT[channel]; + regs[1] = TMP464_TEMP_HYST_REG; + err = regmap_multi_reg_read(regmap, regs, regvals, 2); if (err < 0) break; - err = regmap_read(regmap, TMP464_TEMP_HYST_REG, ®val2); - if (err < 0) - break; - regval -= regval2; - *val = temp_from_reg(regval); + *val = temp_from_reg(regvals[0] - regvals[1]); break; case hwmon_temp_max: err = regmap_read(regmap, TMP464_THERM_LIMIT[channel], ®val); @@ -200,14 +201,12 @@ static int tmp464_temp_read(struct device *dev, u32 attr, int channel, long *val *val = temp_from_reg(regval); break; case hwmon_temp_crit_hyst: - err = regmap_read(regmap, TMP464_THERM2_LIMIT[channel], ®val); + regs[0] = TMP464_THERM2_LIMIT[channel]; + regs[1] = TMP464_TEMP_HYST_REG; + err = regmap_multi_reg_read(regmap, regs, regvals, 2); if (err < 0) break; - err = regmap_read(regmap, TMP464_TEMP_HYST_REG, ®val2); - if (err < 0) - break; - regval -= regval2; - *val = temp_from_reg(regval); + *val = temp_from_reg(regvals[0] - regvals[1]); break; case hwmon_temp_crit: err = regmap_read(regmap, TMP464_THERM2_LIMIT[channel], ®val); @@ -239,8 +238,6 @@ static int tmp464_temp_read(struct device *dev, u32 attr, int channel, long *val break; } - mutex_unlock(&data->update_lock); - return err; } From 21a93a9e314f911d5022e988ab0b82a2f65a3507 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 16 Jul 2024 12:14:12 -0700 Subject: [PATCH 013/573] hwmon: (max6639) Use multi-byte regmap operations Use multi-byte regmap operations where possible to reduce code size and the need for mutex protection. No functional changes. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max6639.c | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/drivers/hwmon/max6639.c b/drivers/hwmon/max6639.c index f54720d3d2ce..c955b0f3a8d3 100644 --- a/drivers/hwmon/max6639.c +++ b/drivers/hwmon/max6639.c @@ -88,25 +88,16 @@ struct max6639_data { static int max6639_temp_read_input(struct device *dev, int channel, long *temp) { + u32 regs[2] = { MAX6639_REG_TEMP_EXT(channel), MAX6639_REG_TEMP(channel) }; struct max6639_data *data = dev_get_drvdata(dev); - unsigned int val; + u8 regvals[2]; int res; - /* - * Lock isn't needed as MAX6639_REG_TEMP wpnt change for at least 250ms after reading - * MAX6639_REG_TEMP_EXT - */ - res = regmap_read(data->regmap, MAX6639_REG_TEMP_EXT(channel), &val); + res = regmap_multi_reg_read(data->regmap, regs, regvals, 2); if (res < 0) return res; - *temp = val >> 5; - res = regmap_read(data->regmap, MAX6639_REG_TEMP(channel), &val); - if (res < 0) - return res; - - *temp |= val << 3; - *temp *= 125; + *temp = ((regvals[0] >> 5) | (regvals[1] << 3)) * 125; return 0; } @@ -290,8 +281,10 @@ static umode_t max6639_fan_is_visible(const void *_data, u32 attr, int channel) static int max6639_read_pwm(struct device *dev, u32 attr, int channel, long *pwm_val) { + u32 regs[2] = { MAX6639_REG_FAN_CONFIG3(channel), MAX6639_REG_GCONFIG }; struct max6639_data *data = dev_get_drvdata(dev); unsigned int val; + u8 regvals[2]; int res; u8 i; @@ -303,26 +296,13 @@ static int max6639_read_pwm(struct device *dev, u32 attr, int channel, *pwm_val = val * 255 / 120; return 0; case hwmon_pwm_freq: - mutex_lock(&data->update_lock); - res = regmap_read(data->regmap, MAX6639_REG_FAN_CONFIG3(channel), &val); - if (res < 0) { - mutex_unlock(&data->update_lock); + res = regmap_multi_reg_read(data->regmap, regs, regvals, 2); + if (res < 0) return res; - } - i = val & MAX6639_FAN_CONFIG3_FREQ_MASK; - - res = regmap_read(data->regmap, MAX6639_REG_GCONFIG, &val); - if (res < 0) { - mutex_unlock(&data->update_lock); - return res; - } - - if (val & MAX6639_GCONFIG_PWM_FREQ_HI) + i = regvals[0] & MAX6639_FAN_CONFIG3_FREQ_MASK; + if (regvals[1] & MAX6639_GCONFIG_PWM_FREQ_HI) i |= 0x4; - i &= 0x7; *pwm_val = freq_table[i]; - - mutex_unlock(&data->update_lock); return 0; default: return -EOPNOTSUPP; From c37d0f08f8897f8a280566908545d13d450ba6ef Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 16 Jul 2024 12:01:21 -0700 Subject: [PATCH 014/573] hwmon: (amc6821) Use multi-byte regmap operations Use multi-byte regmap operations where possible to reduce code size. No functional changes. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/amc6821.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c index ec94392fcb65..ac64b407ed0e 100644 --- a/drivers/hwmon/amc6821.c +++ b/drivers/hwmon/amc6821.c @@ -136,29 +136,25 @@ struct amc6821_data { */ static int amc6821_get_auto_point_temps(struct regmap *regmap, int channel, u8 *temps) { - u32 pwm, regval; + u32 regs[] = { + AMC6821_REG_DCY_LOW_TEMP, + AMC6821_REG_PSV_TEMP, + channel ? AMC6821_REG_RTEMP_FAN_CTRL : AMC6821_REG_LTEMP_FAN_CTRL + }; + u8 regvals[3]; + int slope; int err; - err = regmap_read(regmap, AMC6821_REG_DCY_LOW_TEMP, &pwm); + err = regmap_multi_reg_read(regmap, regs, regvals, 3); if (err) return err; - - err = regmap_read(regmap, AMC6821_REG_PSV_TEMP, ®val); - if (err) - return err; - temps[0] = regval; - - err = regmap_read(regmap, - channel ? AMC6821_REG_RTEMP_FAN_CTRL : AMC6821_REG_LTEMP_FAN_CTRL, - ®val); - if (err) - return err; - temps[1] = FIELD_GET(AMC6821_TEMP_LIMIT_MASK, regval) * 4; + temps[0] = regvals[1]; + temps[1] = FIELD_GET(AMC6821_TEMP_LIMIT_MASK, regvals[2]) * 4; /* slope is 32 >> in °C */ - regval = 32 >> FIELD_GET(AMC6821_TEMP_SLOPE_MASK, regval); - if (regval) - temps[2] = temps[1] + DIV_ROUND_CLOSEST(255 - pwm, regval); + slope = 32 >> FIELD_GET(AMC6821_TEMP_SLOPE_MASK, regvals[2]); + if (slope) + temps[2] = temps[1] + DIV_ROUND_CLOSEST(255 - regvals[0], slope); else temps[2] = 255; From 8abff91c6173b175bda3381bdb14981249502434 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 7 Jul 2024 07:49:51 -0700 Subject: [PATCH 015/573] hwmon: (lm95234) Reorder include files to be in alphabetic order Alphabetic include file order simplifies maintenance and makes it easier to add or remove files. No functional change. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/lm95234.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/lm95234.c b/drivers/hwmon/lm95234.c index 9a7afdb49895..0c509eed6a01 100644 --- a/drivers/hwmon/lm95234.c +++ b/drivers/hwmon/lm95234.c @@ -8,14 +8,14 @@ * Copyright (C) 2008, 2010 Davide Rizzo */ -#include -#include -#include -#include -#include +#include #include #include -#include +#include +#include +#include +#include +#include #include #include From e38b05f0a2fd5b92341d366c9f4f74301cd41ac6 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 9 Jul 2024 21:23:30 -0700 Subject: [PATCH 016/573] hwmon: (lm95234) Use find_closest to find matching update interval Use find_closest() instead of manually coding it to find best update interval. Since find_closest() uses rounding to find the best match, the resulting update interval will now reflect the update interval that is closest to the requested value, not the value that is lower or equal to the requested value. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/lm95234.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/lm95234.c b/drivers/hwmon/lm95234.c index 0c509eed6a01..a36fa7824da8 100644 --- a/drivers/hwmon/lm95234.c +++ b/drivers/hwmon/lm95234.c @@ -18,6 +18,7 @@ #include #include #include +#include #define DRVNAME "lm95234" @@ -471,10 +472,7 @@ static ssize_t update_interval_store(struct device *dev, if (ret < 0) return ret; - for (regval = 0; regval < 3; regval++) { - if (val <= update_intervals[regval]) - break; - } + regval = find_closest(val, update_intervals, ARRAY_SIZE(update_intervals)); mutex_lock(&data->update_lock); data->interval = msecs_to_jiffies(update_intervals[regval]); From 963f9bc976e27bf073b449497fcce404ef223f91 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 7 Jul 2024 16:43:04 -0700 Subject: [PATCH 017/573] hwmon: (lm95234) Convert to use regmap Use regmap to replace local caching and to be able to use regmap API functions. No functional change. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 1 + drivers/hwmon/lm95234.c | 437 +++++++++++++++++----------------------- 2 files changed, 186 insertions(+), 252 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index b60fe2e58ad6..e838a55bb3cb 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1532,6 +1532,7 @@ config SENSORS_LM93 config SENSORS_LM95234 tristate "National Semiconductor LM95234 and compatibles" depends on I2C + select REGMAP_I2C help If you say yes here you get support for the LM95233 and LM95234 temperature sensor chips. diff --git a/drivers/hwmon/lm95234.c b/drivers/hwmon/lm95234.c index a36fa7824da8..7a3aff1d183a 100644 --- a/drivers/hwmon/lm95234.c +++ b/drivers/hwmon/lm95234.c @@ -13,10 +13,10 @@ #include #include #include -#include #include #include #include +#include #include #include @@ -33,6 +33,8 @@ static const unsigned short normal_i2c[] = { #define LM95234_REG_STATUS 0x02 #define LM95234_REG_CONFIG 0x03 #define LM95234_REG_CONVRATE 0x04 +#define LM95234_REG_ENABLE 0x05 +#define LM95234_REG_FILTER 0x06 #define LM95234_REG_STS_FAULT 0x07 #define LM95234_REG_STS_TCRIT1 0x08 #define LM95234_REG_STS_TCRIT2 0x09 @@ -53,181 +55,72 @@ static const unsigned short normal_i2c[] = { /* Client data (each client gets its own) */ struct lm95234_data { - struct i2c_client *client; + struct regmap *regmap; const struct attribute_group *groups[3]; struct mutex update_lock; - unsigned long last_updated, interval; /* in jiffies */ - bool valid; /* false until following fields are valid */ - /* registers values */ - int temp[5]; /* temperature (signed) */ - u32 status; /* fault/alarm status */ - u8 tcrit1[5]; /* critical temperature limit */ - u8 tcrit2[2]; /* high temperature limit */ - s8 toffset[4]; /* remote temperature offset */ - u8 thyst; /* common hysteresis */ - - u8 sensor_type; /* temperature sensor type */ }; -static int lm95234_read_temp(struct i2c_client *client, int index, int *t) +static int lm95234_read_temp(struct regmap *regmap, int index, int *t) { - int val; - u16 temp = 0; + int temp = 0, ret; + u32 val; if (index) { - val = i2c_smbus_read_byte_data(client, - LM95234_REG_UTEMPH(index - 1)); - if (val < 0) - return val; + ret = regmap_read(regmap, LM95234_REG_UTEMPH(index - 1), &val); + if (ret) + return ret; temp = val << 8; - val = i2c_smbus_read_byte_data(client, - LM95234_REG_UTEMPL(index - 1)); - if (val < 0) - return val; + ret = regmap_read(regmap, LM95234_REG_UTEMPL(index - 1), &val); + if (ret) + return ret; temp |= val; - *t = temp; } /* * Read signed temperature if unsigned temperature is 0, * or if this is the local sensor. */ if (!temp) { - val = i2c_smbus_read_byte_data(client, - LM95234_REG_TEMPH(index)); - if (val < 0) - return val; + ret = regmap_read(regmap, LM95234_REG_TEMPH(index), &val); + if (ret) + return ret; temp = val << 8; - val = i2c_smbus_read_byte_data(client, - LM95234_REG_TEMPL(index)); - if (val < 0) - return val; - temp |= val; - *t = (s16)temp; + ret = regmap_read(regmap, LM95234_REG_TEMPL(index), &val); + if (ret) + return ret; + temp = sign_extend32(temp | val, 15); } + *t = DIV_ROUND_CLOSEST(temp * 125, 32); return 0; } -static u16 update_intervals[] = { 143, 364, 1000, 2500 }; - -/* Fill value cache. Must be called with update lock held. */ - -static int lm95234_fill_cache(struct lm95234_data *data, - struct i2c_client *client) -{ - int i, ret; - - ret = i2c_smbus_read_byte_data(client, LM95234_REG_CONVRATE); - if (ret < 0) - return ret; - - data->interval = msecs_to_jiffies(update_intervals[ret & 0x03]); - - for (i = 0; i < ARRAY_SIZE(data->tcrit1); i++) { - ret = i2c_smbus_read_byte_data(client, LM95234_REG_TCRIT1(i)); - if (ret < 0) - return ret; - data->tcrit1[i] = ret; - } - for (i = 0; i < ARRAY_SIZE(data->tcrit2); i++) { - ret = i2c_smbus_read_byte_data(client, LM95234_REG_TCRIT2(i)); - if (ret < 0) - return ret; - data->tcrit2[i] = ret; - } - for (i = 0; i < ARRAY_SIZE(data->toffset); i++) { - ret = i2c_smbus_read_byte_data(client, LM95234_REG_OFFSET(i)); - if (ret < 0) - return ret; - data->toffset[i] = ret; - } - - ret = i2c_smbus_read_byte_data(client, LM95234_REG_TCRIT_HYST); - if (ret < 0) - return ret; - data->thyst = ret; - - ret = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL); - if (ret < 0) - return ret; - data->sensor_type = ret; - - return 0; -} - -static int lm95234_update_device(struct lm95234_data *data) -{ - struct i2c_client *client = data->client; - int ret; - - mutex_lock(&data->update_lock); - - if (time_after(jiffies, data->last_updated + data->interval) || - !data->valid) { - int i; - - if (!data->valid) { - ret = lm95234_fill_cache(data, client); - if (ret < 0) - goto abort; - } - - data->valid = false; - for (i = 0; i < ARRAY_SIZE(data->temp); i++) { - ret = lm95234_read_temp(client, i, &data->temp[i]); - if (ret < 0) - goto abort; - } - - ret = i2c_smbus_read_byte_data(client, LM95234_REG_STS_FAULT); - if (ret < 0) - goto abort; - data->status = ret; - - ret = i2c_smbus_read_byte_data(client, LM95234_REG_STS_TCRIT1); - if (ret < 0) - goto abort; - data->status |= ret << 8; - - ret = i2c_smbus_read_byte_data(client, LM95234_REG_STS_TCRIT2); - if (ret < 0) - goto abort; - data->status |= ret << 16; - - data->last_updated = jiffies; - data->valid = true; - } - ret = 0; -abort: - mutex_unlock(&data->update_lock); - - return ret; -} - static ssize_t temp_show(struct device *dev, struct device_attribute *attr, char *buf) { struct lm95234_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; - int ret = lm95234_update_device(data); + int ret, temp; + ret = lm95234_read_temp(data->regmap, index, &temp); if (ret) return ret; - return sprintf(buf, "%d\n", - DIV_ROUND_CLOSEST(data->temp[index] * 125, 32)); + return sysfs_emit(buf, "%d\n", temp); } static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf) { struct lm95234_data *data = dev_get_drvdata(dev); - u32 mask = to_sensor_dev_attr(attr)->index; - int ret = lm95234_update_device(data); + u8 mask = to_sensor_dev_attr_2(attr)->index; + u8 reg = to_sensor_dev_attr_2(attr)->nr; + int ret; + u32 val; + ret = regmap_read(data->regmap, reg, &val); if (ret) return ret; - return sprintf(buf, "%u", !!(data->status & mask)); + return sysfs_emit(buf, "%u\n", !!(val & mask)); } static ssize_t type_show(struct device *dev, struct device_attribute *attr, @@ -235,24 +128,23 @@ static ssize_t type_show(struct device *dev, struct device_attribute *attr, { struct lm95234_data *data = dev_get_drvdata(dev); u8 mask = to_sensor_dev_attr(attr)->index; - int ret = lm95234_update_device(data); + u32 val; + int ret; + ret = regmap_read(data->regmap, LM95234_REG_REM_MODEL, &val); if (ret) return ret; - return sprintf(buf, data->sensor_type & mask ? "1\n" : "2\n"); + return sysfs_emit(buf, "%s\n", val & mask ? "1" : "2"); } static ssize_t type_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct lm95234_data *data = dev_get_drvdata(dev); - unsigned long val; u8 mask = to_sensor_dev_attr(attr)->index; - int ret = lm95234_update_device(data); - - if (ret) - return ret; + unsigned long val; + int ret; ret = kstrtoul(buf, 10, &val); if (ret < 0) @@ -261,16 +153,10 @@ static ssize_t type_store(struct device *dev, struct device_attribute *attr, if (val != 1 && val != 2) return -EINVAL; - mutex_lock(&data->update_lock); - if (val == 1) - data->sensor_type |= mask; - else - data->sensor_type &= ~mask; - data->valid = false; - i2c_smbus_write_byte_data(data->client, LM95234_REG_REM_MODEL, - data->sensor_type); - mutex_unlock(&data->update_lock); - + ret = regmap_update_bits(data->regmap, LM95234_REG_REM_MODEL, + mask, val == 1 ? mask : 0); + if (ret) + return ret; return count; } @@ -279,12 +165,14 @@ static ssize_t tcrit2_show(struct device *dev, struct device_attribute *attr, { struct lm95234_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; - int ret = lm95234_update_device(data); + int ret; + u32 tcrit2; + ret = regmap_read(data->regmap, LM95234_REG_TCRIT2(index), &tcrit2); if (ret) return ret; - return sprintf(buf, "%u", data->tcrit2[index] * 1000); + return sysfs_emit(buf, "%u\n", tcrit2 * 1000); } static ssize_t tcrit2_store(struct device *dev, struct device_attribute *attr, @@ -293,10 +181,7 @@ static ssize_t tcrit2_store(struct device *dev, struct device_attribute *attr, struct lm95234_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; long val; - int ret = lm95234_update_device(data); - - if (ret) - return ret; + int ret; ret = kstrtol(buf, 10, &val); if (ret < 0) @@ -305,27 +190,38 @@ static ssize_t tcrit2_store(struct device *dev, struct device_attribute *attr, val = DIV_ROUND_CLOSEST(clamp_val(val, 0, (index ? 255 : 127) * 1000), 1000); - mutex_lock(&data->update_lock); - data->tcrit2[index] = val; - i2c_smbus_write_byte_data(data->client, LM95234_REG_TCRIT2(index), val); - mutex_unlock(&data->update_lock); - + ret = regmap_write(data->regmap, LM95234_REG_TCRIT2(index), val); + if (ret) + return ret; return count; } +static ssize_t tcrit_hyst_show(struct lm95234_data *data, char *buf, int reg) +{ + u32 thyst, tcrit; + int ret; + + mutex_lock(&data->update_lock); + ret = regmap_read(data->regmap, reg, &tcrit); + if (ret) + goto unlock; + ret = regmap_read(data->regmap, LM95234_REG_TCRIT_HYST, &thyst); +unlock: + mutex_unlock(&data->update_lock); + if (ret) + return ret; + + /* Result can be negative, so be careful with unsigned operands */ + return sysfs_emit(buf, "%d\n", ((int)tcrit - (int)thyst) * 1000); +} + static ssize_t tcrit2_hyst_show(struct device *dev, struct device_attribute *attr, char *buf) { struct lm95234_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; - int ret = lm95234_update_device(data); - if (ret) - return ret; - - /* Result can be negative, so be careful with unsigned operands */ - return sprintf(buf, "%d", - ((int)data->tcrit2[index] - (int)data->thyst) * 1000); + return tcrit_hyst_show(data, buf, LM95234_REG_TCRIT2(index)); } static ssize_t tcrit1_show(struct device *dev, struct device_attribute *attr, @@ -333,8 +229,14 @@ static ssize_t tcrit1_show(struct device *dev, struct device_attribute *attr, { struct lm95234_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; + int ret; + u32 val; - return sprintf(buf, "%u", data->tcrit1[index] * 1000); + ret = regmap_read(data->regmap, LM95234_REG_TCRIT1(index), &val); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", val * 1000); } static ssize_t tcrit1_store(struct device *dev, struct device_attribute *attr, @@ -342,11 +244,8 @@ static ssize_t tcrit1_store(struct device *dev, struct device_attribute *attr, { struct lm95234_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; - int ret = lm95234_update_device(data); long val; - - if (ret) - return ret; + int ret; ret = kstrtol(buf, 10, &val); if (ret < 0) @@ -354,10 +253,9 @@ static ssize_t tcrit1_store(struct device *dev, struct device_attribute *attr, val = DIV_ROUND_CLOSEST(clamp_val(val, 0, 255000), 1000); - mutex_lock(&data->update_lock); - data->tcrit1[index] = val; - i2c_smbus_write_byte_data(data->client, LM95234_REG_TCRIT1(index), val); - mutex_unlock(&data->update_lock); + ret = regmap_write(data->regmap, LM95234_REG_TCRIT1(index), val); + if (ret) + return ret; return count; } @@ -367,14 +265,8 @@ static ssize_t tcrit1_hyst_show(struct device *dev, { struct lm95234_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; - int ret = lm95234_update_device(data); - if (ret) - return ret; - - /* Result can be negative, so be careful with unsigned operands */ - return sprintf(buf, "%d", - ((int)data->tcrit1[index] - (int)data->thyst) * 1000); + return tcrit_hyst_show(data, buf, LM95234_REG_TCRIT1(index)); } static ssize_t tcrit1_hyst_store(struct device *dev, @@ -383,23 +275,28 @@ static ssize_t tcrit1_hyst_store(struct device *dev, { struct lm95234_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; - int ret = lm95234_update_device(data); + u32 tcrit; long val; - - if (ret) - return ret; + int ret; ret = kstrtol(buf, 10, &val); if (ret < 0) return ret; - val = DIV_ROUND_CLOSEST(clamp_val(val, -255000, 255000), 1000); - val = clamp_val((int)data->tcrit1[index] - val, 0, 31); - mutex_lock(&data->update_lock); - data->thyst = val; - i2c_smbus_write_byte_data(data->client, LM95234_REG_TCRIT_HYST, val); + + ret = regmap_read(data->regmap, LM95234_REG_TCRIT1(index), &tcrit); + if (ret) + goto unlock; + + val = DIV_ROUND_CLOSEST(clamp_val(val, -255000, 255000), 1000); + val = clamp_val((int)tcrit - val, 0, 31); + + ret = regmap_write(data->regmap, LM95234_REG_TCRIT_HYST, val); +unlock: mutex_unlock(&data->update_lock); + if (ret) + return ret; return count; } @@ -409,12 +306,14 @@ static ssize_t offset_show(struct device *dev, struct device_attribute *attr, { struct lm95234_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; - int ret = lm95234_update_device(data); + u32 offset; + int ret; + ret = regmap_read(data->regmap, LM95234_REG_OFFSET(index), &offset); if (ret) return ret; - return sprintf(buf, "%d", data->toffset[index] * 500); + return sysfs_emit(buf, "%d\n", sign_extend32(offset, 7) * 500); } static ssize_t offset_store(struct device *dev, struct device_attribute *attr, @@ -422,11 +321,8 @@ static ssize_t offset_store(struct device *dev, struct device_attribute *attr, { struct lm95234_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; - int ret = lm95234_update_device(data); long val; - - if (ret) - return ret; + int ret; ret = kstrtol(buf, 10, &val); if (ret < 0) @@ -435,25 +331,27 @@ static ssize_t offset_store(struct device *dev, struct device_attribute *attr, /* Accuracy is 1/2 degrees C */ val = DIV_ROUND_CLOSEST(clamp_val(val, -64000, 63500), 500); - mutex_lock(&data->update_lock); - data->toffset[index] = val; - i2c_smbus_write_byte_data(data->client, LM95234_REG_OFFSET(index), val); - mutex_unlock(&data->update_lock); + ret = regmap_write(data->regmap, LM95234_REG_OFFSET(index), val); + if (ret < 0) + return ret; return count; } +static u16 update_intervals[] = { 143, 364, 1000, 2500 }; + static ssize_t update_interval_show(struct device *dev, struct device_attribute *attr, char *buf) { struct lm95234_data *data = dev_get_drvdata(dev); - int ret = lm95234_update_device(data); + u32 convrate; + int ret; + ret = regmap_read(data->regmap, LM95234_REG_CONVRATE, &convrate); if (ret) return ret; - return sprintf(buf, "%lu\n", - DIV_ROUND_CLOSEST(data->interval * 1000, HZ)); + return sysfs_emit(buf, "%u\n", update_intervals[convrate & 0x03]); } static ssize_t update_interval_store(struct device *dev, @@ -461,23 +359,17 @@ static ssize_t update_interval_store(struct device *dev, const char *buf, size_t count) { struct lm95234_data *data = dev_get_drvdata(dev); - int ret = lm95234_update_device(data); unsigned long val; - u8 regval; - - if (ret) - return ret; + int ret; ret = kstrtoul(buf, 10, &val); if (ret < 0) return ret; - regval = find_closest(val, update_intervals, ARRAY_SIZE(update_intervals)); - - mutex_lock(&data->update_lock); - data->interval = msecs_to_jiffies(update_intervals[regval]); - i2c_smbus_write_byte_data(data->client, LM95234_REG_CONVRATE, regval); - mutex_unlock(&data->update_lock); + val = find_closest(val, update_intervals, ARRAY_SIZE(update_intervals)); + ret = regmap_write(data->regmap, LM95234_REG_CONVRATE, val); + if (ret) + return ret; return count; } @@ -488,10 +380,10 @@ static SENSOR_DEVICE_ATTR_RO(temp3_input, temp, 2); static SENSOR_DEVICE_ATTR_RO(temp4_input, temp, 3); static SENSOR_DEVICE_ATTR_RO(temp5_input, temp, 4); -static SENSOR_DEVICE_ATTR_RO(temp2_fault, alarm, BIT(0) | BIT(1)); -static SENSOR_DEVICE_ATTR_RO(temp3_fault, alarm, BIT(2) | BIT(3)); -static SENSOR_DEVICE_ATTR_RO(temp4_fault, alarm, BIT(4) | BIT(5)); -static SENSOR_DEVICE_ATTR_RO(temp5_fault, alarm, BIT(6) | BIT(7)); +static SENSOR_DEVICE_ATTR_2_RO(temp2_fault, alarm, LM95234_REG_STS_FAULT, BIT(0) | BIT(1)); +static SENSOR_DEVICE_ATTR_2_RO(temp3_fault, alarm, LM95234_REG_STS_FAULT, BIT(2) | BIT(3)); +static SENSOR_DEVICE_ATTR_2_RO(temp4_fault, alarm, LM95234_REG_STS_FAULT, BIT(4) | BIT(5)); +static SENSOR_DEVICE_ATTR_2_RO(temp5_fault, alarm, LM95234_REG_STS_FAULT, BIT(6) | BIT(7)); static SENSOR_DEVICE_ATTR_RW(temp2_type, type, BIT(1)); static SENSOR_DEVICE_ATTR_RW(temp3_type, type, BIT(2)); @@ -510,11 +402,11 @@ static SENSOR_DEVICE_ATTR_RO(temp3_max_hyst, tcrit2_hyst, 1); static SENSOR_DEVICE_ATTR_RO(temp4_max_hyst, tcrit1_hyst, 3); static SENSOR_DEVICE_ATTR_RO(temp5_max_hyst, tcrit1_hyst, 4); -static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, BIT(0 + 8)); -static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, alarm, BIT(1 + 16)); -static SENSOR_DEVICE_ATTR_RO(temp3_max_alarm, alarm, BIT(2 + 16)); -static SENSOR_DEVICE_ATTR_RO(temp4_max_alarm, alarm, BIT(3 + 8)); -static SENSOR_DEVICE_ATTR_RO(temp5_max_alarm, alarm, BIT(4 + 8)); +static SENSOR_DEVICE_ATTR_2_RO(temp1_max_alarm, alarm, LM95234_REG_STS_TCRIT1, BIT(0)); +static SENSOR_DEVICE_ATTR_2_RO(temp2_max_alarm, alarm, LM95234_REG_STS_TCRIT2, BIT(1)); +static SENSOR_DEVICE_ATTR_2_RO(temp3_max_alarm, alarm, LM95234_REG_STS_TCRIT2, BIT(2)); +static SENSOR_DEVICE_ATTR_2_RO(temp4_max_alarm, alarm, LM95234_REG_STS_TCRIT1, BIT(3)); +static SENSOR_DEVICE_ATTR_2_RO(temp5_max_alarm, alarm, LM95234_REG_STS_TCRIT1, BIT(4)); static SENSOR_DEVICE_ATTR_RW(temp2_crit, tcrit1, 1); static SENSOR_DEVICE_ATTR_RW(temp3_crit, tcrit1, 2); @@ -522,8 +414,8 @@ static SENSOR_DEVICE_ATTR_RW(temp3_crit, tcrit1, 2); static SENSOR_DEVICE_ATTR_RO(temp2_crit_hyst, tcrit1_hyst, 1); static SENSOR_DEVICE_ATTR_RO(temp3_crit_hyst, tcrit1_hyst, 2); -static SENSOR_DEVICE_ATTR_RO(temp2_crit_alarm, alarm, BIT(1 + 8)); -static SENSOR_DEVICE_ATTR_RO(temp3_crit_alarm, alarm, BIT(2 + 8)); +static SENSOR_DEVICE_ATTR_2_RO(temp2_crit_alarm, alarm, LM95234_REG_STS_TCRIT1, BIT(1)); +static SENSOR_DEVICE_ATTR_2_RO(temp3_crit_alarm, alarm, LM95234_REG_STS_TCRIT1, BIT(2)); static SENSOR_DEVICE_ATTR_RW(temp2_offset, offset, 0); static SENSOR_DEVICE_ATTR_RW(temp3_offset, offset, 1); @@ -587,6 +479,45 @@ static const struct attribute_group lm95234_group = { .attrs = lm95234_attrs, }; +static bool lm95234_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LM95234_REG_TEMPH(0) ... LM95234_REG_TEMPH(4): + case LM95234_REG_TEMPL(0) ... LM95234_REG_TEMPL(4): + case LM95234_REG_UTEMPH(0) ... LM95234_REG_UTEMPH(3): + case LM95234_REG_UTEMPL(0) ... LM95234_REG_UTEMPL(3): + case LM95234_REG_STS_FAULT: + case LM95234_REG_STS_TCRIT1: + case LM95234_REG_STS_TCRIT2: + case LM95234_REG_REM_MODEL_STS: + return true; + default: + return false; + } +} + +static bool lm95234_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LM95234_REG_CONFIG ... LM95234_REG_FILTER: + case LM95234_REG_REM_MODEL ... LM95234_REG_OFFSET(3): + case LM95234_REG_TCRIT1(0) ... LM95234_REG_TCRIT1(4): + case LM95234_REG_TCRIT2(0) ... LM95234_REG_TCRIT2(1): + case LM95234_REG_TCRIT_HYST: + return true; + default: + return false; + } +} + +static const struct regmap_config lm95234_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = lm95234_writeable_reg, + .volatile_reg = lm95234_volatile_reg, + .cache_type = REGCACHE_MAPLE, +}; + static int lm95234_detect(struct i2c_client *client, struct i2c_board_info *info) { @@ -647,33 +578,30 @@ static int lm95234_detect(struct i2c_client *client, return 0; } -static int lm95234_init_client(struct i2c_client *client) +static int lm95234_init_client(struct device *dev, struct regmap *regmap) { - int val, model; + u32 val, model; + int ret; /* start conversion if necessary */ - val = i2c_smbus_read_byte_data(client, LM95234_REG_CONFIG); - if (val < 0) - return val; - if (val & 0x40) - i2c_smbus_write_byte_data(client, LM95234_REG_CONFIG, - val & ~0x40); + ret = regmap_clear_bits(regmap, LM95234_REG_CONFIG, 0x40); + if (ret) + return ret; /* If diode type status reports an error, try to fix it */ - val = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL_STS); - if (val < 0) - return val; - model = i2c_smbus_read_byte_data(client, LM95234_REG_REM_MODEL); - if (model < 0) - return model; + ret = regmap_read(regmap, LM95234_REG_REM_MODEL_STS, &val); + if (ret < 0) + return ret; + ret = regmap_read(regmap, LM95234_REG_REM_MODEL, &model); + if (ret < 0) + return ret; if (model & val) { - dev_notice(&client->dev, + dev_notice(dev, "Fixing remote diode type misconfiguration (0x%x)\n", val); - i2c_smbus_write_byte_data(client, LM95234_REG_REM_MODEL, - model & ~val); + ret = regmap_write(regmap, LM95234_REG_REM_MODEL, model & ~val); } - return 0; + return ret; } static int lm95234_probe(struct i2c_client *client) @@ -682,17 +610,22 @@ static int lm95234_probe(struct i2c_client *client) struct device *dev = &client->dev; struct lm95234_data *data; struct device *hwmon_dev; + struct regmap *regmap; int err; data = devm_kzalloc(dev, sizeof(struct lm95234_data), GFP_KERNEL); if (!data) return -ENOMEM; - data->client = client; + regmap = devm_regmap_init_i2c(client, &lm95234_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + data->regmap = regmap; mutex_init(&data->update_lock); /* Initialize the LM95234 chip */ - err = lm95234_init_client(client); + err = lm95234_init_client(dev, regmap); if (err < 0) return err; From f27ab9f05c8633d63e50d4c412e7425c2dbad176 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 10 Jul 2024 07:11:12 -0700 Subject: [PATCH 018/573] hwmon: (lm95234) Convert to with_info hwmon API Convert to with_info API to simplify the code and reduce its size. This patch reduces the object file size by about 30%. No functional change. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/lm95234.c | 557 ++++++++++++++++------------------------ 1 file changed, 227 insertions(+), 330 deletions(-) diff --git a/drivers/hwmon/lm95234.c b/drivers/hwmon/lm95234.c index 7a3aff1d183a..85350e9c9c4c 100644 --- a/drivers/hwmon/lm95234.c +++ b/drivers/hwmon/lm95234.c @@ -10,14 +10,12 @@ #include #include -#include #include #include #include #include #include #include -#include #include #define DRVNAME "lm95234" @@ -56,11 +54,11 @@ static const unsigned short normal_i2c[] = { /* Client data (each client gets its own) */ struct lm95234_data { struct regmap *regmap; - const struct attribute_group *groups[3]; struct mutex update_lock; + enum chips type; }; -static int lm95234_read_temp(struct regmap *regmap, int index, int *t) +static int lm95234_read_temp(struct regmap *regmap, int index, long *t) { int temp = 0, ret; u32 val; @@ -93,110 +91,7 @@ static int lm95234_read_temp(struct regmap *regmap, int index, int *t) return 0; } -static ssize_t temp_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct lm95234_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(attr)->index; - int ret, temp; - - ret = lm95234_read_temp(data->regmap, index, &temp); - if (ret) - return ret; - - return sysfs_emit(buf, "%d\n", temp); -} - -static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct lm95234_data *data = dev_get_drvdata(dev); - u8 mask = to_sensor_dev_attr_2(attr)->index; - u8 reg = to_sensor_dev_attr_2(attr)->nr; - int ret; - u32 val; - - ret = regmap_read(data->regmap, reg, &val); - if (ret) - return ret; - - return sysfs_emit(buf, "%u\n", !!(val & mask)); -} - -static ssize_t type_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct lm95234_data *data = dev_get_drvdata(dev); - u8 mask = to_sensor_dev_attr(attr)->index; - u32 val; - int ret; - - ret = regmap_read(data->regmap, LM95234_REG_REM_MODEL, &val); - if (ret) - return ret; - - return sysfs_emit(buf, "%s\n", val & mask ? "1" : "2"); -} - -static ssize_t type_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct lm95234_data *data = dev_get_drvdata(dev); - u8 mask = to_sensor_dev_attr(attr)->index; - unsigned long val; - int ret; - - ret = kstrtoul(buf, 10, &val); - if (ret < 0) - return ret; - - if (val != 1 && val != 2) - return -EINVAL; - - ret = regmap_update_bits(data->regmap, LM95234_REG_REM_MODEL, - mask, val == 1 ? mask : 0); - if (ret) - return ret; - return count; -} - -static ssize_t tcrit2_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct lm95234_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(attr)->index; - int ret; - u32 tcrit2; - - ret = regmap_read(data->regmap, LM95234_REG_TCRIT2(index), &tcrit2); - if (ret) - return ret; - - return sysfs_emit(buf, "%u\n", tcrit2 * 1000); -} - -static ssize_t tcrit2_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct lm95234_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(attr)->index; - long val; - int ret; - - ret = kstrtol(buf, 10, &val); - if (ret < 0) - return ret; - - val = DIV_ROUND_CLOSEST(clamp_val(val, 0, (index ? 255 : 127) * 1000), - 1000); - - ret = regmap_write(data->regmap, LM95234_REG_TCRIT2(index), val); - if (ret) - return ret; - return count; -} - -static ssize_t tcrit_hyst_show(struct lm95234_data *data, char *buf, int reg) +static int lm95234_hyst_get(struct lm95234_data *data, int reg, long *val) { u32 thyst, tcrit; int ret; @@ -212,80 +107,18 @@ unlock: return ret; /* Result can be negative, so be careful with unsigned operands */ - return sysfs_emit(buf, "%d\n", ((int)tcrit - (int)thyst) * 1000); + *val = ((int)tcrit - (int)thyst) * 1000; + return 0; } -static ssize_t tcrit2_hyst_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t lm95234_hyst_set(struct lm95234_data *data, long val) { - struct lm95234_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(attr)->index; - - return tcrit_hyst_show(data, buf, LM95234_REG_TCRIT2(index)); -} - -static ssize_t tcrit1_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct lm95234_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(attr)->index; - int ret; - u32 val; - - ret = regmap_read(data->regmap, LM95234_REG_TCRIT1(index), &val); - if (ret) - return ret; - - return sysfs_emit(buf, "%u\n", val * 1000); -} - -static ssize_t tcrit1_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct lm95234_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(attr)->index; - long val; - int ret; - - ret = kstrtol(buf, 10, &val); - if (ret < 0) - return ret; - - val = DIV_ROUND_CLOSEST(clamp_val(val, 0, 255000), 1000); - - ret = regmap_write(data->regmap, LM95234_REG_TCRIT1(index), val); - if (ret) - return ret; - - return count; -} - -static ssize_t tcrit1_hyst_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct lm95234_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(attr)->index; - - return tcrit_hyst_show(data, buf, LM95234_REG_TCRIT1(index)); -} - -static ssize_t tcrit1_hyst_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct lm95234_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(attr)->index; u32 tcrit; - long val; int ret; - ret = kstrtol(buf, 10, &val); - if (ret < 0) - return ret; - mutex_lock(&data->update_lock); - ret = regmap_read(data->regmap, LM95234_REG_TCRIT1(index), &tcrit); + ret = regmap_read(data->regmap, LM95234_REG_TCRIT1(0), &tcrit); if (ret) goto unlock; @@ -295,188 +128,255 @@ static ssize_t tcrit1_hyst_store(struct device *dev, ret = regmap_write(data->regmap, LM95234_REG_TCRIT_HYST, val); unlock: mutex_unlock(&data->update_lock); - if (ret) - return ret; - - return count; + return ret; } -static ssize_t offset_show(struct device *dev, struct device_attribute *attr, - char *buf) +static int lm95234_crit_reg(int channel) { - struct lm95234_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(attr)->index; - u32 offset; - int ret; - - ret = regmap_read(data->regmap, LM95234_REG_OFFSET(index), &offset); - if (ret) - return ret; - - return sysfs_emit(buf, "%d\n", sign_extend32(offset, 7) * 500); + if (channel == 1 || channel == 2) + return LM95234_REG_TCRIT2(channel - 1); + return LM95234_REG_TCRIT1(channel); } -static ssize_t offset_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +static int lm95234_temp_write(struct device *dev, u32 attr, int channel, long val) { struct lm95234_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(attr)->index; - long val; + struct regmap *regmap = data->regmap; + + switch (attr) { + case hwmon_temp_type: + if (val != 1 && val != 2) + return -EINVAL; + return regmap_update_bits(regmap, LM95234_REG_REM_MODEL, + BIT(channel), + val == 1 ? BIT(channel) : 0); + case hwmon_temp_offset: + val = DIV_ROUND_CLOSEST(clamp_val(val, -64000, 63500), 500); + return regmap_write(regmap, LM95234_REG_OFFSET(channel - 1), val); + case hwmon_temp_max: + val = clamp_val(val, 0, channel == 1 ? 127000 : 255000); + val = DIV_ROUND_CLOSEST(val, 1000); + return regmap_write(regmap, lm95234_crit_reg(channel), val); + case hwmon_temp_max_hyst: + return lm95234_hyst_set(data, val); + case hwmon_temp_crit: + val = DIV_ROUND_CLOSEST(clamp_val(val, 0, 255000), 1000); + return regmap_write(regmap, LM95234_REG_TCRIT1(channel), val); + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int lm95234_alarm_reg(int channel) +{ + if (channel == 1 || channel == 2) + return LM95234_REG_STS_TCRIT2; + return LM95234_REG_STS_TCRIT1; +} + +static int lm95234_temp_read(struct device *dev, u32 attr, int channel, long *val) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + u32 regval, mask; int ret; - ret = kstrtol(buf, 10, &val); - if (ret < 0) - return ret; - - /* Accuracy is 1/2 degrees C */ - val = DIV_ROUND_CLOSEST(clamp_val(val, -64000, 63500), 500); - - ret = regmap_write(data->regmap, LM95234_REG_OFFSET(index), val); - if (ret < 0) - return ret; - - return count; + switch (attr) { + case hwmon_temp_input: + return lm95234_read_temp(regmap, channel, val); + case hwmon_temp_max_alarm: + ret = regmap_read(regmap, lm95234_alarm_reg(channel), ®val); + if (ret) + return ret; + *val = !!(regval & BIT(channel)); + break; + case hwmon_temp_crit_alarm: + ret = regmap_read(regmap, LM95234_REG_STS_TCRIT1, ®val); + if (ret) + return ret; + *val = !!(regval & BIT(channel)); + break; + case hwmon_temp_crit_hyst: + return lm95234_hyst_get(data, LM95234_REG_TCRIT1(channel), val); + case hwmon_temp_type: + ret = regmap_read(regmap, LM95234_REG_REM_MODEL, ®val); + if (ret) + return ret; + *val = (regval & BIT(channel)) ? 1 : 2; + break; + case hwmon_temp_offset: + ret = regmap_read(regmap, LM95234_REG_OFFSET(channel - 1), ®val); + if (ret) + return ret; + *val = sign_extend32(regval, 7) * 500; + break; + case hwmon_temp_fault: + ret = regmap_read(regmap, LM95234_REG_STS_FAULT, ®val); + if (ret) + return ret; + mask = (BIT(0) | BIT(1)) << ((channel - 1) << 1); + *val = !!(regval & mask); + break; + case hwmon_temp_max: + ret = regmap_read(regmap, lm95234_crit_reg(channel), ®val); + if (ret) + return ret; + *val = regval * 1000; + break; + case hwmon_temp_max_hyst: + return lm95234_hyst_get(data, lm95234_crit_reg(channel), val); + case hwmon_temp_crit: + ret = regmap_read(regmap, LM95234_REG_TCRIT1(channel), ®val); + if (ret) + return ret; + *val = regval * 1000; + break; + default: + return -EOPNOTSUPP; + } + return 0; } static u16 update_intervals[] = { 143, 364, 1000, 2500 }; -static ssize_t update_interval_show(struct device *dev, - struct device_attribute *attr, char *buf) +static int lm95234_chip_write(struct device *dev, u32 attr, long val) +{ + struct lm95234_data *data = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_chip_update_interval: + val = find_closest(val, update_intervals, ARRAY_SIZE(update_intervals)); + return regmap_write(data->regmap, LM95234_REG_CONVRATE, val); + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int lm95234_chip_read(struct device *dev, u32 attr, long *val) { struct lm95234_data *data = dev_get_drvdata(dev); u32 convrate; int ret; - ret = regmap_read(data->regmap, LM95234_REG_CONVRATE, &convrate); - if (ret) - return ret; + switch (attr) { + case hwmon_chip_update_interval: + ret = regmap_read(data->regmap, LM95234_REG_CONVRATE, &convrate); + if (ret) + return ret; - return sysfs_emit(buf, "%u\n", update_intervals[convrate & 0x03]); + *val = update_intervals[convrate & 0x03]; + break; + default: + return -EOPNOTSUPP; + } + return 0; } -static ssize_t update_interval_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static int lm95234_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) { - struct lm95234_data *data = dev_get_drvdata(dev); - unsigned long val; - int ret; - - ret = kstrtoul(buf, 10, &val); - if (ret < 0) - return ret; - - val = find_closest(val, update_intervals, ARRAY_SIZE(update_intervals)); - ret = regmap_write(data->regmap, LM95234_REG_CONVRATE, val); - if (ret) - return ret; - - return count; + switch (type) { + case hwmon_chip: + return lm95234_chip_write(dev, attr, val); + case hwmon_temp: + return lm95234_temp_write(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } } -static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0); -static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 1); -static SENSOR_DEVICE_ATTR_RO(temp3_input, temp, 2); -static SENSOR_DEVICE_ATTR_RO(temp4_input, temp, 3); -static SENSOR_DEVICE_ATTR_RO(temp5_input, temp, 4); +static int lm95234_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + return lm95234_chip_read(dev, attr, val); + case hwmon_temp: + return lm95234_temp_read(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} -static SENSOR_DEVICE_ATTR_2_RO(temp2_fault, alarm, LM95234_REG_STS_FAULT, BIT(0) | BIT(1)); -static SENSOR_DEVICE_ATTR_2_RO(temp3_fault, alarm, LM95234_REG_STS_FAULT, BIT(2) | BIT(3)); -static SENSOR_DEVICE_ATTR_2_RO(temp4_fault, alarm, LM95234_REG_STS_FAULT, BIT(4) | BIT(5)); -static SENSOR_DEVICE_ATTR_2_RO(temp5_fault, alarm, LM95234_REG_STS_FAULT, BIT(6) | BIT(7)); +static umode_t lm95234_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct lm95234_data *data = _data; -static SENSOR_DEVICE_ATTR_RW(temp2_type, type, BIT(1)); -static SENSOR_DEVICE_ATTR_RW(temp3_type, type, BIT(2)); -static SENSOR_DEVICE_ATTR_RW(temp4_type, type, BIT(3)); -static SENSOR_DEVICE_ATTR_RW(temp5_type, type, BIT(4)); + if (data->type == lm95233 && channel > 2) + return 0; -static SENSOR_DEVICE_ATTR_RW(temp1_max, tcrit1, 0); -static SENSOR_DEVICE_ATTR_RW(temp2_max, tcrit2, 0); -static SENSOR_DEVICE_ATTR_RW(temp3_max, tcrit2, 1); -static SENSOR_DEVICE_ATTR_RW(temp4_max, tcrit1, 3); -static SENSOR_DEVICE_ATTR_RW(temp5_max, tcrit1, 4); + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return 0644; + default: + break; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_max_alarm: + return 0444; + case hwmon_temp_crit_alarm: + case hwmon_temp_crit_hyst: + return (channel && channel < 3) ? 0444 : 0; + case hwmon_temp_type: + case hwmon_temp_offset: + return channel ? 0644 : 0; + case hwmon_temp_fault: + return channel ? 0444 : 0; + case hwmon_temp_max: + return 0644; + case hwmon_temp_max_hyst: + return channel ? 0444 : 0644; + case hwmon_temp_crit: + return (channel && channel < 3) ? 0644 : 0; + default: + break; + } + break; + default: + break; + } + return 0; +} -static SENSOR_DEVICE_ATTR_RW(temp1_max_hyst, tcrit1_hyst, 0); -static SENSOR_DEVICE_ATTR_RO(temp2_max_hyst, tcrit2_hyst, 0); -static SENSOR_DEVICE_ATTR_RO(temp3_max_hyst, tcrit2_hyst, 1); -static SENSOR_DEVICE_ATTR_RO(temp4_max_hyst, tcrit1_hyst, 3); -static SENSOR_DEVICE_ATTR_RO(temp5_max_hyst, tcrit1_hyst, 4); - -static SENSOR_DEVICE_ATTR_2_RO(temp1_max_alarm, alarm, LM95234_REG_STS_TCRIT1, BIT(0)); -static SENSOR_DEVICE_ATTR_2_RO(temp2_max_alarm, alarm, LM95234_REG_STS_TCRIT2, BIT(1)); -static SENSOR_DEVICE_ATTR_2_RO(temp3_max_alarm, alarm, LM95234_REG_STS_TCRIT2, BIT(2)); -static SENSOR_DEVICE_ATTR_2_RO(temp4_max_alarm, alarm, LM95234_REG_STS_TCRIT1, BIT(3)); -static SENSOR_DEVICE_ATTR_2_RO(temp5_max_alarm, alarm, LM95234_REG_STS_TCRIT1, BIT(4)); - -static SENSOR_DEVICE_ATTR_RW(temp2_crit, tcrit1, 1); -static SENSOR_DEVICE_ATTR_RW(temp3_crit, tcrit1, 2); - -static SENSOR_DEVICE_ATTR_RO(temp2_crit_hyst, tcrit1_hyst, 1); -static SENSOR_DEVICE_ATTR_RO(temp3_crit_hyst, tcrit1_hyst, 2); - -static SENSOR_DEVICE_ATTR_2_RO(temp2_crit_alarm, alarm, LM95234_REG_STS_TCRIT1, BIT(1)); -static SENSOR_DEVICE_ATTR_2_RO(temp3_crit_alarm, alarm, LM95234_REG_STS_TCRIT1, BIT(2)); - -static SENSOR_DEVICE_ATTR_RW(temp2_offset, offset, 0); -static SENSOR_DEVICE_ATTR_RW(temp3_offset, offset, 1); -static SENSOR_DEVICE_ATTR_RW(temp4_offset, offset, 2); -static SENSOR_DEVICE_ATTR_RW(temp5_offset, offset, 3); - -static DEVICE_ATTR_RW(update_interval); - -static struct attribute *lm95234_common_attrs[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp3_input.dev_attr.attr, - &sensor_dev_attr_temp2_fault.dev_attr.attr, - &sensor_dev_attr_temp3_fault.dev_attr.attr, - &sensor_dev_attr_temp2_type.dev_attr.attr, - &sensor_dev_attr_temp3_type.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp3_max.dev_attr.attr, - &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_crit.dev_attr.attr, - &sensor_dev_attr_temp3_crit.dev_attr.attr, - &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr, - &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr, - &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_offset.dev_attr.attr, - &sensor_dev_attr_temp3_offset.dev_attr.attr, - &dev_attr_update_interval.attr, +static const struct hwmon_channel_info * const lm95234_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_MAX_ALARM, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_MAX_ALARM | HWMON_T_FAULT | HWMON_T_TYPE | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | + HWMON_T_CRIT_ALARM | HWMON_T_OFFSET, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_MAX_ALARM | HWMON_T_FAULT | HWMON_T_TYPE | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | + HWMON_T_CRIT_ALARM | HWMON_T_OFFSET, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_MAX_ALARM | HWMON_T_FAULT | HWMON_T_TYPE | + HWMON_T_OFFSET, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_MAX_ALARM | HWMON_T_FAULT | HWMON_T_TYPE | + HWMON_T_OFFSET), NULL }; -static const struct attribute_group lm95234_common_group = { - .attrs = lm95234_common_attrs, +static const struct hwmon_ops lm95234_hwmon_ops = { + .is_visible = lm95234_is_visible, + .read = lm95234_read, + .write = lm95234_write, }; -static struct attribute *lm95234_attrs[] = { - &sensor_dev_attr_temp4_input.dev_attr.attr, - &sensor_dev_attr_temp5_input.dev_attr.attr, - &sensor_dev_attr_temp4_fault.dev_attr.attr, - &sensor_dev_attr_temp5_fault.dev_attr.attr, - &sensor_dev_attr_temp4_type.dev_attr.attr, - &sensor_dev_attr_temp5_type.dev_attr.attr, - &sensor_dev_attr_temp4_max.dev_attr.attr, - &sensor_dev_attr_temp5_max.dev_attr.attr, - &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp5_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp4_offset.dev_attr.attr, - &sensor_dev_attr_temp5_offset.dev_attr.attr, - NULL -}; - -static const struct attribute_group lm95234_group = { - .attrs = lm95234_attrs, +static const struct hwmon_chip_info lm95234_chip_info = { + .ops = &lm95234_hwmon_ops, + .info = lm95234_info, }; static bool lm95234_volatile_reg(struct device *dev, unsigned int reg) @@ -606,7 +506,6 @@ static int lm95234_init_client(struct device *dev, struct regmap *regmap) static int lm95234_probe(struct i2c_client *client) { - enum chips type = (uintptr_t)i2c_get_match_data(client); struct device *dev = &client->dev; struct lm95234_data *data; struct device *hwmon_dev; @@ -617,6 +516,8 @@ static int lm95234_probe(struct i2c_client *client) if (!data) return -ENOMEM; + data->type = (uintptr_t)i2c_get_match_data(client); + regmap = devm_regmap_init_i2c(client, &lm95234_regmap_config); if (IS_ERR(regmap)) return PTR_ERR(regmap); @@ -629,12 +530,8 @@ static int lm95234_probe(struct i2c_client *client) if (err < 0) return err; - data->groups[0] = &lm95234_common_group; - if (type == lm95234) - data->groups[1] = &lm95234_group; - - hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, - data, data->groups); + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, &lm95234_chip_info, NULL); return PTR_ERR_OR_ZERO(hwmon_dev); } From 280220236bf53a6eaa3c53653b649366228c4689 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 10 Jul 2024 15:57:18 -0700 Subject: [PATCH 019/573] hwmon: (lm95234) Add support for tempX_enable attribute LM95233/LM95234 support enabling temperature channels one by one. Add support for tempX_enable attribute to be able to use that functionality. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/lm95234.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/lm95234.c b/drivers/hwmon/lm95234.c index 85350e9c9c4c..d3d4b1d82899 100644 --- a/drivers/hwmon/lm95234.c +++ b/drivers/hwmon/lm95234.c @@ -144,6 +144,11 @@ static int lm95234_temp_write(struct device *dev, u32 attr, int channel, long va struct regmap *regmap = data->regmap; switch (attr) { + case hwmon_temp_enable: + if (val && val != 1) + return -EINVAL; + return regmap_update_bits(regmap, LM95234_REG_ENABLE, + BIT(channel), val ? BIT(channel) : 0); case hwmon_temp_type: if (val != 1 && val != 2) return -EINVAL; @@ -183,6 +188,12 @@ static int lm95234_temp_read(struct device *dev, u32 attr, int channel, long *va int ret; switch (attr) { + case hwmon_temp_enable: + ret = regmap_read(regmap, LM95234_REG_ENABLE, ®val); + if (ret) + return ret; + *val = !!(regval & BIT(channel)); + break; case hwmon_temp_input: return lm95234_read_temp(regmap, channel, val); case hwmon_temp_max_alarm: @@ -331,6 +342,7 @@ static umode_t lm95234_is_visible(const void *_data, enum hwmon_sensor_types typ case hwmon_temp_fault: return channel ? 0444 : 0; case hwmon_temp_max: + case hwmon_temp_enable: return 0644; case hwmon_temp_max_hyst: return channel ? 0444 : 0644; @@ -350,21 +362,21 @@ static const struct hwmon_channel_info * const lm95234_info[] = { HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | - HWMON_T_MAX_ALARM, + HWMON_T_MAX_ALARM | HWMON_T_ENABLE, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | HWMON_T_MAX_ALARM | HWMON_T_FAULT | HWMON_T_TYPE | HWMON_T_CRIT | HWMON_T_CRIT_HYST | - HWMON_T_CRIT_ALARM | HWMON_T_OFFSET, + HWMON_T_CRIT_ALARM | HWMON_T_OFFSET | HWMON_T_ENABLE, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | HWMON_T_MAX_ALARM | HWMON_T_FAULT | HWMON_T_TYPE | HWMON_T_CRIT | HWMON_T_CRIT_HYST | - HWMON_T_CRIT_ALARM | HWMON_T_OFFSET, + HWMON_T_CRIT_ALARM | HWMON_T_OFFSET | HWMON_T_ENABLE, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | HWMON_T_MAX_ALARM | HWMON_T_FAULT | HWMON_T_TYPE | - HWMON_T_OFFSET, + HWMON_T_OFFSET | HWMON_T_ENABLE, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | HWMON_T_MAX_ALARM | HWMON_T_FAULT | HWMON_T_TYPE | - HWMON_T_OFFSET), + HWMON_T_OFFSET | HWMON_T_ENABLE), NULL }; From 3ec621cd6fcac82c1d8b3a40f774cd984a7fe55b Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 10 Jul 2024 17:37:54 -0700 Subject: [PATCH 020/573] hwmon: (lm95234) Use multi-byte regmap operations Use multi-byte regmap operations to simplify the code and to reduce dependency on locking. No functional change. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/lm95234.c | 45 +++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/drivers/hwmon/lm95234.c b/drivers/hwmon/lm95234.c index d3d4b1d82899..7da6c8f07332 100644 --- a/drivers/hwmon/lm95234.c +++ b/drivers/hwmon/lm95234.c @@ -60,54 +60,45 @@ struct lm95234_data { static int lm95234_read_temp(struct regmap *regmap, int index, long *t) { + unsigned int regs[2]; int temp = 0, ret; - u32 val; + u8 regvals[2]; if (index) { - ret = regmap_read(regmap, LM95234_REG_UTEMPH(index - 1), &val); + regs[0] = LM95234_REG_UTEMPH(index - 1); + regs[1] = LM95234_REG_UTEMPL(index - 1); + ret = regmap_multi_reg_read(regmap, regs, regvals, 2); if (ret) return ret; - temp = val << 8; - ret = regmap_read(regmap, LM95234_REG_UTEMPL(index - 1), &val); - if (ret) - return ret; - temp |= val; + temp = (regvals[0] << 8) | regvals[1]; } /* * Read signed temperature if unsigned temperature is 0, * or if this is the local sensor. */ if (!temp) { - ret = regmap_read(regmap, LM95234_REG_TEMPH(index), &val); + regs[0] = LM95234_REG_TEMPH(index); + regs[1] = LM95234_REG_TEMPL(index); + ret = regmap_multi_reg_read(regmap, regs, regvals, 2); if (ret) return ret; - temp = val << 8; - ret = regmap_read(regmap, LM95234_REG_TEMPL(index), &val); - if (ret) - return ret; - temp = sign_extend32(temp | val, 15); + temp = (regvals[0] << 8) | regvals[1]; + temp = sign_extend32(temp, 15); } *t = DIV_ROUND_CLOSEST(temp * 125, 32); return 0; } -static int lm95234_hyst_get(struct lm95234_data *data, int reg, long *val) +static int lm95234_hyst_get(struct regmap *regmap, int reg, long *val) { - u32 thyst, tcrit; + unsigned int regs[2] = {reg, LM95234_REG_TCRIT_HYST}; + u8 regvals[2]; int ret; - mutex_lock(&data->update_lock); - ret = regmap_read(data->regmap, reg, &tcrit); - if (ret) - goto unlock; - ret = regmap_read(data->regmap, LM95234_REG_TCRIT_HYST, &thyst); -unlock: - mutex_unlock(&data->update_lock); + ret = regmap_multi_reg_read(regmap, regs, regvals, 2); if (ret) return ret; - - /* Result can be negative, so be careful with unsigned operands */ - *val = ((int)tcrit - (int)thyst) * 1000; + *val = (regvals[0] - regvals[1]) * 1000; return 0; } @@ -209,7 +200,7 @@ static int lm95234_temp_read(struct device *dev, u32 attr, int channel, long *va *val = !!(regval & BIT(channel)); break; case hwmon_temp_crit_hyst: - return lm95234_hyst_get(data, LM95234_REG_TCRIT1(channel), val); + return lm95234_hyst_get(regmap, LM95234_REG_TCRIT1(channel), val); case hwmon_temp_type: ret = regmap_read(regmap, LM95234_REG_REM_MODEL, ®val); if (ret) @@ -236,7 +227,7 @@ static int lm95234_temp_read(struct device *dev, u32 attr, int channel, long *va *val = regval * 1000; break; case hwmon_temp_max_hyst: - return lm95234_hyst_get(data, lm95234_crit_reg(channel), val); + return lm95234_hyst_get(regmap, lm95234_crit_reg(channel), val); case hwmon_temp_crit: ret = regmap_read(regmap, LM95234_REG_TCRIT1(channel), ®val); if (ret) From b4be51302d6873c23b4813676f851b5fbfa0f0fc Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 19 Jul 2024 22:37:06 +0200 Subject: [PATCH 021/573] hwmon: (dell-smm) Add Dell Latitude 7320 to fan control whitelist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the Dell Latitude 7320 to the fan control whitelist to allow for manual fan control. Closes: https://github.com/Wer-Wolf/i8kutils/issues/8 Signed-off-by: Armin Wolf Acked-by: Pali Rohár Link: https://lore.kernel.org/r/20240719203706.19466-1-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 0362a13f6525..f9b3a3030f13 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1488,6 +1488,14 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = { }, .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], }, + { + .ident = "Dell Latitude 7320", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 7320"), + }, + .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3], + }, { .ident = "Dell Latitude E6440", .matches = { From 97adb1aacef8f3f3ec3363ac1f8b72db432f55f0 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Sun, 21 Jul 2024 17:19:02 +0200 Subject: [PATCH 022/573] hwmon: (ltc2992) use device_for_each_child_node_scoped() to access child nodes The iterated nodes are direct children of the device node, and the `device_for_each_child_node()` macro accounts for child node availability. `fwnode_for_each_available_child_node()` is meant to access the child nodes of an fwnode, and therefore not direct child nodes of the device node. In this case, the child nodes are not required outside the loop, and the scoped version of the macro can be used to remove the repetitive `goto put` pattern. Use `device_for_each_child_node_scoped_scoped()` to indicate device's direct child nodes. Reviewed-by: Jonathan Cameron Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240721-device_for_each_child_node-available-v2-2-f33748fd8b2d@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc2992.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c index d4a93223cd3b..541fa09dc6e7 100644 --- a/drivers/hwmon/ltc2992.c +++ b/drivers/hwmon/ltc2992.c @@ -854,33 +854,24 @@ static const struct regmap_config ltc2992_regmap_config = { static int ltc2992_parse_dt(struct ltc2992_state *st) { - struct fwnode_handle *fwnode; - struct fwnode_handle *child; u32 addr; u32 val; int ret; - fwnode = dev_fwnode(&st->client->dev); - - fwnode_for_each_available_child_node(fwnode, child) { + device_for_each_child_node_scoped(&st->client->dev, child) { ret = fwnode_property_read_u32(child, "reg", &addr); - if (ret < 0) { - fwnode_handle_put(child); + if (ret < 0) return ret; - } - if (addr > 1) { - fwnode_handle_put(child); + if (addr > 1) return -EINVAL; - } ret = fwnode_property_read_u32(child, "shunt-resistor-micro-ohms", &val); if (!ret) { - if (!val) { - fwnode_handle_put(child); + if (!val) return dev_err_probe(&st->client->dev, -EINVAL, "shunt resistor value cannot be zero\n"); - } + st->r_sense_uohm[addr] = val; } } From 744ec4477b11c42e2c8de9eb8364675ae7a0bd81 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 18 Jul 2024 09:52:01 -0700 Subject: [PATCH 023/573] hwmon: (max16065) Fix overflows seen when writing limits Writing large limits resulted in overflows as reported by module tests. in0_lcrit: Suspected overflow: [max=5538, read 0, written 2147483647] in0_crit: Suspected overflow: [max=5538, read 0, written 2147483647] in0_min: Suspected overflow: [max=5538, read 0, written 2147483647] Fix the problem by clamping prior to multiplications and the use of DIV_ROUND_CLOSEST, and by using consistent variable types. Reviewed-by: Tzung-Bi Shih Fixes: f5bae2642e3d ("hwmon: Driver for MAX16065 System Manager and compatibles") Signed-off-by: Guenter Roeck --- drivers/hwmon/max16065.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/max16065.c b/drivers/hwmon/max16065.c index 7ce9a89f93a0..5b2a174c6bad 100644 --- a/drivers/hwmon/max16065.c +++ b/drivers/hwmon/max16065.c @@ -114,9 +114,10 @@ static inline int LIMIT_TO_MV(int limit, int range) return limit * range / 256; } -static inline int MV_TO_LIMIT(int mv, int range) +static inline int MV_TO_LIMIT(unsigned long mv, int range) { - return clamp_val(DIV_ROUND_CLOSEST(mv * 256, range), 0, 255); + mv = clamp_val(mv, 0, ULONG_MAX / 256); + return DIV_ROUND_CLOSEST(clamp_val(mv * 256, 0, range * 255), range); } static inline int ADC_TO_CURR(int adc, int gain) From 119abf7d1815f098f7f91ae7abc84324a19943d7 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 21 Jul 2024 06:41:17 -0700 Subject: [PATCH 024/573] hwmon: (max16065) Fix alarm attributes Chips reporting overcurrent alarms report it in the second alarm register. That means the second alarm register has to be read, even if the chip only supports 8 or fewer ADC channels. MAX16067 and MAX16068 report undervoltage and overvoltage alarms in separate registers. Fold register contents together to report both with the existing alarm attribute. This requires actually storing the chip type in struct max16065_data. Rename the variable 'chip' to match the variable name used in the probe function. Reviewed-by: Tzung-Bi Shih Fixes: f5bae2642e3d ("hwmon: Driver for MAX16065 System Manager and compatibles") Signed-off-by: Guenter Roeck --- drivers/hwmon/max16065.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/max16065.c b/drivers/hwmon/max16065.c index 5b2a174c6bad..0ccb5eb596fc 100644 --- a/drivers/hwmon/max16065.c +++ b/drivers/hwmon/max16065.c @@ -79,7 +79,7 @@ static const bool max16065_have_current[] = { }; struct max16065_data { - enum chips type; + enum chips chip; struct i2c_client *client; const struct attribute_group *groups[4]; struct mutex update_lock; @@ -162,10 +162,17 @@ static struct max16065_data *max16065_update_device(struct device *dev) MAX16065_CURR_SENSE); } - for (i = 0; i < DIV_ROUND_UP(data->num_adc, 8); i++) + for (i = 0; i < 2; i++) data->fault[i] = i2c_smbus_read_byte_data(client, MAX16065_FAULT(i)); + /* + * MAX16067 and MAX16068 have separate undervoltage and + * overvoltage alarm bits. Squash them together. + */ + if (data->chip == max16067 || data->chip == max16068) + data->fault[0] |= data->fault[1]; + data->last_updated = jiffies; data->valid = true; } @@ -514,6 +521,7 @@ static int max16065_probe(struct i2c_client *client) if (unlikely(!data)) return -ENOMEM; + data->chip = chip; data->client = client; mutex_init(&data->update_lock); From 351047fc5947a9549bae237104b800a89923a04d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 12 Jul 2024 13:06:30 -0700 Subject: [PATCH 025/573] hwmon: (max6697) Reorder include files Reorder include files to alphabetic order to improve maintainability. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max6697.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c index 20981f9443dd..9a2a21230c7d 100644 --- a/drivers/hwmon/max6697.c +++ b/drivers/hwmon/max6697.c @@ -6,16 +6,16 @@ * Copyright (c) 2011 David George */ -#include -#include -#include -#include -#include +#include #include #include -#include +#include +#include +#include +#include #include #include +#include #include From f81489a136ac2c18a7b9bb22cf37c0a6f6d58545 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 12 Jul 2024 13:57:13 -0700 Subject: [PATCH 026/573] hwmon: (max6697) Drop platform data support Platform data is not used anywhere in the upstram kernel. Drop support for it to simplify code maintenance. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max6697.c | 174 ++++++++++++-------------- include/linux/platform_data/max6697.h | 33 ----- 2 files changed, 81 insertions(+), 126 deletions(-) delete mode 100644 include/linux/platform_data/max6697.h diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c index 9a2a21230c7d..b9766533d876 100644 --- a/drivers/hwmon/max6697.c +++ b/drivers/hwmon/max6697.c @@ -17,8 +17,6 @@ #include #include -#include - enum chips { max6581, max6602, max6622, max6636, max6689, max6693, max6694, max6697, max6698, max6699 }; @@ -558,54 +556,97 @@ static const struct attribute_group max6697_group = { }; __ATTRIBUTE_GROUPS(max6697); -static void max6697_get_config_of(struct device_node *node, - struct max6697_platform_data *pdata) +static int max6697_config_of(struct max6697_data *data, struct i2c_client *client) { - int len; - const __be32 *prop; + const struct max6697_chip_data *chip = data->chip; + struct device_node *node = client->dev.of_node; + int ret, confreg; + int factor = 0; + u32 vals[2]; - pdata->smbus_timeout_disable = - of_property_read_bool(node, "smbus-timeout-disable"); - pdata->extended_range_enable = - of_property_read_bool(node, "extended-range-enable"); - pdata->beta_compensation = - of_property_read_bool(node, "beta-compensation-enable"); + confreg = 0; + if (of_property_read_bool(node, "smbus-timeout-disable") && + (chip->valid_conf & MAX6697_CONF_TIMEOUT)) { + confreg |= MAX6697_CONF_TIMEOUT; + } + if (of_property_read_bool(node, "extended-range-enable") && + (chip->valid_conf & MAX6581_CONF_EXTENDED)) { + confreg |= MAX6581_CONF_EXTENDED; + data->temp_offset = 64; + } + if (of_property_read_bool(node, "beta-compensation-enable") && + (chip->valid_conf & MAX6693_CONF_BETA)) { + confreg |= MAX6693_CONF_BETA; + } - prop = of_get_property(node, "alert-mask", &len); - if (prop && len == sizeof(u32)) - pdata->alert_mask = be32_to_cpu(prop[0]); - prop = of_get_property(node, "over-temperature-mask", &len); - if (prop && len == sizeof(u32)) - pdata->over_temperature_mask = be32_to_cpu(prop[0]); - prop = of_get_property(node, "resistance-cancellation", &len); - if (prop) { - if (len == sizeof(u32)) - pdata->resistance_cancellation = be32_to_cpu(prop[0]); - else - pdata->resistance_cancellation = 0xfe; - } - prop = of_get_property(node, "transistor-ideality", &len); - if (prop && len == 2 * sizeof(u32)) { - pdata->ideality_mask = be32_to_cpu(prop[0]); - pdata->ideality_value = be32_to_cpu(prop[1]); + if (of_property_read_u32(node, "alert-mask", vals)) + vals[0] = 0; + ret = i2c_smbus_write_byte_data(client, MAX6697_REG_ALERT_MASK, + MAX6697_ALERT_MAP_BITS(vals[0])); + if (ret) + return ret; + + if (of_property_read_u32(node, "over-temperature-mask", vals)) + vals[0] = 0; + ret = i2c_smbus_write_byte_data(client, MAX6697_REG_OVERT_MASK, + MAX6697_OVERT_MAP_BITS(vals[0])); + if (ret) + return ret; + + if (data->type != max6581) { + if (of_property_read_bool(node, "resistance-cancellation") && + chip->valid_conf & MAX6697_CONF_RESISTANCE) { + confreg |= MAX6697_CONF_RESISTANCE; + factor = 1; + } + } else { + if (of_property_read_u32(node, "resistance-cancellation", &vals[0])) { + if (of_property_read_bool(node, "resistance-cancellation")) + vals[0] = 0xfe; + else + vals[0] = 0; + } + + vals[0] &= 0xfe; + factor = hweight8(vals[0]); + ret = i2c_smbus_write_byte_data(client, MAX6581_REG_RESISTANCE, + vals[0] >> 1); + if (ret < 0) + return ret; + + if (of_property_read_u32_array(node, "transistor-ideality", vals, 2)) { + vals[0] = 0; + vals[1] = 0; + } + + ret = i2c_smbus_write_byte_data(client, MAX6581_REG_IDEALITY, + vals[1]); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(client, + MAX6581_REG_IDEALITY_SELECT, + (vals[0] & 0xfe) >> 1); + if (ret < 0) + return ret; } + ret = i2c_smbus_write_byte_data(client, MAX6697_REG_CONFIG, confreg); + if (ret < 0) + return ret; + return factor; } static int max6697_init_chip(struct max6697_data *data, struct i2c_client *client) { - struct max6697_platform_data *pdata = dev_get_platdata(&client->dev); - struct max6697_platform_data p; const struct max6697_chip_data *chip = data->chip; int factor = chip->channels; int ret, reg; /* - * Don't touch configuration if neither platform data nor OF - * configuration was specified. If that is the case, use the - * current chip configuration. + * Don't touch configuration if there is no devicetree configuration. + * If that is the case, use the current chip configuration. */ - if (!pdata && !client->dev.of_node) { + if (!client->dev.of_node) { reg = i2c_smbus_read_byte_data(client, MAX6697_REG_CONFIG); if (reg < 0) return reg; @@ -621,67 +662,14 @@ static int max6697_init_chip(struct max6697_data *data, if (reg & MAX6697_CONF_RESISTANCE) factor++; } - goto done; - } - - if (client->dev.of_node) { - memset(&p, 0, sizeof(p)); - max6697_get_config_of(client->dev.of_node, &p); - pdata = &p; - } - - reg = 0; - if (pdata->smbus_timeout_disable && - (chip->valid_conf & MAX6697_CONF_TIMEOUT)) { - reg |= MAX6697_CONF_TIMEOUT; - } - if (pdata->extended_range_enable && - (chip->valid_conf & MAX6581_CONF_EXTENDED)) { - reg |= MAX6581_CONF_EXTENDED; - data->temp_offset = 64; - } - if (pdata->resistance_cancellation && - (chip->valid_conf & MAX6697_CONF_RESISTANCE)) { - reg |= MAX6697_CONF_RESISTANCE; - factor++; - } - if (pdata->beta_compensation && - (chip->valid_conf & MAX6693_CONF_BETA)) { - reg |= MAX6693_CONF_BETA; - } - - ret = i2c_smbus_write_byte_data(client, MAX6697_REG_CONFIG, reg); - if (ret < 0) - return ret; - - ret = i2c_smbus_write_byte_data(client, MAX6697_REG_ALERT_MASK, - MAX6697_ALERT_MAP_BITS(pdata->alert_mask)); - if (ret < 0) - return ret; - - ret = i2c_smbus_write_byte_data(client, MAX6697_REG_OVERT_MASK, - MAX6697_OVERT_MAP_BITS(pdata->over_temperature_mask)); - if (ret < 0) - return ret; - - if (data->type == max6581) { - factor += hweight8(pdata->resistance_cancellation >> 1); - ret = i2c_smbus_write_byte_data(client, MAX6581_REG_RESISTANCE, - pdata->resistance_cancellation >> 1); - if (ret < 0) - return ret; - ret = i2c_smbus_write_byte_data(client, MAX6581_REG_IDEALITY, - pdata->ideality_value); - if (ret < 0) - return ret; - ret = i2c_smbus_write_byte_data(client, - MAX6581_REG_IDEALITY_SELECT, - pdata->ideality_mask >> 1); + data->update_interval = factor * MAX6697_CONV_TIME; + } else { + ret = max6697_config_of(data, client); if (ret < 0) return ret; + data->update_interval = (factor + ret) * MAX6697_CONV_TIME; } -done: - data->update_interval = factor * MAX6697_CONV_TIME; + return 0; } diff --git a/include/linux/platform_data/max6697.h b/include/linux/platform_data/max6697.h deleted file mode 100644 index 6fbb70005541..000000000000 --- a/include/linux/platform_data/max6697.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * max6697.h - * Copyright (c) 2012 Guenter Roeck - */ - -#ifndef MAX6697_H -#define MAX6697_H - -#include - -/* - * For all bit masks: - * bit 0: local temperature - * bit 1..7: remote temperatures - */ -struct max6697_platform_data { - bool smbus_timeout_disable; /* set to disable SMBus timeouts */ - bool extended_range_enable; /* set to enable extended temp range */ - bool beta_compensation; /* set to enable beta compensation */ - u8 alert_mask; /* set bit to 1 to disable alert */ - u8 over_temperature_mask; /* set bit to 1 to disable */ - u8 resistance_cancellation; /* set bit to 0 to disable - * bit mask for MAX6581, - * boolean for other chips - */ - u8 ideality_mask; /* set bit to 0 to disable */ - u8 ideality_value; /* transistor ideality as per - * MAX6581 datasheet - */ -}; - -#endif /* MAX6697_H */ From c02b25dcf64b07990a75292a83486b5348f3561e Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 12 Jul 2024 14:46:19 -0700 Subject: [PATCH 027/573] hwmon: (max6697) Use bit operations where possible Use bit operations to improve code maintainability. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max6697.c | 43 +++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c index b9766533d876..74bae3586fa6 100644 --- a/drivers/hwmon/max6697.c +++ b/drivers/hwmon/max6697.c @@ -6,6 +6,8 @@ * Copyright (c) 2011 David George */ +#include +#include #include #include #include @@ -32,20 +34,31 @@ static const u8 MAX6697_REG_CRIT[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 }; /* - * Map device tree / platform data register bit map to chip bit map. + * Map device tree / internal register bit map to chip bit map. * Applies to alert register and over-temperature register. */ + +#define MAX6697_EXTERNAL_MASK_DT GENMASK(7, 1) +#define MAX6697_LOCAL_MASK_DT BIT(0) +#define MAX6697_EXTERNAL_MASK_CHIP GENMASK(6, 0) +#define MAX6697_LOCAL_MASK_CHIP BIT(7) + +/* alert - local channel is in bit 6 */ #define MAX6697_ALERT_MAP_BITS(reg) ((((reg) & 0x7e) >> 1) | \ (((reg) & 0x01) << 6) | ((reg) & 0x80)) -#define MAX6697_OVERT_MAP_BITS(reg) (((reg) >> 1) | (((reg) & 0x01) << 7)) + +/* over-temperature - local channel is in bit 7 */ +#define MAX6697_OVERT_MAP_BITS(reg) \ + (FIELD_PREP(MAX6697_EXTERNAL_MASK_CHIP, FIELD_GET(MAX6697_EXTERNAL_MASK_DT, reg)) | \ + FIELD_PREP(MAX6697_LOCAL_MASK_CHIP, FIELD_GET(MAX6697_LOCAL_MASK_DT, reg))) #define MAX6697_REG_STAT(n) (0x44 + (n)) #define MAX6697_REG_CONFIG 0x41 -#define MAX6581_CONF_EXTENDED (1 << 1) -#define MAX6693_CONF_BETA (1 << 2) -#define MAX6697_CONF_RESISTANCE (1 << 3) -#define MAX6697_CONF_TIMEOUT (1 << 5) +#define MAX6581_CONF_EXTENDED BIT(1) +#define MAX6693_CONF_BETA BIT(2) +#define MAX6697_CONF_RESISTANCE BIT(3) +#define MAX6697_CONF_TIMEOUT BIT(5) #define MAX6697_REG_ALERT_MASK 0x42 #define MAX6697_REG_OVERT_MASK 0x43 @@ -193,7 +206,7 @@ static struct max6697_data *max6697_update_device(struct device *dev) goto abort; for (i = 0; i < data->chip->channels; i++) { - if (data->chip->have_ext & (1 << i)) { + if (data->chip->have_ext & BIT(i)) { val = i2c_smbus_read_byte_data(client, MAX6697_REG_TEMP_EXT[i]); if (unlikely(val < 0)) { @@ -217,7 +230,7 @@ static struct max6697_data *max6697_update_device(struct device *dev) } data->temp[i][MAX6697_TEMP_MAX] = val; - if (data->chip->have_crit & (1 << i)) { + if (data->chip->have_crit & BIT(i)) { val = i2c_smbus_read_byte_data(client, MAX6697_REG_CRIT[i]); if (unlikely(val < 0)) { @@ -291,7 +304,7 @@ static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, if (data->chip->alarm_map) index = data->chip->alarm_map[index]; - return sprintf(buf, "%u\n", (data->alarms >> index) & 0x1); + return sprintf(buf, "%u\n", !!(data->alarms & BIT(index))); } static ssize_t temp_store(struct device *dev, @@ -342,20 +355,20 @@ static ssize_t offset_store(struct device *dev, struct device_attribute *devattr ret = select; goto abort; } - channel_enabled = (select & (1 << (index - 1))); + channel_enabled = (select & BIT(index - 1)); temp = clamp_val(temp, MAX6581_OFFSET_MIN, MAX6581_OFFSET_MAX); val = DIV_ROUND_CLOSEST(temp, 250); /* disable the offset for channel if the new offset is 0 */ if (val == 0) { if (channel_enabled) ret = i2c_smbus_write_byte_data(data->client, MAX6581_REG_OFFSET_SELECT, - select & ~(1 << (index - 1))); + select & ~BIT(index - 1)); ret = ret < 0 ? ret : count; goto abort; } if (!channel_enabled) { ret = i2c_smbus_write_byte_data(data->client, MAX6581_REG_OFFSET_SELECT, - select | (1 << (index - 1))); + select | BIT(index - 1)); if (ret < 0) goto abort; } @@ -378,7 +391,7 @@ static ssize_t offset_show(struct device *dev, struct device_attribute *devattr, select = i2c_smbus_read_byte_data(data->client, MAX6581_REG_OFFSET_SELECT); if (select < 0) ret = select; - else if (select & (1 << (index - 1))) + else if (select & BIT(index - 1)) ret = i2c_smbus_read_byte_data(data->client, MAX6581_REG_OFFSET); else ret = 0; @@ -467,9 +480,9 @@ static umode_t max6697_is_visible(struct kobject *kobj, struct attribute *attr, if (channel >= chip->channels) return 0; - if ((nr == 3 || nr == 4) && !(chip->have_crit & (1 << channel))) + if ((nr == 3 || nr == 4) && !(chip->have_crit & BIT(channel))) return 0; - if (nr == 5 && !(chip->have_fault & (1 << channel))) + if (nr == 5 && !(chip->have_fault & BIT(channel))) return 0; /* offset reg is only supported on max6581 remote channels */ if (nr == 6) From 3a2a8cc3fe24ec48a3d222f5b9db68a3404ffab3 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 12 Jul 2024 15:45:38 -0700 Subject: [PATCH 028/573] hwmon: (max6697) Convert to use regmap Use regmap for register caching, and use regmap API for bit operations to simplify the code. This patch reduces object file size by approximately 10%. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max6697.c | 317 +++++++++++++++------------------------- 1 file changed, 121 insertions(+), 196 deletions(-) diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c index 74bae3586fa6..90cb203c66e2 100644 --- a/drivers/hwmon/max6697.c +++ b/drivers/hwmon/max6697.c @@ -13,10 +13,10 @@ #include #include #include -#include #include #include #include +#include #include enum chips { max6581, max6602, max6622, max6636, max6689, max6693, max6694, @@ -82,20 +82,15 @@ struct max6697_chip_data { }; struct max6697_data { - struct i2c_client *client; + struct regmap *regmap; enum chips type; const struct max6697_chip_data *chip; - int update_interval; /* in milli-seconds */ int temp_offset; /* in degrees C */ struct mutex update_lock; - unsigned long last_updated; /* In jiffies */ - bool valid; /* true if following fields are valid */ - /* 1x local and up to 7x remote */ - u8 temp[8][4]; /* [nr][0]=temp [1]=ext [2]=max [3]=crit */ #define MAX6697_TEMP_INPUT 0 #define MAX6697_TEMP_EXT 1 #define MAX6697_TEMP_MAX 2 @@ -189,88 +184,22 @@ static inline int max6581_offset_to_millic(int val) return sign_extend32(val, 7) * 250; } -static struct max6697_data *max6697_update_device(struct device *dev) -{ - struct max6697_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - struct max6697_data *ret = data; - int val; - int i; - u32 alarms; - - mutex_lock(&data->update_lock); - - if (data->valid && - !time_after(jiffies, data->last_updated - + msecs_to_jiffies(data->update_interval))) - goto abort; - - for (i = 0; i < data->chip->channels; i++) { - if (data->chip->have_ext & BIT(i)) { - val = i2c_smbus_read_byte_data(client, - MAX6697_REG_TEMP_EXT[i]); - if (unlikely(val < 0)) { - ret = ERR_PTR(val); - goto abort; - } - data->temp[i][MAX6697_TEMP_EXT] = val; - } - - val = i2c_smbus_read_byte_data(client, MAX6697_REG_TEMP[i]); - if (unlikely(val < 0)) { - ret = ERR_PTR(val); - goto abort; - } - data->temp[i][MAX6697_TEMP_INPUT] = val; - - val = i2c_smbus_read_byte_data(client, MAX6697_REG_MAX[i]); - if (unlikely(val < 0)) { - ret = ERR_PTR(val); - goto abort; - } - data->temp[i][MAX6697_TEMP_MAX] = val; - - if (data->chip->have_crit & BIT(i)) { - val = i2c_smbus_read_byte_data(client, - MAX6697_REG_CRIT[i]); - if (unlikely(val < 0)) { - ret = ERR_PTR(val); - goto abort; - } - data->temp[i][MAX6697_TEMP_CRIT] = val; - } - } - - alarms = 0; - for (i = 0; i < 3; i++) { - val = i2c_smbus_read_byte_data(client, MAX6697_REG_STAT(i)); - if (unlikely(val < 0)) { - ret = ERR_PTR(val); - goto abort; - } - alarms = (alarms << 8) | val; - } - data->alarms = alarms; - data->last_updated = jiffies; - data->valid = true; -abort: - mutex_unlock(&data->update_lock); - - return ret; -} - static ssize_t temp_input_show(struct device *dev, struct device_attribute *devattr, char *buf) { + struct max6697_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(devattr)->index; - struct max6697_data *data = max6697_update_device(dev); - int temp; + unsigned int regs[2] = { MAX6697_REG_TEMP[index], + MAX6697_REG_TEMP_EXT[index] }; + u8 regdata[2] = { }; + int temp, ret; - if (IS_ERR(data)) - return PTR_ERR(data); + ret = regmap_multi_reg_read(data->regmap, regs, regdata, + data->chip->have_ext & BIT(index) ? 2 : 1); + if (ret) + return ret; - temp = (data->temp[index][MAX6697_TEMP_INPUT] - data->temp_offset) << 3; - temp |= data->temp[index][MAX6697_TEMP_EXT] >> 5; + temp = ((regdata[0] - data->temp_offset) << 3) | (regdata[1] >> 5); return sprintf(buf, "%d\n", temp * 125); } @@ -278,33 +207,41 @@ static ssize_t temp_input_show(struct device *dev, static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, char *buf) { - int nr = to_sensor_dev_attr_2(devattr)->nr; + struct max6697_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr_2(devattr)->index; - struct max6697_data *data = max6697_update_device(dev); - int temp; + int nr = to_sensor_dev_attr_2(devattr)->nr; + unsigned int temp; + int reg, ret; - if (IS_ERR(data)) - return PTR_ERR(data); + if (index == MAX6697_TEMP_MAX) + reg = MAX6697_REG_MAX[nr]; + else + reg = MAX6697_REG_CRIT[nr]; - temp = data->temp[nr][index]; - temp -= data->temp_offset; + ret = regmap_read(data->regmap, reg, &temp); + if (ret) + return ret; - return sprintf(buf, "%d\n", temp * 1000); + return sprintf(buf, "%d\n", ((int)temp - data->temp_offset) * 1000); } static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct max6697_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; - struct max6697_data *data = max6697_update_device(dev); - - if (IS_ERR(data)) - return PTR_ERR(data); + unsigned int alarms; + int reg, ret; if (data->chip->alarm_map) index = data->chip->alarm_map[index]; - return sprintf(buf, "%u\n", !!(data->alarms & BIT(index))); + reg = MAX6697_REG_STAT(2 - (index / 8)); + ret = regmap_read(data->regmap, reg, &alarms); + if (ret) + return ret; + + return sprintf(buf, "%u\n", !!(alarms & BIT(index & 7))); } static ssize_t temp_store(struct device *dev, @@ -321,82 +258,63 @@ static ssize_t temp_store(struct device *dev, if (ret < 0) return ret; - mutex_lock(&data->update_lock); temp = clamp_val(temp, -1000000, 1000000); /* prevent underflow */ temp = DIV_ROUND_CLOSEST(temp, 1000) + data->temp_offset; temp = clamp_val(temp, 0, data->type == max6581 ? 255 : 127); - data->temp[nr][index] = temp; - ret = i2c_smbus_write_byte_data(data->client, - index == 2 ? MAX6697_REG_MAX[nr] - : MAX6697_REG_CRIT[nr], - temp); - mutex_unlock(&data->update_lock); + ret = regmap_write(data->regmap, + index == 2 ? MAX6697_REG_MAX[nr] + : MAX6697_REG_CRIT[nr], + temp); - return ret < 0 ? ret : count; + return ret ? : count; } static ssize_t offset_store(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { - int val, ret, index, select; - struct max6697_data *data; - bool channel_enabled; + struct max6697_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + struct regmap *regmap = data->regmap; long temp; + int ret; - index = to_sensor_dev_attr(devattr)->index; - data = dev_get_drvdata(dev); ret = kstrtol(buf, 10, &temp); if (ret < 0) return ret; mutex_lock(&data->update_lock); - select = i2c_smbus_read_byte_data(data->client, MAX6581_REG_OFFSET_SELECT); - if (select < 0) { - ret = select; - goto abort; - } - channel_enabled = (select & BIT(index - 1)); temp = clamp_val(temp, MAX6581_OFFSET_MIN, MAX6581_OFFSET_MAX); - val = DIV_ROUND_CLOSEST(temp, 250); - /* disable the offset for channel if the new offset is 0 */ - if (val == 0) { - if (channel_enabled) - ret = i2c_smbus_write_byte_data(data->client, MAX6581_REG_OFFSET_SELECT, - select & ~BIT(index - 1)); - ret = ret < 0 ? ret : count; - goto abort; + temp = DIV_ROUND_CLOSEST(temp, 250); + if (!temp) { /* disable this (and only this) channel */ + ret = regmap_clear_bits(regmap, MAX6581_REG_OFFSET_SELECT, BIT(index - 1)); + goto unlock; } - if (!channel_enabled) { - ret = i2c_smbus_write_byte_data(data->client, MAX6581_REG_OFFSET_SELECT, - select | BIT(index - 1)); - if (ret < 0) - goto abort; - } - ret = i2c_smbus_write_byte_data(data->client, MAX6581_REG_OFFSET, val); - ret = ret < 0 ? ret : count; - -abort: + /* enable channel, and update offset */ + ret = regmap_set_bits(regmap, MAX6581_REG_OFFSET_SELECT, BIT(index - 1)); + if (ret) + goto unlock; + ret = regmap_write(regmap, MAX6581_REG_OFFSET, temp); +unlock: mutex_unlock(&data->update_lock); - return ret; + return ret ? : count; } static ssize_t offset_show(struct device *dev, struct device_attribute *devattr, char *buf) { - struct max6697_data *data; - int select, ret, index; + unsigned int regs[2] = { MAX6581_REG_OFFSET_SELECT, MAX6581_REG_OFFSET }; + struct max6697_data *data = dev_get_drvdata(dev); + int index = to_sensor_dev_attr(devattr)->index; + u8 regdata[2]; + int ret; - index = to_sensor_dev_attr(devattr)->index; - data = dev_get_drvdata(dev); - mutex_lock(&data->update_lock); - select = i2c_smbus_read_byte_data(data->client, MAX6581_REG_OFFSET_SELECT); - if (select < 0) - ret = select; - else if (select & BIT(index - 1)) - ret = i2c_smbus_read_byte_data(data->client, MAX6581_REG_OFFSET); - else - ret = 0; - mutex_unlock(&data->update_lock); - return ret < 0 ? ret : sprintf(buf, "%d\n", max6581_offset_to_millic(ret)); + ret = regmap_multi_reg_read(data->regmap, regs, regdata, 2); + if (ret) + return ret; + + if (!(regdata[0] & BIT(index - 1))) + regdata[1] = 0; + + return sprintf(buf, "%d\n", max6581_offset_to_millic(regdata[1])); } static SENSOR_DEVICE_ATTR_RO(temp1_input, temp_input, 0); @@ -569,12 +487,11 @@ static const struct attribute_group max6697_group = { }; __ATTRIBUTE_GROUPS(max6697); -static int max6697_config_of(struct max6697_data *data, struct i2c_client *client) +static int max6697_config_of(struct device_node *node, struct max6697_data *data) { const struct max6697_chip_data *chip = data->chip; - struct device_node *node = client->dev.of_node; + struct regmap *regmap = data->regmap; int ret, confreg; - int factor = 0; u32 vals[2]; confreg = 0; @@ -594,15 +511,15 @@ static int max6697_config_of(struct max6697_data *data, struct i2c_client *clien if (of_property_read_u32(node, "alert-mask", vals)) vals[0] = 0; - ret = i2c_smbus_write_byte_data(client, MAX6697_REG_ALERT_MASK, - MAX6697_ALERT_MAP_BITS(vals[0])); + ret = regmap_write(regmap, MAX6697_REG_ALERT_MASK, + MAX6697_ALERT_MAP_BITS(vals[0])); if (ret) return ret; if (of_property_read_u32(node, "over-temperature-mask", vals)) vals[0] = 0; - ret = i2c_smbus_write_byte_data(client, MAX6697_REG_OVERT_MASK, - MAX6697_OVERT_MAP_BITS(vals[0])); + ret = regmap_write(regmap, MAX6697_REG_OVERT_MASK, + MAX6697_OVERT_MAP_BITS(vals[0])); if (ret) return ret; @@ -610,7 +527,6 @@ static int max6697_config_of(struct max6697_data *data, struct i2c_client *clien if (of_property_read_bool(node, "resistance-cancellation") && chip->valid_conf & MAX6697_CONF_RESISTANCE) { confreg |= MAX6697_CONF_RESISTANCE; - factor = 1; } } else { if (of_property_read_u32(node, "resistance-cancellation", &vals[0])) { @@ -621,9 +537,7 @@ static int max6697_config_of(struct max6697_data *data, struct i2c_client *clien } vals[0] &= 0xfe; - factor = hweight8(vals[0]); - ret = i2c_smbus_write_byte_data(client, MAX6581_REG_RESISTANCE, - vals[0] >> 1); + ret = regmap_write(regmap, MAX6581_REG_RESISTANCE, vals[0] >> 1); if (ret < 0) return ret; @@ -632,81 +546,92 @@ static int max6697_config_of(struct max6697_data *data, struct i2c_client *clien vals[1] = 0; } - ret = i2c_smbus_write_byte_data(client, MAX6581_REG_IDEALITY, - vals[1]); + ret = regmap_write(regmap, MAX6581_REG_IDEALITY, vals[1]); if (ret < 0) return ret; - ret = i2c_smbus_write_byte_data(client, - MAX6581_REG_IDEALITY_SELECT, - (vals[0] & 0xfe) >> 1); + ret = regmap_write(regmap, MAX6581_REG_IDEALITY_SELECT, + (vals[0] & 0xfe) >> 1); if (ret < 0) return ret; } - ret = i2c_smbus_write_byte_data(client, MAX6697_REG_CONFIG, confreg); - if (ret < 0) - return ret; - return factor; + return regmap_write(regmap, MAX6697_REG_CONFIG, confreg); } -static int max6697_init_chip(struct max6697_data *data, - struct i2c_client *client) +static int max6697_init_chip(struct device_node *np, struct max6697_data *data) { - const struct max6697_chip_data *chip = data->chip; - int factor = chip->channels; - int ret, reg; + unsigned int reg; + int ret; /* * Don't touch configuration if there is no devicetree configuration. * If that is the case, use the current chip configuration. */ - if (!client->dev.of_node) { - reg = i2c_smbus_read_byte_data(client, MAX6697_REG_CONFIG); - if (reg < 0) - return reg; + if (!np) { + struct regmap *regmap = data->regmap; + + ret = regmap_read(regmap, MAX6697_REG_CONFIG, ®); + if (ret < 0) + return ret; if (data->type == max6581) { if (reg & MAX6581_CONF_EXTENDED) data->temp_offset = 64; - reg = i2c_smbus_read_byte_data(client, - MAX6581_REG_RESISTANCE); - if (reg < 0) - return reg; - factor += hweight8(reg); - } else { - if (reg & MAX6697_CONF_RESISTANCE) - factor++; + ret = regmap_read(regmap, MAX6581_REG_RESISTANCE, ®); } - data->update_interval = factor * MAX6697_CONV_TIME; } else { - ret = max6697_config_of(data, client); - if (ret < 0) - return ret; - data->update_interval = (factor + ret) * MAX6697_CONV_TIME; + ret = max6697_config_of(np, data); } - return 0; + return ret; } +static bool max6697_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x00 ... 0x09: /* temperature high bytes */ + case 0x44 ... 0x47: /* status */ + case 0x51 ... 0x58: /* temperature low bytes */ + return true; + default: + return false; + } +} + +static bool max6697_writeable_reg(struct device *dev, unsigned int reg) +{ + return reg != 0x0a && reg != 0x0f && !max6697_volatile_reg(dev, reg); +} + +static const struct regmap_config max6697_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x58, + .writeable_reg = max6697_writeable_reg, + .volatile_reg = max6697_volatile_reg, + .cache_type = REGCACHE_MAPLE, +}; + static int max6697_probe(struct i2c_client *client) { - struct i2c_adapter *adapter = client->adapter; struct device *dev = &client->dev; struct max6697_data *data; struct device *hwmon_dev; + struct regmap *regmap; int err; - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) - return -ENODEV; + regmap = regmap_init_i2c(client, &max6697_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); data = devm_kzalloc(dev, sizeof(struct max6697_data), GFP_KERNEL); if (!data) return -ENOMEM; + data->regmap = regmap; data->type = (uintptr_t)i2c_get_match_data(client); data->chip = &max6697_chip_data[data->type]; - data->client = client; mutex_init(&data->update_lock); - err = max6697_init_chip(data, client); + err = max6697_init_chip(client->dev.of_node, data); if (err) return err; From 678c2a4582023a093d0c20def5ead7dd93f9004a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 13 Jul 2024 07:44:20 -0700 Subject: [PATCH 029/573] hwmon: (max6697) Convert to with_info hwmon API Convert to with_info hwmon API to simplify the code and reduce its size. This patch reduces object file size by approximately 25%. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max6697.c | 464 +++++++++++++++------------------------- 1 file changed, 173 insertions(+), 291 deletions(-) diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c index 90cb203c66e2..bfbaf1b64c1e 100644 --- a/drivers/hwmon/max6697.c +++ b/drivers/hwmon/max6697.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -52,7 +51,9 @@ static const u8 MAX6697_REG_CRIT[] = { (FIELD_PREP(MAX6697_EXTERNAL_MASK_CHIP, FIELD_GET(MAX6697_EXTERNAL_MASK_DT, reg)) | \ FIELD_PREP(MAX6697_LOCAL_MASK_CHIP, FIELD_GET(MAX6697_LOCAL_MASK_DT, reg))) -#define MAX6697_REG_STAT(n) (0x44 + (n)) +#define MAX6697_REG_STAT_ALARM 0x44 +#define MAX6697_REG_STAT_CRIT 0x45 +#define MAX6697_REG_STAT_FAULT 0x46 #define MAX6697_REG_CONFIG 0x41 #define MAX6581_CONF_EXTENDED BIT(1) @@ -78,7 +79,6 @@ struct max6697_chip_data { u32 have_crit; u32 have_fault; u8 valid_conf; - const u8 *alarm_map; }; struct max6697_data { @@ -98,11 +98,6 @@ struct max6697_data { u32 alarms; }; -/* Diode fault status bits on MAX6581 are right shifted by one bit */ -static const u8 max6581_alarm_map[] = { - 0, 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23 }; - static const struct max6697_chip_data max6697_chip_data[] = { [max6581] = { .channels = 8, @@ -110,7 +105,6 @@ static const struct max6697_chip_data max6697_chip_data[] = { .have_ext = 0x7f, .have_fault = 0xfe, .valid_conf = MAX6581_CONF_EXTENDED | MAX6697_CONF_TIMEOUT, - .alarm_map = max6581_alarm_map, }, [max6602] = { .channels = 5, @@ -179,313 +173,202 @@ static const struct max6697_chip_data max6697_chip_data[] = { }, }; -static inline int max6581_offset_to_millic(int val) -{ - return sign_extend32(val, 7) * 250; -} - -static ssize_t temp_input_show(struct device *dev, - struct device_attribute *devattr, char *buf) +static int max6697_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) { + unsigned int offset_regs[2] = { MAX6581_REG_OFFSET_SELECT, MAX6581_REG_OFFSET }; + unsigned int temp_regs[2] = { MAX6697_REG_TEMP[channel], + MAX6697_REG_TEMP_EXT[channel] }; struct max6697_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(devattr)->index; - unsigned int regs[2] = { MAX6697_REG_TEMP[index], - MAX6697_REG_TEMP_EXT[index] }; - u8 regdata[2] = { }; - int temp, ret; - - ret = regmap_multi_reg_read(data->regmap, regs, regdata, - data->chip->have_ext & BIT(index) ? 2 : 1); - if (ret) - return ret; - - temp = ((regdata[0] - data->temp_offset) << 3) | (regdata[1] >> 5); - - return sprintf(buf, "%d\n", temp * 125); -} - -static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct max6697_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr_2(devattr)->index; - int nr = to_sensor_dev_attr_2(devattr)->nr; - unsigned int temp; - int reg, ret; - - if (index == MAX6697_TEMP_MAX) - reg = MAX6697_REG_MAX[nr]; - else - reg = MAX6697_REG_CRIT[nr]; - - ret = regmap_read(data->regmap, reg, &temp); - if (ret) - return ret; - - return sprintf(buf, "%d\n", ((int)temp - data->temp_offset) * 1000); -} - -static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct max6697_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(attr)->index; - unsigned int alarms; - int reg, ret; - - if (data->chip->alarm_map) - index = data->chip->alarm_map[index]; - - reg = MAX6697_REG_STAT(2 - (index / 8)); - ret = regmap_read(data->regmap, reg, &alarms); - if (ret) - return ret; - - return sprintf(buf, "%u\n", !!(alarms & BIT(index & 7))); -} - -static ssize_t temp_store(struct device *dev, - struct device_attribute *devattr, const char *buf, - size_t count) -{ - int nr = to_sensor_dev_attr_2(devattr)->nr; - int index = to_sensor_dev_attr_2(devattr)->index; - struct max6697_data *data = dev_get_drvdata(dev); - long temp; - int ret; - - ret = kstrtol(buf, 10, &temp); - if (ret < 0) - return ret; - - temp = clamp_val(temp, -1000000, 1000000); /* prevent underflow */ - temp = DIV_ROUND_CLOSEST(temp, 1000) + data->temp_offset; - temp = clamp_val(temp, 0, data->type == max6581 ? 255 : 127); - ret = regmap_write(data->regmap, - index == 2 ? MAX6697_REG_MAX[nr] - : MAX6697_REG_CRIT[nr], - temp); - - return ret ? : count; -} - -static ssize_t offset_store(struct device *dev, struct device_attribute *devattr, const char *buf, - size_t count) -{ - struct max6697_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(devattr)->index; struct regmap *regmap = data->regmap; - long temp; + u8 regdata[2] = { }; + u32 regval; int ret; - ret = kstrtol(buf, 10, &temp); - if (ret < 0) - return ret; + switch (attr) { + case hwmon_temp_input: + ret = regmap_multi_reg_read(regmap, temp_regs, regdata, + data->chip->have_ext & BIT(channel) ? 2 : 1); + if (ret) + return ret; + *val = (((regdata[0] - data->temp_offset) << 3) | (regdata[1] >> 5)) * 125; + break; + case hwmon_temp_max: + ret = regmap_read(regmap, MAX6697_REG_MAX[channel], ®val); + if (ret) + return ret; + *val = ((int)regval - data->temp_offset) * 1000; + break; + case hwmon_temp_crit: + ret = regmap_read(regmap, MAX6697_REG_CRIT[channel], ®val); + if (ret) + return ret; + *val = ((int)regval - data->temp_offset) * 1000; + break; + case hwmon_temp_offset: + ret = regmap_multi_reg_read(regmap, offset_regs, regdata, 2); + if (ret) + return ret; - mutex_lock(&data->update_lock); - temp = clamp_val(temp, MAX6581_OFFSET_MIN, MAX6581_OFFSET_MAX); - temp = DIV_ROUND_CLOSEST(temp, 250); - if (!temp) { /* disable this (and only this) channel */ - ret = regmap_clear_bits(regmap, MAX6581_REG_OFFSET_SELECT, BIT(index - 1)); - goto unlock; + if (!(regdata[0] & BIT(channel - 1))) + regdata[1] = 0; + + *val = sign_extend32(regdata[1], 7) * 250; + break; + case hwmon_temp_fault: + ret = regmap_read(regmap, MAX6697_REG_STAT_FAULT, ®val); + if (ret) + return ret; + if (data->type == max6581) + *val = !!(regval & BIT(channel - 1)); + else + *val = !!(regval & BIT(channel)); + break; + case hwmon_temp_crit_alarm: + ret = regmap_read(regmap, MAX6697_REG_STAT_CRIT, ®val); + if (ret) + return ret; + *val = !!(regval & BIT(channel ? channel - 1 : 7)); + break; + case hwmon_temp_max_alarm: + ret = regmap_read(regmap, MAX6697_REG_STAT_ALARM, ®val); + if (ret) + return ret; + switch (channel) { + case 0: + *val = !!(regval & BIT(6)); + break; + case 7: + *val = !!(regval & BIT(7)); + break; + default: + *val = !!(regval & BIT(channel - 1)); + break; + } + break; + default: + return -EOPNOTSUPP; } - /* enable channel, and update offset */ - ret = regmap_set_bits(regmap, MAX6581_REG_OFFSET_SELECT, BIT(index - 1)); - if (ret) - goto unlock; - ret = regmap_write(regmap, MAX6581_REG_OFFSET, temp); -unlock: - mutex_unlock(&data->update_lock); - return ret ? : count; + return 0; } -static ssize_t offset_show(struct device *dev, struct device_attribute *devattr, char *buf) +static int max6697_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) { - unsigned int regs[2] = { MAX6581_REG_OFFSET_SELECT, MAX6581_REG_OFFSET }; struct max6697_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(devattr)->index; - u8 regdata[2]; + struct regmap *regmap = data->regmap; int ret; - ret = regmap_multi_reg_read(data->regmap, regs, regdata, 2); - if (ret) + switch (attr) { + case hwmon_temp_max: + val = clamp_val(val, -1000000, 1000000); /* prevent underflow */ + val = DIV_ROUND_CLOSEST(val, 1000) + data->temp_offset; + val = clamp_val(val, 0, data->type == max6581 ? 255 : 127); + return regmap_write(regmap, MAX6697_REG_MAX[channel], val); + case hwmon_temp_crit: + val = clamp_val(val, -1000000, 1000000); /* prevent underflow */ + val = DIV_ROUND_CLOSEST(val, 1000) + data->temp_offset; + val = clamp_val(val, 0, data->type == max6581 ? 255 : 127); + return regmap_write(regmap, MAX6697_REG_CRIT[channel], val); + case hwmon_temp_offset: + mutex_lock(&data->update_lock); + val = clamp_val(val, MAX6581_OFFSET_MIN, MAX6581_OFFSET_MAX); + val = DIV_ROUND_CLOSEST(val, 250); + if (!val) { /* disable this (and only this) channel */ + ret = regmap_clear_bits(regmap, MAX6581_REG_OFFSET_SELECT, + BIT(channel - 1)); + } else { + /* enable channel and update offset */ + ret = regmap_set_bits(regmap, MAX6581_REG_OFFSET_SELECT, + BIT(channel - 1)); + if (ret) + goto unlock; + ret = regmap_write(regmap, MAX6581_REG_OFFSET, val); + } +unlock: + mutex_unlock(&data->update_lock); return ret; - - if (!(regdata[0] & BIT(index - 1))) - regdata[1] = 0; - - return sprintf(buf, "%d\n", max6581_offset_to_millic(regdata[1])); + default: + return -EOPNOTSUPP; + } } -static SENSOR_DEVICE_ATTR_RO(temp1_input, temp_input, 0); -static SENSOR_DEVICE_ATTR_2_RW(temp1_max, temp, 0, MAX6697_TEMP_MAX); -static SENSOR_DEVICE_ATTR_2_RW(temp1_crit, temp, 0, MAX6697_TEMP_CRIT); - -static SENSOR_DEVICE_ATTR_RO(temp2_input, temp_input, 1); -static SENSOR_DEVICE_ATTR_2_RW(temp2_max, temp, 1, MAX6697_TEMP_MAX); -static SENSOR_DEVICE_ATTR_2_RW(temp2_crit, temp, 1, MAX6697_TEMP_CRIT); - -static SENSOR_DEVICE_ATTR_RO(temp3_input, temp_input, 2); -static SENSOR_DEVICE_ATTR_2_RW(temp3_max, temp, 2, MAX6697_TEMP_MAX); -static SENSOR_DEVICE_ATTR_2_RW(temp3_crit, temp, 2, MAX6697_TEMP_CRIT); - -static SENSOR_DEVICE_ATTR_RO(temp4_input, temp_input, 3); -static SENSOR_DEVICE_ATTR_2_RW(temp4_max, temp, 3, MAX6697_TEMP_MAX); -static SENSOR_DEVICE_ATTR_2_RW(temp4_crit, temp, 3, MAX6697_TEMP_CRIT); - -static SENSOR_DEVICE_ATTR_RO(temp5_input, temp_input, 4); -static SENSOR_DEVICE_ATTR_2_RW(temp5_max, temp, 4, MAX6697_TEMP_MAX); -static SENSOR_DEVICE_ATTR_2_RW(temp5_crit, temp, 4, MAX6697_TEMP_CRIT); - -static SENSOR_DEVICE_ATTR_RO(temp6_input, temp_input, 5); -static SENSOR_DEVICE_ATTR_2_RW(temp6_max, temp, 5, MAX6697_TEMP_MAX); -static SENSOR_DEVICE_ATTR_2_RW(temp6_crit, temp, 5, MAX6697_TEMP_CRIT); - -static SENSOR_DEVICE_ATTR_RO(temp7_input, temp_input, 6); -static SENSOR_DEVICE_ATTR_2_RW(temp7_max, temp, 6, MAX6697_TEMP_MAX); -static SENSOR_DEVICE_ATTR_2_RW(temp7_crit, temp, 6, MAX6697_TEMP_CRIT); - -static SENSOR_DEVICE_ATTR_RO(temp8_input, temp_input, 7); -static SENSOR_DEVICE_ATTR_2_RW(temp8_max, temp, 7, MAX6697_TEMP_MAX); -static SENSOR_DEVICE_ATTR_2_RW(temp8_crit, temp, 7, MAX6697_TEMP_CRIT); - -static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 22); -static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, alarm, 16); -static SENSOR_DEVICE_ATTR_RO(temp3_max_alarm, alarm, 17); -static SENSOR_DEVICE_ATTR_RO(temp4_max_alarm, alarm, 18); -static SENSOR_DEVICE_ATTR_RO(temp5_max_alarm, alarm, 19); -static SENSOR_DEVICE_ATTR_RO(temp6_max_alarm, alarm, 20); -static SENSOR_DEVICE_ATTR_RO(temp7_max_alarm, alarm, 21); -static SENSOR_DEVICE_ATTR_RO(temp8_max_alarm, alarm, 23); - -static SENSOR_DEVICE_ATTR_RO(temp1_crit_alarm, alarm, 15); -static SENSOR_DEVICE_ATTR_RO(temp2_crit_alarm, alarm, 8); -static SENSOR_DEVICE_ATTR_RO(temp3_crit_alarm, alarm, 9); -static SENSOR_DEVICE_ATTR_RO(temp4_crit_alarm, alarm, 10); -static SENSOR_DEVICE_ATTR_RO(temp5_crit_alarm, alarm, 11); -static SENSOR_DEVICE_ATTR_RO(temp6_crit_alarm, alarm, 12); -static SENSOR_DEVICE_ATTR_RO(temp7_crit_alarm, alarm, 13); -static SENSOR_DEVICE_ATTR_RO(temp8_crit_alarm, alarm, 14); - -static SENSOR_DEVICE_ATTR_RO(temp2_fault, alarm, 1); -static SENSOR_DEVICE_ATTR_RO(temp3_fault, alarm, 2); -static SENSOR_DEVICE_ATTR_RO(temp4_fault, alarm, 3); -static SENSOR_DEVICE_ATTR_RO(temp5_fault, alarm, 4); -static SENSOR_DEVICE_ATTR_RO(temp6_fault, alarm, 5); -static SENSOR_DEVICE_ATTR_RO(temp7_fault, alarm, 6); -static SENSOR_DEVICE_ATTR_RO(temp8_fault, alarm, 7); - -/* There is no offset for local temperature so starting from temp2 */ -static SENSOR_DEVICE_ATTR_RW(temp2_offset, offset, 1); -static SENSOR_DEVICE_ATTR_RW(temp3_offset, offset, 2); -static SENSOR_DEVICE_ATTR_RW(temp4_offset, offset, 3); -static SENSOR_DEVICE_ATTR_RW(temp5_offset, offset, 4); -static SENSOR_DEVICE_ATTR_RW(temp6_offset, offset, 5); -static SENSOR_DEVICE_ATTR_RW(temp7_offset, offset, 6); -static SENSOR_DEVICE_ATTR_RW(temp8_offset, offset, 7); - -static DEVICE_ATTR(dummy, 0, NULL, NULL); - -static umode_t max6697_is_visible(struct kobject *kobj, struct attribute *attr, - int index) +static umode_t max6697_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) { - struct device *dev = kobj_to_dev(kobj); - struct max6697_data *data = dev_get_drvdata(dev); + const struct max6697_data *data = _data; const struct max6697_chip_data *chip = data->chip; - int channel = index / 7; /* channel number */ - int nr = index % 7; /* attribute index within channel */ if (channel >= chip->channels) return 0; - if ((nr == 3 || nr == 4) && !(chip->have_crit & BIT(channel))) - return 0; - if (nr == 5 && !(chip->have_fault & BIT(channel))) - return 0; - /* offset reg is only supported on max6581 remote channels */ - if (nr == 6) - if (data->type != max6581 || channel == 0) - return 0; - - return attr->mode; + switch (attr) { + case hwmon_temp_max: + return 0644; + case hwmon_temp_input: + case hwmon_temp_max_alarm: + return 0444; + case hwmon_temp_crit: + if (chip->have_crit & BIT(channel)) + return 0644; + break; + case hwmon_temp_crit_alarm: + if (chip->have_crit & BIT(channel)) + return 0444; + break; + case hwmon_temp_fault: + if (chip->have_fault & BIT(channel)) + return 0444; + break; + case hwmon_temp_offset: + if (data->type == max6581 && channel) + return 0644; + break; + default: + break; + } + return 0; } -/* - * max6697_is_visible uses the index into the following array to determine - * if attributes should be created or not. Any change in order or content - * must be matched in max6697_is_visible. - */ -static struct attribute *max6697_attributes[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_crit.dev_attr.attr, - &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, - &dev_attr_dummy.attr, - &dev_attr_dummy.attr, - - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_crit.dev_attr.attr, - &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_fault.dev_attr.attr, - &sensor_dev_attr_temp2_offset.dev_attr.attr, - - &sensor_dev_attr_temp3_input.dev_attr.attr, - &sensor_dev_attr_temp3_max.dev_attr.attr, - &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_crit.dev_attr.attr, - &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_fault.dev_attr.attr, - &sensor_dev_attr_temp3_offset.dev_attr.attr, - - &sensor_dev_attr_temp4_input.dev_attr.attr, - &sensor_dev_attr_temp4_max.dev_attr.attr, - &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp4_crit.dev_attr.attr, - &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp4_fault.dev_attr.attr, - &sensor_dev_attr_temp4_offset.dev_attr.attr, - - &sensor_dev_attr_temp5_input.dev_attr.attr, - &sensor_dev_attr_temp5_max.dev_attr.attr, - &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp5_crit.dev_attr.attr, - &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp5_fault.dev_attr.attr, - &sensor_dev_attr_temp5_offset.dev_attr.attr, - - &sensor_dev_attr_temp6_input.dev_attr.attr, - &sensor_dev_attr_temp6_max.dev_attr.attr, - &sensor_dev_attr_temp6_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp6_crit.dev_attr.attr, - &sensor_dev_attr_temp6_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp6_fault.dev_attr.attr, - &sensor_dev_attr_temp6_offset.dev_attr.attr, - - &sensor_dev_attr_temp7_input.dev_attr.attr, - &sensor_dev_attr_temp7_max.dev_attr.attr, - &sensor_dev_attr_temp7_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp7_crit.dev_attr.attr, - &sensor_dev_attr_temp7_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp7_fault.dev_attr.attr, - &sensor_dev_attr_temp7_offset.dev_attr.attr, - - &sensor_dev_attr_temp8_input.dev_attr.attr, - &sensor_dev_attr_temp8_max.dev_attr.attr, - &sensor_dev_attr_temp8_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp8_crit.dev_attr.attr, - &sensor_dev_attr_temp8_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp8_fault.dev_attr.attr, - &sensor_dev_attr_temp8_offset.dev_attr.attr, +/* Return 0 if detection is successful, -ENODEV otherwise */ +static const struct hwmon_channel_info * const max6697_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_FAULT | HWMON_T_OFFSET, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_FAULT | HWMON_T_OFFSET, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_FAULT | HWMON_T_OFFSET, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_FAULT | HWMON_T_OFFSET, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_FAULT | HWMON_T_OFFSET, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_FAULT | HWMON_T_OFFSET, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_FAULT | HWMON_T_OFFSET), NULL }; -static const struct attribute_group max6697_group = { - .attrs = max6697_attributes, .is_visible = max6697_is_visible, +static const struct hwmon_ops max6697_hwmon_ops = { + .is_visible = max6697_is_visible, + .read = max6697_read, + .write = max6697_write, +}; + +static const struct hwmon_chip_info max6697_chip_info = { + .ops = &max6697_hwmon_ops, + .info = max6697_info, }; -__ATTRIBUTE_GROUPS(max6697); static int max6697_config_of(struct device_node *node, struct max6697_data *data) { @@ -635,9 +518,8 @@ static int max6697_probe(struct i2c_client *client) if (err) return err; - hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, - data, - max6697_groups); + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &max6697_chip_info, NULL); return PTR_ERR_OR_ZERO(hwmon_dev); } From 1e56afb1e648556111b51561cfa1940077a251c5 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 13 Jul 2024 14:20:34 -0700 Subject: [PATCH 030/573] hwmon: (max6697) Add support for tempX_min and tempX_min_alarm MAX6581 supports setting the minimum temperature as well as minimum temperature alarms. Add support for it. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max6697.c | 59 +++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c index bfbaf1b64c1e..0c34c0c81232 100644 --- a/drivers/hwmon/max6697.c +++ b/drivers/hwmon/max6697.c @@ -32,6 +32,7 @@ static const u8 MAX6697_REG_MAX[] = { static const u8 MAX6697_REG_CRIT[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 }; +#define MAX6697_REG_MIN 0x30 /* * Map device tree / internal register bit map to chip bit map. * Applies to alert register and over-temperature register. @@ -54,6 +55,7 @@ static const u8 MAX6697_REG_CRIT[] = { #define MAX6697_REG_STAT_ALARM 0x44 #define MAX6697_REG_STAT_CRIT 0x45 #define MAX6697_REG_STAT_FAULT 0x46 +#define MAX6697_REG_STAT_MIN_ALARM 0x47 #define MAX6697_REG_CONFIG 0x41 #define MAX6581_CONF_EXTENDED BIT(1) @@ -173,6 +175,18 @@ static const struct max6697_chip_data max6697_chip_data[] = { }, }; +static int max6697_alarm_channel_map(int channel) +{ + switch (channel) { + case 0: + return 6; + case 7: + return 7; + default: + return channel - 1; + } +} + static int max6697_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { @@ -205,6 +219,12 @@ static int max6697_read(struct device *dev, enum hwmon_sensor_types type, return ret; *val = ((int)regval - data->temp_offset) * 1000; break; + case hwmon_temp_min: + ret = regmap_read(regmap, MAX6697_REG_MIN, ®val); + if (ret) + return ret; + *val = ((int)regval - data->temp_offset) * 1000; + break; case hwmon_temp_offset: ret = regmap_multi_reg_read(regmap, offset_regs, regdata, 2); if (ret) @@ -234,17 +254,13 @@ static int max6697_read(struct device *dev, enum hwmon_sensor_types type, ret = regmap_read(regmap, MAX6697_REG_STAT_ALARM, ®val); if (ret) return ret; - switch (channel) { - case 0: - *val = !!(regval & BIT(6)); - break; - case 7: - *val = !!(regval & BIT(7)); - break; - default: - *val = !!(regval & BIT(channel - 1)); - break; - } + *val = !!(regval & BIT(max6697_alarm_channel_map(channel))); + break; + case hwmon_temp_min_alarm: + ret = regmap_read(regmap, MAX6697_REG_STAT_MIN_ALARM, ®val); + if (ret) + return ret; + *val = !!(regval & BIT(max6697_alarm_channel_map(channel))); break; default: return -EOPNOTSUPP; @@ -270,6 +286,11 @@ static int max6697_write(struct device *dev, enum hwmon_sensor_types type, val = DIV_ROUND_CLOSEST(val, 1000) + data->temp_offset; val = clamp_val(val, 0, data->type == max6581 ? 255 : 127); return regmap_write(regmap, MAX6697_REG_CRIT[channel], val); + case hwmon_temp_min: + val = clamp_val(val, -1000000, 1000000); /* prevent underflow */ + val = DIV_ROUND_CLOSEST(val, 1000) + data->temp_offset; + val = clamp_val(val, 0, 255); + return regmap_write(regmap, MAX6697_REG_MIN, val); case hwmon_temp_offset: mutex_lock(&data->update_lock); val = clamp_val(val, MAX6581_OFFSET_MIN, MAX6581_OFFSET_MAX); @@ -308,6 +329,14 @@ static umode_t max6697_is_visible(const void *_data, enum hwmon_sensor_types typ case hwmon_temp_input: case hwmon_temp_max_alarm: return 0444; + case hwmon_temp_min: + if (data->type == max6581) + return channel ? 0444 : 0644; + break; + case hwmon_temp_min_alarm: + if (data->type == max6581) + return 0444; + break; case hwmon_temp_crit: if (chip->have_crit & BIT(channel)) return 0644; @@ -334,27 +363,35 @@ static umode_t max6697_is_visible(const void *_data, enum hwmon_sensor_types typ static const struct hwmon_channel_info * const max6697_info[] = { HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MIN | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_MIN | HWMON_T_MIN_ALARM | HWMON_T_FAULT | HWMON_T_OFFSET, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_MIN | HWMON_T_MIN_ALARM | HWMON_T_FAULT | HWMON_T_OFFSET, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_MIN | HWMON_T_MIN_ALARM | HWMON_T_FAULT | HWMON_T_OFFSET, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_MIN | HWMON_T_MIN_ALARM | HWMON_T_FAULT | HWMON_T_OFFSET, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_MIN | HWMON_T_MIN_ALARM | HWMON_T_FAULT | HWMON_T_OFFSET, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_MIN | HWMON_T_MIN_ALARM | HWMON_T_FAULT | HWMON_T_OFFSET, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_MIN | HWMON_T_MIN_ALARM | HWMON_T_FAULT | HWMON_T_OFFSET), NULL }; From 6b1bb53579b0a46c7a2467fffcd23e54e844ff41 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Tue, 23 Jul 2024 10:17:35 +1200 Subject: [PATCH 031/573] dt-bindings: hwmon: Add adt7475 fan/pwm properties Add fan child nodes that allow describing the connections for the ADT7475 to the fans it controls. This also allows setting some initial values for the pwm duty cycle and frequency. Signed-off-by: Chris Packham Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20240722221737.3407958-2-chris.packham@alliedtelesis.co.nz Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/adt7475.yaml | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/hwmon/adt7475.yaml b/Documentation/devicetree/bindings/hwmon/adt7475.yaml index 051c976ab711..df2b5b889e4d 100644 --- a/Documentation/devicetree/bindings/hwmon/adt7475.yaml +++ b/Documentation/devicetree/bindings/hwmon/adt7475.yaml @@ -51,6 +51,24 @@ properties: enum: [0, 1] default: 1 + "#pwm-cells": + const: 4 + description: | + Number of cells in a PWM specifier. + - 0: The PWM channel + - 1: The PWM period in nanoseconds + - 90909091 (11 Hz) + - 71428571 (14 Hz) + - 45454545 (22 Hz) + - 34482759 (29 Hz) + - 28571429 (35 Hz) + - 22727273 (44 Hz) + - 17241379 (58 Hz) + - 11363636 (88 Hz) + - 44444 (22 kHz) + - 2: PWM flags 0 or PWM_POLARITY_INVERTED + - 3: The default PWM duty cycle in nanoseconds + patternProperties: "^adi,bypass-attenuator-in[0-4]$": description: | @@ -81,6 +99,10 @@ patternProperties: - smbalert# - gpio + "^fan-[0-9]+$": + $ref: fan-common.yaml# + unevaluatedProperties: false + required: - compatible - reg @@ -89,11 +111,12 @@ additionalProperties: false examples: - | + #include i2c { #address-cells = <1>; #size-cells = <0>; - hwmon@2e { + pwm: hwmon@2e { compatible = "adi,adt7476"; reg = <0x2e>; adi,bypass-attenuator-in0 = <1>; @@ -101,5 +124,15 @@ examples: adi,pwm-active-state = <1 0 1>; adi,pin10-function = "smbalert#"; adi,pin14-function = "tach4"; + #pwm-cells = <4>; + + /* PWMs at 22.5 kHz frequency, 50% duty*/ + fan-0 { + pwms = <&pwm 0 44444 0 22222>; + }; + + fan-1 { + pwms = <&pwm 2 44444 0 22222>; + }; }; }; From 20705629555ad3a86eb2d5bf5244e6ae261664e3 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Tue, 23 Jul 2024 10:17:36 +1200 Subject: [PATCH 032/573] dt-bindings: hwmon: adt7475: Deprecate adi,pwm-active-state Now that we have fan child nodes that can specify flags for the PWM outputs we no longer need the adi,pwm-active-state property. Signed-off-by: Chris Packham Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20240722221737.3407958-3-chris.packham@alliedtelesis.co.nz Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/adt7475.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/hwmon/adt7475.yaml b/Documentation/devicetree/bindings/hwmon/adt7475.yaml index df2b5b889e4d..79e8d62fa3b3 100644 --- a/Documentation/devicetree/bindings/hwmon/adt7475.yaml +++ b/Documentation/devicetree/bindings/hwmon/adt7475.yaml @@ -45,6 +45,7 @@ properties: the pwm uses a logic low output for 100% duty cycle. If set to 1 the pwm uses a logic high output for 100% duty cycle. $ref: /schemas/types.yaml#/definitions/uint32-array + deprecated: true minItems: 3 maxItems: 3 items: @@ -121,7 +122,6 @@ examples: reg = <0x2e>; adi,bypass-attenuator-in0 = <1>; adi,bypass-attenuator-in1 = <0>; - adi,pwm-active-state = <1 0 1>; adi,pin10-function = "smbalert#"; adi,pin14-function = "tach4"; #pwm-cells = <4>; From 777c97ff08d02ef2d19326b8ca9ef9f82b709007 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Tue, 23 Jul 2024 10:17:37 +1200 Subject: [PATCH 033/573] hwmon: (adt7475) Add support for configuring initial PWM state By default the PWM duty cycle in hardware is 100%. On some systems this can cause unwanted fan noise. Add the ability to specify the fan connections and initial state of the PWMs via device properties. Signed-off-by: Chris Packham Link: https://lore.kernel.org/r/20240722221737.3407958-4-chris.packham@alliedtelesis.co.nz [groeck: Cleaned up formatting] Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7475.c | 130 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/drivers/hwmon/adt7475.c b/drivers/hwmon/adt7475.c index bc186c61a2c0..05c85dc6b142 100644 --- a/drivers/hwmon/adt7475.c +++ b/drivers/hwmon/adt7475.c @@ -21,6 +21,8 @@ #include #include +#include + /* Indexes for the sysfs hooks */ #define INPUT 0 @@ -1662,6 +1664,130 @@ static int adt7475_set_pwm_polarity(struct i2c_client *client) return 0; } +struct adt7475_pwm_config { + int index; + int freq; + int flags; + int duty; +}; + +static int _adt7475_pwm_properties_parse_args(u32 args[4], struct adt7475_pwm_config *cfg) +{ + int freq_hz; + int duty; + + if (args[1] == 0) + return -EINVAL; + + freq_hz = 1000000000UL / args[1]; + if (args[3] >= args[1]) + duty = 255; + else + duty = div_u64(255ULL * args[3], args[1]); + + cfg->index = args[0]; + cfg->freq = find_closest(freq_hz, pwmfreq_table, ARRAY_SIZE(pwmfreq_table)); + cfg->flags = args[2]; + cfg->duty = duty; + + return 0; +} + +static int adt7475_pwm_properties_parse_reference_args(struct fwnode_handle *fwnode, + struct adt7475_pwm_config *cfg) +{ + int ret, i; + struct fwnode_reference_args rargs = {}; + u32 args[4] = {}; + + ret = fwnode_property_get_reference_args(fwnode, "pwms", "#pwm-cells", 0, 0, &rargs); + if (ret) + return ret; + + if (rargs.nargs != 4) { + fwnode_handle_put(rargs.fwnode); + return -EINVAL; + } + + for (i = 0; i < 4; i++) + args[i] = rargs.args[i]; + + ret = _adt7475_pwm_properties_parse_args(args, cfg); + + fwnode_handle_put(rargs.fwnode); + + return ret; +} + +static int adt7475_pwm_properties_parse_args(struct fwnode_handle *fwnode, + struct adt7475_pwm_config *cfg) +{ + int ret; + u32 args[4] = {}; + + ret = fwnode_property_read_u32_array(fwnode, "pwms", args, ARRAY_SIZE(args)); + if (ret) + return ret; + + return _adt7475_pwm_properties_parse_args(args, cfg); +} + +static int adt7475_fan_pwm_config(struct i2c_client *client) +{ + struct adt7475_data *data = i2c_get_clientdata(client); + struct fwnode_handle *child; + struct adt7475_pwm_config cfg = {}; + int ret; + + device_for_each_child_node(&client->dev, child) { + if (!fwnode_property_present(child, "pwms")) + continue; + + if (is_of_node(child)) + ret = adt7475_pwm_properties_parse_reference_args(child, &cfg); + else + ret = adt7475_pwm_properties_parse_args(child, &cfg); + + if (cfg.index >= ADT7475_PWM_COUNT) + return -EINVAL; + + ret = adt7475_read(PWM_CONFIG_REG(cfg.index)); + if (ret < 0) + return ret; + data->pwm[CONTROL][cfg.index] = ret; + if (cfg.flags & PWM_POLARITY_INVERTED) + data->pwm[CONTROL][cfg.index] |= BIT(4); + else + data->pwm[CONTROL][cfg.index] &= ~BIT(4); + + /* Force to manual mode so PWM values take effect */ + data->pwm[CONTROL][cfg.index] &= ~0xE0; + data->pwm[CONTROL][cfg.index] |= 0x07 << 5; + + ret = i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(cfg.index), + data->pwm[CONTROL][cfg.index]); + if (ret) + return ret; + + data->pwm[INPUT][cfg.index] = cfg.duty; + ret = i2c_smbus_write_byte_data(client, PWM_REG(cfg.index), + data->pwm[INPUT][cfg.index]); + if (ret) + return ret; + + data->range[cfg.index] = adt7475_read(TEMP_TRANGE_REG(cfg.index)); + data->range[cfg.index] &= ~0xf; + data->range[cfg.index] |= cfg.freq; + + ret = i2c_smbus_write_byte_data(client, TEMP_TRANGE_REG(cfg.index), + data->range[cfg.index]); + if (ret) + return ret; + } + + return 0; +} + static int adt7475_probe(struct i2c_client *client) { enum chips chip; @@ -1774,6 +1900,10 @@ static int adt7475_probe(struct i2c_client *client) if (ret && ret != -EINVAL) dev_warn(&client->dev, "Error configuring pwm polarity\n"); + ret = adt7475_fan_pwm_config(client); + if (ret) + dev_warn(&client->dev, "Error %d configuring fan/pwm\n", ret); + /* Start monitoring */ switch (chip) { case adt7475: From e21d6d4c7acb77fafc71586a0c94db928219c78e Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Fri, 26 Jul 2024 14:07:33 +0200 Subject: [PATCH 034/573] hwmon: (ltc2947) Move to firmware agnostic API Use the device property APIs so the driver does not depend on OF to work. While at it, properly include mod_devicetable for struct of_device_id and fix some parameter alignment in the changed places. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240726-dev-hwmon-ltc6947-fw-agnostic-v1-1-f7d6cab7d438@analog.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc2947-core.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/hwmon/ltc2947-core.c b/drivers/hwmon/ltc2947-core.c index d2ff6e700770..244839167e51 100644 --- a/drivers/hwmon/ltc2947-core.c +++ b/drivers/hwmon/ltc2947-core.c @@ -11,7 +11,8 @@ #include #include #include -#include +#include +#include #include #include "ltc2947.h" @@ -1034,9 +1035,8 @@ static int ltc2947_setup(struct ltc2947_data *st) /* 19.89E-6 * 10E9 */ st->lsb_energy = 19890; } - ret = of_property_read_u32_array(st->dev->of_node, - "adi,accumulator-ctl-pol", accum, - ARRAY_SIZE(accum)); + ret = device_property_read_u32_array(st->dev, "adi,accumulator-ctl-pol", + accum, ARRAY_SIZE(accum)); if (!ret) { u32 accum_reg = LTC2947_ACCUM_POL_1(accum[0]) | LTC2947_ACCUM_POL_2(accum[1]); @@ -1045,9 +1045,9 @@ static int ltc2947_setup(struct ltc2947_data *st) if (ret) return ret; } - ret = of_property_read_u32(st->dev->of_node, - "adi,accumulation-deadband-microamp", - &deadband); + ret = device_property_read_u32(st->dev, + "adi,accumulation-deadband-microamp", + &deadband); if (!ret) { /* the LSB is the same as the current, so 3mA */ ret = regmap_write(st->map, LTC2947_REG_ACCUM_DEADBAND, @@ -1056,7 +1056,7 @@ static int ltc2947_setup(struct ltc2947_data *st) return ret; } /* check gpio cfg */ - ret = of_property_read_u32(st->dev->of_node, "adi,gpio-out-pol", &pol); + ret = device_property_read_u32(st->dev, "adi,gpio-out-pol", &pol); if (!ret) { /* setup GPIO as output */ u32 gpio_ctl = LTC2947_GPIO_EN(1) | LTC2947_GPIO_FAN_EN(1) | @@ -1067,8 +1067,8 @@ static int ltc2947_setup(struct ltc2947_data *st) if (ret) return ret; } - ret = of_property_read_u32_array(st->dev->of_node, "adi,gpio-in-accum", - accum, ARRAY_SIZE(accum)); + ret = device_property_read_u32_array(st->dev, "adi,gpio-in-accum", + accum, ARRAY_SIZE(accum)); if (!ret) { /* * Setup the accum options. The gpioctl is already defined as From 34ea1f9867219e2810210dcfe3392a75a74dc6c3 Mon Sep 17 00:00:00 2001 From: Cryolitia PukNgae Date: Fri, 26 Jul 2024 09:21:14 +0800 Subject: [PATCH 035/573] hwmon: (oxp-sensors) Fix typo in driver documentation RMP -> RPM Signed-off-by: Cryolitia PukNgae Link: https://lore.kernel.org/r/20240726-typo-v1-1-3ca3f07f93e9@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/oxp-sensors.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/hwmon/oxp-sensors.rst b/Documentation/hwmon/oxp-sensors.rst index 55b1ef61625e..50618f064379 100644 --- a/Documentation/hwmon/oxp-sensors.rst +++ b/Documentation/hwmon/oxp-sensors.rst @@ -52,7 +52,7 @@ Sysfs entries The following attributes are supported: fan1_input - Read Only. Reads current fan RMP. + Read Only. Reads current fan RPM. pwm1_enable Read Write. Enable manual fan control. Write "1" to set to manual, write "0" From 74b6451beb8b2cda63b19aff680d4517a0955242 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 15 Jul 2024 07:20:35 -0700 Subject: [PATCH 036/573] hwmon: (max1668) Reorder include files to alphabetic order Reorder include files to alphabetic order to simplify driver maintenance. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max1668.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/max1668.c b/drivers/hwmon/max1668.c index 9fc583ebb11b..f5b5cc29da17 100644 --- a/drivers/hwmon/max1668.c +++ b/drivers/hwmon/max1668.c @@ -6,15 +6,15 @@ * some credit to Christoph Scheurer, but largely a rewrite */ -#include -#include -#include -#include -#include +#include #include #include -#include +#include +#include +#include +#include #include +#include /* Addresses to scan */ static const unsigned short max1668_addr_list[] = { From 696dbe45fb1739eee853b1a110b5001b898031c8 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 15 Jul 2024 07:22:44 -0700 Subject: [PATCH 037/573] hwmon: (max1668) Use BIT macro Use bit macro to make the code easier to understand and reduce duplication. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max1668.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/max1668.c b/drivers/hwmon/max1668.c index f5b5cc29da17..83085ed0ae7e 100644 --- a/drivers/hwmon/max1668.c +++ b/drivers/hwmon/max1668.c @@ -6,6 +6,7 @@ * some credit to Christoph Scheurer, but largely a rewrite */ +#include #include #include #include @@ -172,7 +173,7 @@ static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, if (IS_ERR(data)) return PTR_ERR(data); - return sprintf(buf, "%u\n", (data->alarms >> index) & 0x1); + return sprintf(buf, "%u\n", !!(data->alarms & BIT(index))); } static ssize_t show_fault(struct device *dev, @@ -185,7 +186,7 @@ static ssize_t show_fault(struct device *dev, return PTR_ERR(data); return sprintf(buf, "%u\n", - (data->alarms & (1 << 12)) && data->temp[index] == 127); + (data->alarms & BIT(12)) && data->temp[index] == 127); } static ssize_t set_temp_max(struct device *dev, From 021730acbca603b6bf8575f2e5f4b62c71869656 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 15 Jul 2024 07:18:57 -0700 Subject: [PATCH 038/573] hwmon: (max1668) Convert to use regmap Use regmap for caching to simplify the code and to hide read/write register address differences. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max1668.c | 215 ++++++++++++++++++---------------------- 1 file changed, 99 insertions(+), 116 deletions(-) diff --git a/drivers/hwmon/max1668.c b/drivers/hwmon/max1668.c index 83085ed0ae7e..c7eae28c0b01 100644 --- a/drivers/hwmon/max1668.c +++ b/drivers/hwmon/max1668.c @@ -6,15 +6,15 @@ * some credit to Christoph Scheurer, but largely a rewrite */ +#include #include #include #include #include #include #include -#include #include -#include +#include #include /* Addresses to scan */ @@ -31,14 +31,10 @@ static const unsigned short max1668_addr_list[] = { /* limits */ -/* write high limits */ -#define MAX1668_REG_LIMH_WR(nr) (0x13 + 2 * (nr)) -/* write low limits */ -#define MAX1668_REG_LIML_WR(nr) (0x14 + 2 * (nr)) -/* read high limits */ -#define MAX1668_REG_LIMH_RD(nr) (0x08 + 2 * (nr)) +/* high limits */ +#define MAX1668_REG_LIMH(nr) (0x08 + 2 * (nr)) /* read low limits */ -#define MAX1668_REG_LIML_RD(nr) (0x09 + 2 * (nr)) +#define MAX1668_REG_LIML(nr) (0x09 + 2 * (nr)) /* manufacturer and device ID Constants */ #define MAN_ID_MAXIM 0x4d @@ -54,139 +50,91 @@ MODULE_PARM_DESC(read_only, "Don't set any values, read only mode"); enum chips { max1668, max1805, max1989 }; struct max1668_data { - struct i2c_client *client; + struct regmap *regmap; const struct attribute_group *groups[3]; enum chips type; - - struct mutex update_lock; - bool valid; /* true if following fields are valid */ - unsigned long last_updated; /* In jiffies */ - - /* 1x local and 4x remote */ - s8 temp_max[5]; - s8 temp_min[5]; - s8 temp[5]; - u16 alarms; }; -static struct max1668_data *max1668_update_device(struct device *dev) -{ - struct max1668_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - struct max1668_data *ret = data; - s32 val; - int i; - - mutex_lock(&data->update_lock); - - if (data->valid && !time_after(jiffies, - data->last_updated + HZ + HZ / 2)) - goto abort; - - for (i = 0; i < 5; i++) { - val = i2c_smbus_read_byte_data(client, MAX1668_REG_TEMP(i)); - if (unlikely(val < 0)) { - ret = ERR_PTR(val); - goto abort; - } - data->temp[i] = (s8) val; - - val = i2c_smbus_read_byte_data(client, MAX1668_REG_LIMH_RD(i)); - if (unlikely(val < 0)) { - ret = ERR_PTR(val); - goto abort; - } - data->temp_max[i] = (s8) val; - - val = i2c_smbus_read_byte_data(client, MAX1668_REG_LIML_RD(i)); - if (unlikely(val < 0)) { - ret = ERR_PTR(val); - goto abort; - } - data->temp_min[i] = (s8) val; - } - - val = i2c_smbus_read_byte_data(client, MAX1668_REG_STAT1); - if (unlikely(val < 0)) { - ret = ERR_PTR(val); - goto abort; - } - data->alarms = val << 8; - - val = i2c_smbus_read_byte_data(client, MAX1668_REG_STAT2); - if (unlikely(val < 0)) { - ret = ERR_PTR(val); - goto abort; - } - data->alarms |= val; - - data->last_updated = jiffies; - data->valid = true; -abort: - mutex_unlock(&data->update_lock); - - return ret; -} - static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, char *buf) { int index = to_sensor_dev_attr(devattr)->index; - struct max1668_data *data = max1668_update_device(dev); + struct max1668_data *data = dev_get_drvdata(dev); + u32 temp; + int ret; - if (IS_ERR(data)) - return PTR_ERR(data); + ret = regmap_read(data->regmap, MAX1668_REG_TEMP(index), &temp); + if (ret) + return ret; - return sprintf(buf, "%d\n", data->temp[index] * 1000); + return sprintf(buf, "%d\n", sign_extend32(temp, 7) * 1000); } static ssize_t show_temp_max(struct device *dev, struct device_attribute *devattr, char *buf) { int index = to_sensor_dev_attr(devattr)->index; - struct max1668_data *data = max1668_update_device(dev); + struct max1668_data *data = dev_get_drvdata(dev); + u32 temp; + int ret; - if (IS_ERR(data)) - return PTR_ERR(data); + ret = regmap_read(data->regmap, MAX1668_REG_LIMH(index), &temp); + if (ret) + return ret; - return sprintf(buf, "%d\n", data->temp_max[index] * 1000); + return sprintf(buf, "%d\n", sign_extend32(temp, 7) * 1000); } static ssize_t show_temp_min(struct device *dev, struct device_attribute *devattr, char *buf) { int index = to_sensor_dev_attr(devattr)->index; - struct max1668_data *data = max1668_update_device(dev); + struct max1668_data *data = dev_get_drvdata(dev); + u32 temp; + int ret; - if (IS_ERR(data)) - return PTR_ERR(data); + ret = regmap_read(data->regmap, MAX1668_REG_LIML(index), &temp); + if (ret) + return ret; - return sprintf(buf, "%d\n", data->temp_min[index] * 1000); + return sprintf(buf, "%d\n", sign_extend32(temp, 7) * 1000); } static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, char *buf) { int index = to_sensor_dev_attr(attr)->index; - struct max1668_data *data = max1668_update_device(dev); + struct max1668_data *data = dev_get_drvdata(dev); + u32 alarm; + int ret; - if (IS_ERR(data)) - return PTR_ERR(data); + ret = regmap_read(data->regmap, + index >= 8 ? MAX1668_REG_STAT1 : MAX1668_REG_STAT2, + &alarm); + if (ret) + return ret; - return sprintf(buf, "%u\n", !!(data->alarms & BIT(index))); + return sprintf(buf, "%u\n", !!(alarm & BIT(index & 7))); } static ssize_t show_fault(struct device *dev, struct device_attribute *devattr, char *buf) { int index = to_sensor_dev_attr(devattr)->index; - struct max1668_data *data = max1668_update_device(dev); + struct max1668_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + u32 alarm, temp; + int ret; - if (IS_ERR(data)) - return PTR_ERR(data); + ret = regmap_read(regmap, MAX1668_REG_STAT1, &alarm); + if (ret) + return ret; - return sprintf(buf, "%u\n", - (data->alarms & BIT(12)) && data->temp[index] == 127); + ret = regmap_read(regmap, MAX1668_REG_TEMP(index), &temp); + if (ret) + return ret; + + return sprintf(buf, "%u\n", (alarm & BIT(4)) && temp == 127); } static ssize_t set_temp_max(struct device *dev, @@ -195,7 +143,6 @@ static ssize_t set_temp_max(struct device *dev, { int index = to_sensor_dev_attr(devattr)->index; struct max1668_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; long temp; int ret; @@ -203,14 +150,10 @@ static ssize_t set_temp_max(struct device *dev, if (ret < 0) return ret; - mutex_lock(&data->update_lock); - data->temp_max[index] = clamp_val(temp/1000, -128, 127); - ret = i2c_smbus_write_byte_data(client, - MAX1668_REG_LIMH_WR(index), - data->temp_max[index]); + temp = clamp_val(temp / 1000, -128, 127); + ret = regmap_write(data->regmap, MAX1668_REG_LIMH(index), temp); if (ret < 0) count = ret; - mutex_unlock(&data->update_lock); return count; } @@ -221,7 +164,6 @@ static ssize_t set_temp_min(struct device *dev, { int index = to_sensor_dev_attr(devattr)->index; struct max1668_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; long temp; int ret; @@ -229,14 +171,10 @@ static ssize_t set_temp_min(struct device *dev, if (ret < 0) return ret; - mutex_lock(&data->update_lock); - data->temp_min[index] = clamp_val(temp/1000, -128, 127); - ret = i2c_smbus_write_byte_data(client, - MAX1668_REG_LIML_WR(index), - data->temp_min[index]); + temp = clamp_val(temp / 1000, -128, 127); + ret = regmap_write(data->regmap, MAX1668_REG_LIML(index), temp); if (ret < 0) count = ret; - mutex_unlock(&data->update_lock); return count; } @@ -392,6 +330,48 @@ static int max1668_detect(struct i2c_client *client, return 0; } +/* regmap */ + +static int max1668_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + int ret; + + ret = i2c_smbus_read_byte_data(context, reg); + if (ret < 0) + return ret; + + *val = ret; + return 0; +} + +static int max1668_reg_write(void *context, unsigned int reg, unsigned int val) +{ + return i2c_smbus_write_byte_data(context, reg + 11, val); +} + +static bool max1668_regmap_is_volatile(struct device *dev, unsigned int reg) +{ + return reg <= MAX1668_REG_STAT2; +} + +static bool max1668_regmap_is_writeable(struct device *dev, unsigned int reg) +{ + return reg > MAX1668_REG_STAT2 && reg <= MAX1668_REG_LIML(4); +} + +static const struct regmap_config max1668_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = max1668_regmap_is_volatile, + .writeable_reg = max1668_regmap_is_writeable, +}; + +static const struct regmap_bus max1668_regmap_bus = { + .reg_write = max1668_reg_write, + .reg_read = max1668_reg_read, +}; + static int max1668_probe(struct i2c_client *client) { struct i2c_adapter *adapter = client->adapter; @@ -406,9 +386,12 @@ static int max1668_probe(struct i2c_client *client) if (!data) return -ENOMEM; - data->client = client; + data->regmap = devm_regmap_init(dev, &max1668_regmap_bus, client, + &max1668_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + data->type = (uintptr_t)i2c_get_match_data(client); - mutex_init(&data->update_lock); /* sysfs hooks */ data->groups[0] = &max1668_group_common; From 355c529077b61aadf68910dc103dd54c30ec24d5 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 15 Jul 2024 07:25:38 -0700 Subject: [PATCH 039/573] hwmon: (max1668) Replace chip type with number of channels The only difference between supported chips is the number of channels. Drop enum chips and list the number of channels in struct i2c_device_id directly. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max1668.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/max1668.c b/drivers/hwmon/max1668.c index c7eae28c0b01..f8180a8597c0 100644 --- a/drivers/hwmon/max1668.c +++ b/drivers/hwmon/max1668.c @@ -47,12 +47,10 @@ static bool read_only; module_param(read_only, bool, 0); MODULE_PARM_DESC(read_only, "Don't set any values, read only mode"); -enum chips { max1668, max1805, max1989 }; - struct max1668_data { struct regmap *regmap; const struct attribute_group *groups[3]; - enum chips type; + int channels; }; static ssize_t show_temp(struct device *dev, @@ -391,11 +389,11 @@ static int max1668_probe(struct i2c_client *client) if (IS_ERR(data->regmap)) return PTR_ERR(data->regmap); - data->type = (uintptr_t)i2c_get_match_data(client); + data->channels = (uintptr_t)i2c_get_match_data(client); /* sysfs hooks */ data->groups[0] = &max1668_group_common; - if (data->type == max1668 || data->type == max1989) + if (data->channels == 5) data->groups[1] = &max1668_group_unique; hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, @@ -404,9 +402,9 @@ static int max1668_probe(struct i2c_client *client) } static const struct i2c_device_id max1668_id[] = { - { "max1668", max1668 }, - { "max1805", max1805 }, - { "max1989", max1989 }, + { "max1668", 5 }, + { "max1805", 3 }, + { "max1989", 5 }, { } }; MODULE_DEVICE_TABLE(i2c, max1668_id); From c1b93b5414b48054600c96e28c325ad5920a47e6 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 15 Jul 2024 07:48:24 -0700 Subject: [PATCH 040/573] hwmon: (max1668) Convert to use with_info hwmon API Convert to use with_info API to simplify the code and to reduce its size. This patch reduces object file size by approximately 25%. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max1668.c | 337 ++++++++++++++-------------------------- 1 file changed, 114 insertions(+), 223 deletions(-) diff --git a/drivers/hwmon/max1668.c b/drivers/hwmon/max1668.c index f8180a8597c0..a8197a86f559 100644 --- a/drivers/hwmon/max1668.c +++ b/drivers/hwmon/max1668.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -49,247 +48,144 @@ MODULE_PARM_DESC(read_only, "Don't set any values, read only mode"); struct max1668_data { struct regmap *regmap; - const struct attribute_group *groups[3]; int channels; }; -static ssize_t show_temp(struct device *dev, - struct device_attribute *devattr, char *buf) +static int max1668_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) { - int index = to_sensor_dev_attr(devattr)->index; - struct max1668_data *data = dev_get_drvdata(dev); - u32 temp; - int ret; - - ret = regmap_read(data->regmap, MAX1668_REG_TEMP(index), &temp); - if (ret) - return ret; - - return sprintf(buf, "%d\n", sign_extend32(temp, 7) * 1000); -} - -static ssize_t show_temp_max(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - int index = to_sensor_dev_attr(devattr)->index; - struct max1668_data *data = dev_get_drvdata(dev); - u32 temp; - int ret; - - ret = regmap_read(data->regmap, MAX1668_REG_LIMH(index), &temp); - if (ret) - return ret; - - return sprintf(buf, "%d\n", sign_extend32(temp, 7) * 1000); -} - -static ssize_t show_temp_min(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - int index = to_sensor_dev_attr(devattr)->index; - struct max1668_data *data = dev_get_drvdata(dev); - u32 temp; - int ret; - - ret = regmap_read(data->regmap, MAX1668_REG_LIML(index), &temp); - if (ret) - return ret; - - return sprintf(buf, "%d\n", sign_extend32(temp, 7) * 1000); -} - -static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, - char *buf) -{ - int index = to_sensor_dev_attr(attr)->index; - struct max1668_data *data = dev_get_drvdata(dev); - u32 alarm; - int ret; - - ret = regmap_read(data->regmap, - index >= 8 ? MAX1668_REG_STAT1 : MAX1668_REG_STAT2, - &alarm); - if (ret) - return ret; - - return sprintf(buf, "%u\n", !!(alarm & BIT(index & 7))); -} - -static ssize_t show_fault(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - int index = to_sensor_dev_attr(devattr)->index; struct max1668_data *data = dev_get_drvdata(dev); struct regmap *regmap = data->regmap; - u32 alarm, temp; + u32 regs[2] = { MAX1668_REG_STAT1, MAX1668_REG_TEMP(channel) }; + u8 regvals[2]; + u32 regval; int ret; - ret = regmap_read(regmap, MAX1668_REG_STAT1, &alarm); - if (ret) - return ret; - - ret = regmap_read(regmap, MAX1668_REG_TEMP(index), &temp); - if (ret) - return ret; - - return sprintf(buf, "%u\n", (alarm & BIT(4)) && temp == 127); + switch (attr) { + case hwmon_temp_input: + ret = regmap_read(regmap, MAX1668_REG_TEMP(channel), ®val); + if (ret) + return ret; + *val = sign_extend32(regval, 7) * 1000; + break; + case hwmon_temp_min: + ret = regmap_read(regmap, MAX1668_REG_LIML(channel), ®val); + if (ret) + return ret; + *val = sign_extend32(regval, 7) * 1000; + break; + case hwmon_temp_max: + ret = regmap_read(regmap, MAX1668_REG_LIMH(channel), ®val); + if (ret) + return ret; + *val = sign_extend32(regval, 7) * 1000; + break; + case hwmon_temp_min_alarm: + ret = regmap_read(regmap, + channel ? MAX1668_REG_STAT2 : MAX1668_REG_STAT1, + ®val); + if (ret) + return ret; + if (channel) + *val = !!(regval & BIT(9 - channel * 2)); + else + *val = !!(regval & BIT(5)); + break; + case hwmon_temp_max_alarm: + ret = regmap_read(regmap, + channel ? MAX1668_REG_STAT2 : MAX1668_REG_STAT1, + ®val); + if (ret) + return ret; + if (channel) + *val = !!(regval & BIT(8 - channel * 2)); + else + *val = !!(regval & BIT(6)); + break; + case hwmon_temp_fault: + ret = regmap_multi_reg_read(regmap, regs, regvals, 2); + if (ret) + return ret; + *val = !!((regvals[0] & BIT(4)) && regvals[1] == 127); + break; + default: + return -EOPNOTSUPP; + } + return 0; } -static ssize_t set_temp_max(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) +static int max1668_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) { - int index = to_sensor_dev_attr(devattr)->index; struct max1668_data *data = dev_get_drvdata(dev); - long temp; - int ret; + struct regmap *regmap = data->regmap; - ret = kstrtol(buf, 10, &temp); - if (ret < 0) - return ret; + val = clamp_val(val / 1000, -128, 127); - temp = clamp_val(temp / 1000, -128, 127); - ret = regmap_write(data->regmap, MAX1668_REG_LIMH(index), temp); - if (ret < 0) - count = ret; - - return count; + switch (attr) { + case hwmon_temp_min: + return regmap_write(regmap, MAX1668_REG_LIML(channel), val); + case hwmon_temp_max: + return regmap_write(regmap, MAX1668_REG_LIMH(channel), val); + default: + return -EOPNOTSUPP; + } } -static ssize_t set_temp_min(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) +static umode_t max1668_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) { - int index = to_sensor_dev_attr(devattr)->index; - struct max1668_data *data = dev_get_drvdata(dev); - long temp; - int ret; + const struct max1668_data *data = _data; - ret = kstrtol(buf, 10, &temp); - if (ret < 0) - return ret; + if (channel >= data->channels) + return 0; - temp = clamp_val(temp / 1000, -128, 127); - ret = regmap_write(data->regmap, MAX1668_REG_LIML(index), temp); - if (ret < 0) - count = ret; - - return count; + switch (attr) { + case hwmon_temp_min: + case hwmon_temp_max: + return read_only ? 0444 : 0644; + case hwmon_temp_input: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + return 0444; + case hwmon_temp_fault: + if (channel) + return 0444; + break; + default: + break; + } + return 0; } -static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); -static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp_max, - set_temp_max, 0); -static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO, show_temp_min, - set_temp_min, 0); -static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); -static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO, show_temp_max, - set_temp_max, 1); -static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO, show_temp_min, - set_temp_min, 1); -static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); -static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO, show_temp_max, - set_temp_max, 2); -static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO, show_temp_min, - set_temp_min, 2); -static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); -static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO, show_temp_max, - set_temp_max, 3); -static SENSOR_DEVICE_ATTR(temp4_min, S_IRUGO, show_temp_min, - set_temp_min, 3); -static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp, NULL, 4); -static SENSOR_DEVICE_ATTR(temp5_max, S_IRUGO, show_temp_max, - set_temp_max, 4); -static SENSOR_DEVICE_ATTR(temp5_min, S_IRUGO, show_temp_min, - set_temp_min, 4); - -static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 14); -static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, 13); -static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_alarm, NULL, 7); -static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 6); -static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_alarm, NULL, 5); -static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_alarm, NULL, 4); -static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_alarm, NULL, 3); -static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_alarm, NULL, 2); -static SENSOR_DEVICE_ATTR(temp5_min_alarm, S_IRUGO, show_alarm, NULL, 1); -static SENSOR_DEVICE_ATTR(temp5_max_alarm, S_IRUGO, show_alarm, NULL, 0); - -static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_fault, NULL, 1); -static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_fault, NULL, 2); -static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_fault, NULL, 3); -static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_fault, NULL, 4); - -/* Attributes common to MAX1668, MAX1989 and MAX1805 */ -static struct attribute *max1668_attribute_common[] = { - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp2_min.dev_attr.attr, - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp3_max.dev_attr.attr, - &sensor_dev_attr_temp3_min.dev_attr.attr, - &sensor_dev_attr_temp3_input.dev_attr.attr, - - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, - - &sensor_dev_attr_temp2_fault.dev_attr.attr, - &sensor_dev_attr_temp3_fault.dev_attr.attr, +static const struct hwmon_channel_info * const max1668_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | + HWMON_T_FAULT, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | + HWMON_T_FAULT, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | + HWMON_T_FAULT, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | + HWMON_T_FAULT), NULL }; -/* Attributes not present on MAX1805 */ -static struct attribute *max1668_attribute_unique[] = { - &sensor_dev_attr_temp4_max.dev_attr.attr, - &sensor_dev_attr_temp4_min.dev_attr.attr, - &sensor_dev_attr_temp4_input.dev_attr.attr, - &sensor_dev_attr_temp5_max.dev_attr.attr, - &sensor_dev_attr_temp5_min.dev_attr.attr, - &sensor_dev_attr_temp5_input.dev_attr.attr, - - &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp5_min_alarm.dev_attr.attr, - - &sensor_dev_attr_temp4_fault.dev_attr.attr, - &sensor_dev_attr_temp5_fault.dev_attr.attr, - NULL +static const struct hwmon_ops max1668_hwmon_ops = { + .is_visible = max1668_is_visible, + .read = max1668_read, + .write = max1668_write, }; -static umode_t max1668_attribute_mode(struct kobject *kobj, - struct attribute *attr, int index) -{ - umode_t ret = S_IRUGO; - if (read_only) - return ret; - if (attr == &sensor_dev_attr_temp1_max.dev_attr.attr || - attr == &sensor_dev_attr_temp2_max.dev_attr.attr || - attr == &sensor_dev_attr_temp3_max.dev_attr.attr || - attr == &sensor_dev_attr_temp4_max.dev_attr.attr || - attr == &sensor_dev_attr_temp5_max.dev_attr.attr || - attr == &sensor_dev_attr_temp1_min.dev_attr.attr || - attr == &sensor_dev_attr_temp2_min.dev_attr.attr || - attr == &sensor_dev_attr_temp3_min.dev_attr.attr || - attr == &sensor_dev_attr_temp4_min.dev_attr.attr || - attr == &sensor_dev_attr_temp5_min.dev_attr.attr) - ret |= S_IWUSR; - return ret; -} - -static const struct attribute_group max1668_group_common = { - .attrs = max1668_attribute_common, - .is_visible = max1668_attribute_mode -}; - -static const struct attribute_group max1668_group_unique = { - .attrs = max1668_attribute_unique, - .is_visible = max1668_attribute_mode +static const struct hwmon_chip_info max1668_chip_info = { + .ops = &max1668_hwmon_ops, + .info = max1668_info, }; /* Return 0 if detection is successful, -ENODEV otherwise */ @@ -391,13 +287,8 @@ static int max1668_probe(struct i2c_client *client) data->channels = (uintptr_t)i2c_get_match_data(client); - /* sysfs hooks */ - data->groups[0] = &max1668_group_common; - if (data->channels == 5) - data->groups[1] = &max1668_group_unique; - - hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, - data, data->groups); + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &max1668_chip_info, NULL); return PTR_ERR_OR_ZERO(hwmon_dev); } From 3adc2857bb6a7c6cb669627b87e9998a3a28231d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 27 Jul 2024 12:06:04 -0700 Subject: [PATCH 041/573] hwmon: (max6697) Document discrepancy in overtemperature status bit values In the MAX6581 datasheet Revision 0 to 3, the local channel overtemperature status is reported in bit 6 of register 0x45, and the overtemperature status for remote channel 7 is reported in bit 7. In Revision 4 and later, the local channel overtemperature status is reported in bit 7, and the remote channel 7 overtemperature status is reported in bit 6. A real chip was found to match the functionality documented in Revision 4 and later. The code was fixed with commit 1ea3fd1eb986 ("hwmon: (max6697) Fix swapped temp{1,8} critical alarms"). At that time it looked like this was an original bug. It only turned out later that the problem was the result of incorrect information in the chip datasheet. Document the discrepancy to avoid confusion caused by old versions of the datasheet. Cc: Tzung-Bi Shih Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max6697.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c index 0c34c0c81232..0735a1d2c20f 100644 --- a/drivers/hwmon/max6697.c +++ b/drivers/hwmon/max6697.c @@ -248,6 +248,16 @@ static int max6697_read(struct device *dev, enum hwmon_sensor_types type, ret = regmap_read(regmap, MAX6697_REG_STAT_CRIT, ®val); if (ret) return ret; + /* + * In the MAX6581 datasheet revision 0 to 3, the local channel + * overtemperature status is reported in bit 6 of register 0x45, + * and the overtemperature status for remote channel 7 is + * reported in bit 7. In Revision 4 and later, the local channel + * overtemperature status is reported in bit 7, and the remote + * channel 7 overtemperature status is reported in bit 6. A real + * chip was found to match the functionality documented in + * Revision 4 and later. + */ *val = !!(regval & BIT(channel ? channel - 1 : 7)); break; case hwmon_temp_max_alarm: From 88c0f840dfe36e8c2fd9ddc7d6698e485f0ca1ad Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 15 Jul 2024 22:36:35 -0700 Subject: [PATCH 042/573] hwmon: (max1619) Clamp temperature range when writing limits Module test code reports underflows when writing sensor limits. temp2_min: Suspected underflow: [min=-77000, read 101000, written -2147483648] temp2_max: Suspected underflow: [min=-77000, read 101000, written -2147483648] temp2_crit: Suspected underflow: [min=-77000, read 101000, written -2147483648] Clamp temperature ranges when writing limits to fix the problem. While at it, use sign_extend32() when reading temperatures to make the code easier to understand. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max1619.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/drivers/hwmon/max1619.c b/drivers/hwmon/max1619.c index a89a519cf5d9..464f4c838394 100644 --- a/drivers/hwmon/max1619.c +++ b/drivers/hwmon/max1619.c @@ -52,16 +52,6 @@ static const unsigned short normal_i2c[] = { * Conversions */ -static int temp_from_reg(int val) -{ - return (val & 0x80 ? val-0x100 : val) * 1000; -} - -static int temp_to_reg(int val) -{ - return (val < 0 ? val+0x100*1000 : val) / 1000; -} - enum temp_index { t_input1 = 0, t_input2, @@ -142,7 +132,7 @@ static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct max1619_data *data = max1619_update_device(dev); - return sprintf(buf, "%d\n", temp_from_reg(data->temp[attr->index])); + return sprintf(buf, "%d\n", sign_extend(data->temp[attr->index], 7) * 1000); } static ssize_t temp_store(struct device *dev, @@ -158,7 +148,7 @@ static ssize_t temp_store(struct device *dev, return err; mutex_lock(&data->update_lock); - data->temp[attr->index] = temp_to_reg(val); + data->temp[attr->index] = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000); i2c_smbus_write_byte_data(client, regs_write[attr->index], data->temp[attr->index]); mutex_unlock(&data->update_lock); From 216147e624638fbcd64ed1014916138733b01653 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 15 Jul 2024 20:28:58 -0700 Subject: [PATCH 043/573] hwmon: (max1619) Reorder include files to alphabetic order Simplify maintenance by reordering include files to alphabetic order. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max1619.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/max1619.c b/drivers/hwmon/max1619.c index 464f4c838394..8eb7d04bd2f5 100644 --- a/drivers/hwmon/max1619.c +++ b/drivers/hwmon/max1619.c @@ -12,15 +12,15 @@ * http://pdfserv.maxim-ic.com/en/ds/MAX1619.pdf */ -#include -#include -#include -#include -#include +#include #include #include -#include +#include +#include +#include +#include #include +#include #include static const unsigned short normal_i2c[] = { From 02c3c4e7026b307a220132b59758432b6e7a0cac Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 28 Jul 2024 07:09:54 -0700 Subject: [PATCH 044/573] hwmon: (max1619) Mask valid alarm bits Bit 0, 5, and 6 in the status register are reserved and, if set, do not indicate an alarm. Bit 7 is the 'busy' bit and also does not indicate an alarm. Mask the non-alarm bits to avoid reporting them to userspace. Cc: Tzung-Bi Shih Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max1619.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/max1619.c b/drivers/hwmon/max1619.c index 8eb7d04bd2f5..5edc9bbbe299 100644 --- a/drivers/hwmon/max1619.c +++ b/drivers/hwmon/max1619.c @@ -112,6 +112,7 @@ static struct max1619_data *max1619_update_device(struct device *dev) config = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONFIG); if (!(config & 0x20)) data->alarms ^= 0x02; + data->alarms &= 0x1e; data->last_updated = jiffies; data->valid = true; From f8016132ce49dc4acf5c476cebfc93c27fb493cc Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 15 Jul 2024 22:26:22 -0700 Subject: [PATCH 045/573] hwmon: (max1619) Convert to use regmap Use regmap for local caching, to hide register read/write address differences, and for multi-byte operations. With this change, the driver specific lock is no longer necessary. While at it, check errors seen when initializing the chip and bail out if chip initialization fails. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- Documentation/hwmon/max1619.rst | 4 - drivers/hwmon/max1619.c | 259 +++++++++++++++++--------------- 2 files changed, 137 insertions(+), 126 deletions(-) diff --git a/Documentation/hwmon/max1619.rst b/Documentation/hwmon/max1619.rst index e25956e70f73..b5fc175ae18d 100644 --- a/Documentation/hwmon/max1619.rst +++ b/Documentation/hwmon/max1619.rst @@ -27,7 +27,3 @@ All temperature values are given in degrees Celsius. Resolution is 1.0 degree for the local temperature and for the remote temperature. Only the external sensor has high and low limits. - -The max1619 driver will not update its values more frequently than every -other second; reading them more often will do no harm, but will return -'old' values. diff --git a/drivers/hwmon/max1619.c b/drivers/hwmon/max1619.c index 5edc9bbbe299..76ccc3e94b4c 100644 --- a/drivers/hwmon/max1619.c +++ b/drivers/hwmon/max1619.c @@ -17,10 +17,8 @@ #include #include #include -#include #include -#include -#include +#include #include static const unsigned short normal_i2c[] = { @@ -30,27 +28,17 @@ static const unsigned short normal_i2c[] = { * The MAX1619 registers */ -#define MAX1619_REG_R_MAN_ID 0xFE -#define MAX1619_REG_R_CHIP_ID 0xFF -#define MAX1619_REG_R_CONFIG 0x03 -#define MAX1619_REG_W_CONFIG 0x09 -#define MAX1619_REG_R_CONVRATE 0x04 -#define MAX1619_REG_W_CONVRATE 0x0A -#define MAX1619_REG_R_STATUS 0x02 -#define MAX1619_REG_R_LOCAL_TEMP 0x00 -#define MAX1619_REG_R_REMOTE_TEMP 0x01 -#define MAX1619_REG_R_REMOTE_HIGH 0x07 -#define MAX1619_REG_W_REMOTE_HIGH 0x0D -#define MAX1619_REG_R_REMOTE_LOW 0x08 -#define MAX1619_REG_W_REMOTE_LOW 0x0E -#define MAX1619_REG_R_REMOTE_CRIT 0x10 -#define MAX1619_REG_W_REMOTE_CRIT 0x12 -#define MAX1619_REG_R_TCRIT_HYST 0x11 -#define MAX1619_REG_W_TCRIT_HYST 0x13 - -/* - * Conversions - */ +#define MAX1619_REG_LOCAL_TEMP 0x00 +#define MAX1619_REG_REMOTE_TEMP 0x01 +#define MAX1619_REG_STATUS 0x02 +#define MAX1619_REG_CONFIG 0x03 +#define MAX1619_REG_CONVRATE 0x04 +#define MAX1619_REG_REMOTE_HIGH 0x07 +#define MAX1619_REG_REMOTE_LOW 0x08 +#define MAX1619_REG_REMOTE_CRIT 0x10 +#define MAX1619_REG_REMOTE_CRIT_HYST 0x11 +#define MAX1619_REG_MAN_ID 0xFE +#define MAX1619_REG_CHIP_ID 0xFF enum temp_index { t_input1 = 0, @@ -66,63 +54,15 @@ enum temp_index { * Client data (each client gets its own) */ -struct max1619_data { - struct i2c_client *client; - struct mutex update_lock; - bool valid; /* false until following fields are valid */ - unsigned long last_updated; /* in jiffies */ - - /* registers values */ - u8 temp[t_num_regs]; /* index with enum temp_index */ - u8 alarms; +static const u8 regs[t_num_regs] = { + [t_input1] = MAX1619_REG_LOCAL_TEMP, + [t_input2] = MAX1619_REG_REMOTE_TEMP, + [t_low2] = MAX1619_REG_REMOTE_LOW, + [t_high2] = MAX1619_REG_REMOTE_HIGH, + [t_crit2] = MAX1619_REG_REMOTE_CRIT, + [t_hyst2] = MAX1619_REG_REMOTE_CRIT_HYST, }; -static const u8 regs_read[t_num_regs] = { - [t_input1] = MAX1619_REG_R_LOCAL_TEMP, - [t_input2] = MAX1619_REG_R_REMOTE_TEMP, - [t_low2] = MAX1619_REG_R_REMOTE_LOW, - [t_high2] = MAX1619_REG_R_REMOTE_HIGH, - [t_crit2] = MAX1619_REG_R_REMOTE_CRIT, - [t_hyst2] = MAX1619_REG_R_TCRIT_HYST, -}; - -static const u8 regs_write[t_num_regs] = { - [t_low2] = MAX1619_REG_W_REMOTE_LOW, - [t_high2] = MAX1619_REG_W_REMOTE_HIGH, - [t_crit2] = MAX1619_REG_W_REMOTE_CRIT, - [t_hyst2] = MAX1619_REG_W_TCRIT_HYST, -}; - -static struct max1619_data *max1619_update_device(struct device *dev) -{ - struct max1619_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - int config, i; - - mutex_lock(&data->update_lock); - - if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) { - dev_dbg(&client->dev, "Updating max1619 data.\n"); - for (i = 0; i < t_num_regs; i++) - data->temp[i] = i2c_smbus_read_byte_data(client, - regs_read[i]); - data->alarms = i2c_smbus_read_byte_data(client, - MAX1619_REG_R_STATUS); - /* If OVERT polarity is low, reverse alarm bit */ - config = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONFIG); - if (!(config & 0x20)) - data->alarms ^= 0x02; - data->alarms &= 0x1e; - - data->last_updated = jiffies; - data->valid = true; - } - - mutex_unlock(&data->update_lock); - - return data; -} - /* * Sysfs stuff */ @@ -131,9 +71,15 @@ static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct max1619_data *data = max1619_update_device(dev); + struct regmap *regmap = dev_get_drvdata(dev); + u32 temp; + int ret; - return sprintf(buf, "%d\n", sign_extend(data->temp[attr->index], 7) * 1000); + ret = regmap_read(regmap, regs[attr->index], &temp); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", sign_extend32(temp, 7) * 1000); } static ssize_t temp_store(struct device *dev, @@ -141,34 +87,61 @@ static ssize_t temp_store(struct device *dev, size_t count) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct max1619_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; + struct regmap *regmap = dev_get_drvdata(dev); long val; int err = kstrtol(buf, 10, &val); if (err) return err; - mutex_lock(&data->update_lock); - data->temp[attr->index] = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000); - i2c_smbus_write_byte_data(client, regs_write[attr->index], - data->temp[attr->index]); - mutex_unlock(&data->update_lock); + val = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000); + err = regmap_write(regmap, regs[attr->index], val); + if (err < 0) + return err; return count; } +static int get_alarms(struct regmap *regmap) +{ + static u32 regs[2] = { MAX1619_REG_STATUS, MAX1619_REG_CONFIG }; + u8 regdata[2]; + int ret; + + ret = regmap_multi_reg_read(regmap, regs, regdata, 2); + if (ret) + return ret; + + /* OVERT status bit may be reversed */ + if (!(regdata[1] & 0x20)) + regdata[0] ^= 0x02; + + return regdata[0] & 0x1e; +} + static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct max1619_data *data = max1619_update_device(dev); - return sprintf(buf, "%d\n", data->alarms); + struct regmap *regmap = dev_get_drvdata(dev); + int alarms; + + alarms = get_alarms(regmap); + if (alarms < 0) + return alarms; + + return sprintf(buf, "%d\n", alarms); } static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf) { int bitnr = to_sensor_dev_attr(attr)->index; - struct max1619_data *data = max1619_update_device(dev); - return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1); + struct regmap *regmap = dev_get_drvdata(dev); + int alarms; + + alarms = get_alarms(regmap); + if (alarms < 0) + return alarms; + + return sprintf(buf, "%d\n", (alarms >> bitnr) & 1); } static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, t_input1); @@ -212,9 +185,9 @@ static int max1619_detect(struct i2c_client *client, return -ENODEV; /* detection */ - reg_config = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONFIG); - reg_convrate = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONVRATE); - reg_status = i2c_smbus_read_byte_data(client, MAX1619_REG_R_STATUS); + reg_config = i2c_smbus_read_byte_data(client, MAX1619_REG_CONFIG); + reg_convrate = i2c_smbus_read_byte_data(client, MAX1619_REG_CONVRATE); + reg_status = i2c_smbus_read_byte_data(client, MAX1619_REG_STATUS); if ((reg_config & 0x03) != 0x00 || reg_convrate > 0x07 || (reg_status & 0x61) != 0x00) { dev_dbg(&adapter->dev, "MAX1619 detection failed at 0x%02x\n", @@ -223,8 +196,8 @@ static int max1619_detect(struct i2c_client *client, } /* identification */ - man_id = i2c_smbus_read_byte_data(client, MAX1619_REG_R_MAN_ID); - chip_id = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CHIP_ID); + man_id = i2c_smbus_read_byte_data(client, MAX1619_REG_MAN_ID); + chip_id = i2c_smbus_read_byte_data(client, MAX1619_REG_CHIP_ID); if (man_id != 0x4D || chip_id != 0x04) { dev_info(&adapter->dev, "Unsupported chip (man_id=0x%02X, chip_id=0x%02X).\n", @@ -237,40 +210,82 @@ static int max1619_detect(struct i2c_client *client, return 0; } -static void max1619_init_client(struct i2c_client *client) +static int max1619_init_chip(struct regmap *regmap) { - u8 config; + int ret; - /* - * Start the conversions. - */ - i2c_smbus_write_byte_data(client, MAX1619_REG_W_CONVRATE, - 5); /* 2 Hz */ - config = i2c_smbus_read_byte_data(client, MAX1619_REG_R_CONFIG); - if (config & 0x40) - i2c_smbus_write_byte_data(client, MAX1619_REG_W_CONFIG, - config & 0xBF); /* run */ + ret = regmap_write(regmap, MAX1619_REG_CONVRATE, 5); /* 2 Hz */ + if (ret) + return ret; + + /* Start conversions */ + return regmap_clear_bits(regmap, MAX1619_REG_CONFIG, 0x40); } -static int max1619_probe(struct i2c_client *new_client) +/* regmap */ + +static int max1619_reg_read(void *context, unsigned int reg, unsigned int *val) { - struct max1619_data *data; + int ret; + + ret = i2c_smbus_read_byte_data(context, reg); + if (ret < 0) + return ret; + + *val = ret; + return 0; +} + +static int max1619_reg_write(void *context, unsigned int reg, unsigned int val) +{ + int offset = reg < MAX1619_REG_REMOTE_CRIT ? 6 : 2; + + return i2c_smbus_write_byte_data(context, reg + offset, val); +} + +static bool max1619_regmap_is_volatile(struct device *dev, unsigned int reg) +{ + return reg <= MAX1619_REG_STATUS; +} + +static bool max1619_regmap_is_writeable(struct device *dev, unsigned int reg) +{ + return reg > MAX1619_REG_STATUS && reg <= MAX1619_REG_REMOTE_CRIT_HYST; +} + +static const struct regmap_config max1619_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX1619_REG_REMOTE_CRIT_HYST, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = max1619_regmap_is_volatile, + .writeable_reg = max1619_regmap_is_writeable, +}; + +static const struct regmap_bus max1619_regmap_bus = { + .reg_write = max1619_reg_write, + .reg_read = max1619_reg_read, +}; + +static int max1619_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; struct device *hwmon_dev; + struct regmap *regmap; + int ret; - data = devm_kzalloc(&new_client->dev, sizeof(struct max1619_data), - GFP_KERNEL); - if (!data) - return -ENOMEM; + regmap = devm_regmap_init(dev, &max1619_regmap_bus, client, + &max1619_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); - data->client = new_client; - mutex_init(&data->update_lock); + ret = max1619_init_chip(regmap); + if (ret) + return ret; - /* Initialize the MAX1619 chip */ - max1619_init_client(new_client); - - hwmon_dev = devm_hwmon_device_register_with_groups(&new_client->dev, - new_client->name, - data, + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + client->name, + regmap, max1619_groups); return PTR_ERR_OR_ZERO(hwmon_dev); } From f0b1f1f468f5c9bde6269e73c9a3eb57cb7e3293 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 16 Jul 2024 08:24:28 -0700 Subject: [PATCH 046/573] hwmon: (max1619) Convert to with_info API Convert driver to with_info hwmon API to simplify the code and with it its maintainability. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max1619.c | 269 +++++++++++++++++++++++----------------- 1 file changed, 157 insertions(+), 112 deletions(-) diff --git a/drivers/hwmon/max1619.c b/drivers/hwmon/max1619.c index 76ccc3e94b4c..5159ff96ff37 100644 --- a/drivers/hwmon/max1619.c +++ b/drivers/hwmon/max1619.c @@ -14,20 +14,14 @@ #include #include -#include #include #include #include #include -#include static const unsigned short normal_i2c[] = { 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, 0x4c, 0x4d, 0x4e, I2C_CLIENT_END }; -/* - * The MAX1619 registers - */ - #define MAX1619_REG_LOCAL_TEMP 0x00 #define MAX1619_REG_REMOTE_TEMP 0x01 #define MAX1619_REG_STATUS 0x02 @@ -40,66 +34,6 @@ static const unsigned short normal_i2c[] = { #define MAX1619_REG_MAN_ID 0xFE #define MAX1619_REG_CHIP_ID 0xFF -enum temp_index { - t_input1 = 0, - t_input2, - t_low2, - t_high2, - t_crit2, - t_hyst2, - t_num_regs -}; - -/* - * Client data (each client gets its own) - */ - -static const u8 regs[t_num_regs] = { - [t_input1] = MAX1619_REG_LOCAL_TEMP, - [t_input2] = MAX1619_REG_REMOTE_TEMP, - [t_low2] = MAX1619_REG_REMOTE_LOW, - [t_high2] = MAX1619_REG_REMOTE_HIGH, - [t_crit2] = MAX1619_REG_REMOTE_CRIT, - [t_hyst2] = MAX1619_REG_REMOTE_CRIT_HYST, -}; - -/* - * Sysfs stuff - */ - -static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct regmap *regmap = dev_get_drvdata(dev); - u32 temp; - int ret; - - ret = regmap_read(regmap, regs[attr->index], &temp); - if (ret < 0) - return ret; - - return sprintf(buf, "%d\n", sign_extend32(temp, 7) * 1000); -} - -static ssize_t temp_store(struct device *dev, - struct device_attribute *devattr, const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct regmap *regmap = dev_get_drvdata(dev); - long val; - int err = kstrtol(buf, 10, &val); - if (err) - return err; - - val = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000); - err = regmap_write(regmap, regs[attr->index], val); - if (err < 0) - return err; - return count; -} - static int get_alarms(struct regmap *regmap) { static u32 regs[2] = { MAX1619_REG_STATUS, MAX1619_REG_CONFIG }; @@ -117,62 +51,175 @@ static int get_alarms(struct regmap *regmap) return regdata[0] & 0x1e; } -static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, - char *buf) +static int max1619_temp_read(struct regmap *regmap, u32 attr, int channel, long *val) { - struct regmap *regmap = dev_get_drvdata(dev); - int alarms; + int reg = -1, alarm_bit = 0; + u32 temp; + int ret; - alarms = get_alarms(regmap); - if (alarms < 0) - return alarms; - - return sprintf(buf, "%d\n", alarms); + switch (attr) { + case hwmon_temp_input: + reg = channel ? MAX1619_REG_REMOTE_TEMP : MAX1619_REG_LOCAL_TEMP; + break; + case hwmon_temp_min: + reg = MAX1619_REG_REMOTE_LOW; + break; + case hwmon_temp_max: + reg = MAX1619_REG_REMOTE_HIGH; + break; + case hwmon_temp_crit: + reg = MAX1619_REG_REMOTE_CRIT; + break; + case hwmon_temp_crit_hyst: + reg = MAX1619_REG_REMOTE_CRIT_HYST; + break; + case hwmon_temp_min_alarm: + alarm_bit = 3; + break; + case hwmon_temp_max_alarm: + alarm_bit = 4; + break; + case hwmon_temp_crit_alarm: + alarm_bit = 1; + break; + case hwmon_temp_fault: + alarm_bit = 2; + break; + default: + return -EOPNOTSUPP; + } + if (reg >= 0) { + ret = regmap_read(regmap, reg, &temp); + if (ret < 0) + return ret; + *val = sign_extend32(temp, 7) * 1000; + } else { + ret = get_alarms(regmap); + if (ret < 0) + return ret; + *val = !!(ret & BIT(alarm_bit)); + } + return 0; } -static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, - char *buf) +static int max1619_chip_read(struct regmap *regmap, u32 attr, long *val) { - int bitnr = to_sensor_dev_attr(attr)->index; - struct regmap *regmap = dev_get_drvdata(dev); int alarms; - alarms = get_alarms(regmap); - if (alarms < 0) - return alarms; - - return sprintf(buf, "%d\n", (alarms >> bitnr) & 1); + switch (attr) { + case hwmon_chip_alarms: + alarms = get_alarms(regmap); + if (alarms < 0) + return alarms; + *val = alarms; + break; + default: + return -EOPNOTSUPP; + } + return 0; } -static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, t_input1); -static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, t_input2); -static SENSOR_DEVICE_ATTR_RW(temp2_min, temp, t_low2); -static SENSOR_DEVICE_ATTR_RW(temp2_max, temp, t_high2); -static SENSOR_DEVICE_ATTR_RW(temp2_crit, temp, t_crit2); -static SENSOR_DEVICE_ATTR_RW(temp2_crit_hyst, temp, t_hyst2); +static int max1619_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct regmap *regmap = dev_get_drvdata(dev); -static DEVICE_ATTR_RO(alarms); -static SENSOR_DEVICE_ATTR_RO(temp2_crit_alarm, alarm, 1); -static SENSOR_DEVICE_ATTR_RO(temp2_fault, alarm, 2); -static SENSOR_DEVICE_ATTR_RO(temp2_min_alarm, alarm, 3); -static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, alarm, 4); + switch (type) { + case hwmon_chip: + return max1619_chip_read(regmap, attr, val); + case hwmon_temp: + return max1619_temp_read(regmap, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} -static struct attribute *max1619_attrs[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp2_min.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp2_crit.dev_attr.attr, - &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr, +static int max1619_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct regmap *regmap = dev_get_drvdata(dev); + int reg; - &dev_attr_alarms.attr, - &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_fault.dev_attr.attr, - &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + if (type != hwmon_temp) + return -EOPNOTSUPP; + + switch (attr) { + case hwmon_temp_min: + reg = MAX1619_REG_REMOTE_LOW; + break; + case hwmon_temp_max: + reg = MAX1619_REG_REMOTE_HIGH; + break; + case hwmon_temp_crit: + reg = MAX1619_REG_REMOTE_CRIT; + break; + case hwmon_temp_crit_hyst: + reg = MAX1619_REG_REMOTE_CRIT_HYST; + break; + default: + return -EOPNOTSUPP; + } + val = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000); + return regmap_write(regmap, reg, val); +} + +static umode_t max1619_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_alarms: + return 0444; + default: + break; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + return 0444; + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + case hwmon_temp_crit_hyst: + return 0644; + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + case hwmon_temp_fault: + return 0444; + default: + break; + } + break; + default: + break; + } + return 0; +} + +static const struct hwmon_channel_info * const max1619_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_ALARMS), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT), NULL }; -ATTRIBUTE_GROUPS(max1619); + +static const struct hwmon_ops max1619_hwmon_ops = { + .is_visible = max1619_is_visible, + .read = max1619_read, + .write = max1619_write, +}; + +static const struct hwmon_chip_info max1619_chip_info = { + .ops = &max1619_hwmon_ops, + .info = max1619_info, +}; /* Return 0 if detection is successful, -ENODEV otherwise */ static int max1619_detect(struct i2c_client *client, @@ -283,10 +330,8 @@ static int max1619_probe(struct i2c_client *client) if (ret) return ret; - hwmon_dev = devm_hwmon_device_register_with_groups(dev, - client->name, - regmap, - max1619_groups); + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + regmap, &max1619_chip_info, NULL); return PTR_ERR_OR_ZERO(hwmon_dev); } From 13047ebe63a8b4ffabbb97ec6edc8c7dcdb96cee Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 16 Jul 2024 09:01:27 -0700 Subject: [PATCH 047/573] hwmon: (max1619) Add support for update_interval attribute The chip supports reading and writing the conversion rate. Add support for the update_interval sysfs attribute. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max1619.c | 50 ++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/max1619.c b/drivers/hwmon/max1619.c index 5159ff96ff37..72e17afb22f0 100644 --- a/drivers/hwmon/max1619.c +++ b/drivers/hwmon/max1619.c @@ -18,6 +18,7 @@ #include #include #include +#include static const unsigned short normal_i2c[] = { 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, 0x4c, 0x4d, 0x4e, I2C_CLIENT_END }; @@ -102,11 +103,20 @@ static int max1619_temp_read(struct regmap *regmap, u32 attr, int channel, long return 0; } +static u16 update_intervals[] = { 16000, 8000, 4000, 2000, 1000, 500, 250, 125 }; + static int max1619_chip_read(struct regmap *regmap, u32 attr, long *val) { - int alarms; + int alarms, ret; + u32 regval; switch (attr) { + case hwmon_chip_update_interval: + ret = regmap_read(regmap, MAX1619_REG_CONVRATE, ®val); + if (ret < 0) + return ret; + *val = update_intervals[regval & 7]; + break; case hwmon_chip_alarms: alarms = get_alarms(regmap); if (alarms < 0) @@ -134,14 +144,21 @@ static int max1619_read(struct device *dev, enum hwmon_sensor_types type, } } -static int max1619_write(struct device *dev, enum hwmon_sensor_types type, - u32 attr, int channel, long val) +static int max1619_chip_write(struct regmap *regmap, u32 attr, long val) { - struct regmap *regmap = dev_get_drvdata(dev); - int reg; - - if (type != hwmon_temp) + switch (attr) { + case hwmon_chip_update_interval: + val = find_closest_descending(val, update_intervals, ARRAY_SIZE(update_intervals)); + return regmap_write(regmap, MAX1619_REG_CONVRATE, val); + default: return -EOPNOTSUPP; + } +} + +static int max1619_temp_write(struct regmap *regmap, + u32 attr, int channel, long val) +{ + int reg; switch (attr) { case hwmon_temp_min: @@ -163,12 +180,29 @@ static int max1619_write(struct device *dev, enum hwmon_sensor_types type, return regmap_write(regmap, reg, val); } +static int max1619_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct regmap *regmap = dev_get_drvdata(dev); + + switch (type) { + case hwmon_chip: + return max1619_chip_write(regmap, attr, val); + case hwmon_temp: + return max1619_temp_write(regmap, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + static umode_t max1619_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr, int channel) { switch (type) { case hwmon_chip: switch (attr) { + case hwmon_chip_update_interval: + return 0644; case hwmon_chip_alarms: return 0444; default: @@ -200,7 +234,7 @@ static umode_t max1619_is_visible(const void *_data, enum hwmon_sensor_types typ } static const struct hwmon_channel_info * const max1619_info[] = { - HWMON_CHANNEL_INFO(chip, HWMON_C_ALARMS), + HWMON_CHANNEL_INFO(chip, HWMON_C_ALARMS | HWMON_C_UPDATE_INTERVAL), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | From 90368fa3978fee3a5df5cbee3312e3fe51f7a8c5 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 26 Jul 2024 15:29:40 -0700 Subject: [PATCH 048/573] hwmon: (max1619) Improve chip detection code Bail out immediately if reading any of the registers used for chip detection fails, or if it returns an unexpected value. Drop all log messages from detection code. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/max1619.c | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/drivers/hwmon/max1619.c b/drivers/hwmon/max1619.c index 72e17afb22f0..9b6d03cff4df 100644 --- a/drivers/hwmon/max1619.c +++ b/drivers/hwmon/max1619.c @@ -260,31 +260,27 @@ static int max1619_detect(struct i2c_client *client, struct i2c_board_info *info) { struct i2c_adapter *adapter = client->adapter; - u8 reg_config, reg_convrate, reg_status, man_id, chip_id; + int regval; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) return -ENODEV; - /* detection */ - reg_config = i2c_smbus_read_byte_data(client, MAX1619_REG_CONFIG); - reg_convrate = i2c_smbus_read_byte_data(client, MAX1619_REG_CONVRATE); - reg_status = i2c_smbus_read_byte_data(client, MAX1619_REG_STATUS); - if ((reg_config & 0x03) != 0x00 - || reg_convrate > 0x07 || (reg_status & 0x61) != 0x00) { - dev_dbg(&adapter->dev, "MAX1619 detection failed at 0x%02x\n", - client->addr); + regval = i2c_smbus_read_byte_data(client, MAX1619_REG_CONFIG); + if (regval < 0 || (regval & 0x03)) + return -ENODEV; + regval = i2c_smbus_read_byte_data(client, MAX1619_REG_CONVRATE); + if (regval < 0 || regval > 0x07) + return -ENODEV; + regval = i2c_smbus_read_byte_data(client, MAX1619_REG_STATUS); + if (regval < 0 || (regval & 0x61)) return -ENODEV; - } - /* identification */ - man_id = i2c_smbus_read_byte_data(client, MAX1619_REG_MAN_ID); - chip_id = i2c_smbus_read_byte_data(client, MAX1619_REG_CHIP_ID); - if (man_id != 0x4D || chip_id != 0x04) { - dev_info(&adapter->dev, - "Unsupported chip (man_id=0x%02X, chip_id=0x%02X).\n", - man_id, chip_id); + regval = i2c_smbus_read_byte_data(client, MAX1619_REG_MAN_ID); + if (regval != 0x4d) + return -ENODEV; + regval = i2c_smbus_read_byte_data(client, MAX1619_REG_CHIP_ID); + if (regval != 0x04) return -ENODEV; - } strscpy(info->type, "max1619", I2C_NAME_SIZE); From b034a90b2745e43b4a85b56dc5fd7a6fa1a21f31 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Wed, 31 Jul 2024 13:12:41 -0600 Subject: [PATCH 049/573] gpio: Use of_property_present() Use of_property_present() to test for property presence rather than of_find_property(). This is part of a larger effort to remove callers of of_find_property() and similar functions. of_find_property() leaks the DT struct property and data pointers which is a problem for dynamically allocated nodes which may be freed. Signed-off-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20240731191312.1710417-3-robh@kernel.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-of.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index f6af5e7be4d1..6683e531df52 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -1058,13 +1058,13 @@ static int of_gpiochip_add_pin_range(struct gpio_chip *chip) int index = 0, ret, trim; const char *name; static const char group_names_propname[] = "gpio-ranges-group-names"; - struct property *group_names; + bool has_group_names; np = dev_of_node(&chip->gpiodev->dev); if (!np) return 0; - group_names = of_find_property(np, group_names_propname, NULL); + has_group_names = of_property_present(np, group_names_propname); for (;; index++) { ret = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3, @@ -1085,7 +1085,7 @@ static int of_gpiochip_add_pin_range(struct gpio_chip *chip) if (pinspec.args[2]) { /* npins != 0: linear range */ - if (group_names) { + if (has_group_names) { of_property_read_string_index(np, group_names_propname, index, &name); @@ -1123,7 +1123,7 @@ static int of_gpiochip_add_pin_range(struct gpio_chip *chip) break; } - if (!group_names) { + if (!has_group_names) { pr_err("%pOF: GPIO group range requested but no %s property.\n", np, group_names_propname); break; From 312fc21c86824426b27ac9aa0889426ab10eae49 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Mon, 22 Jul 2024 15:10:57 +0300 Subject: [PATCH 050/573] dt-bindings: mfd: Add Analog Devices ADP5585 The ADP5585 is a 10/11 input/output port expander with a built in keypad matrix decoder, programmable logic, reset generator, and PWM generator. These bindings model the device as an MFD, and support the GPIO expander and PWM functions. These bindings support the GPIO and PWM functions. Drop the existing adi,adp5585 and adi,adp5585-02 compatible strings from trivial-devices.yaml. They have been added there by mistake as the driver that was submitted at the same time used different compatible strings. We can take them over safely. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Laurent Pinchart Link: https://lore.kernel.org/r/20240722121100.2855-2-laurent.pinchart@ideasonboard.com Signed-off-by: Lee Jones --- .../devicetree/bindings/mfd/adi,adp5585.yaml | 92 +++++++++++++++++++ .../devicetree/bindings/trivial-devices.yaml | 4 - MAINTAINERS | 7 ++ 3 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 Documentation/devicetree/bindings/mfd/adi,adp5585.yaml diff --git a/Documentation/devicetree/bindings/mfd/adi,adp5585.yaml b/Documentation/devicetree/bindings/mfd/adi,adp5585.yaml new file mode 100644 index 000000000000..f9c069f8534b --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/adi,adp5585.yaml @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/adi,adp5585.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices ADP5585 Keypad Decoder and I/O Expansion + +maintainers: + - Laurent Pinchart + +description: + The ADP5585 is a 10/11 input/output port expander with a built in keypad + matrix decoder, programmable logic, reset generator, and PWM generator. + +properties: + compatible: + items: + - enum: + - adi,adp5585-00 # Default + - adi,adp5585-01 # 11 GPIOs + - adi,adp5585-02 # No pull-up resistors by default on special pins + - adi,adp5585-03 # Alternate I2C address + - adi,adp5585-04 # Pull-down resistors on all pins by default + - const: adi,adp5585 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + vdd-supply: true + + gpio-controller: true + + '#gpio-cells': + const: 2 + + gpio-reserved-ranges: true + + "#pwm-cells": + const: 3 + +required: + - compatible + - reg + - gpio-controller + - "#gpio-cells" + - "#pwm-cells" + +allOf: + - if: + properties: + compatible: + contains: + const: adi,adp5585-01 + then: + properties: + gpio-reserved-ranges: false + else: + properties: + gpio-reserved-ranges: + maxItems: 1 + items: + items: + - const: 5 + - const: 1 + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + io-expander@34 { + compatible = "adi,adp5585-00", "adi,adp5585"; + reg = <0x34>; + + vdd-supply = <®_3v3>; + + gpio-controller; + #gpio-cells = <2>; + gpio-reserved-ranges = <5 1>; + + #pwm-cells = <3>; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 7913ca9b6b54..a2e0fd21a320 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -38,10 +38,6 @@ properties: - ad,adm9240 # AD5110 - Nonvolatile Digital Potentiometer - adi,ad5110 - # Analog Devices ADP5585 Keypad Decoder and I/O Expansion - - adi,adp5585 - # Analog Devices ADP5585 Keypad Decoder and I/O Expansion with support for Row5 - - adi,adp5585-02 # Analog Devices ADP5589 Keypad Decoder and I/O Expansion - adi,adp5589 # Analog Devices LT7182S Dual Channel 6A, 20V PolyPhase Step-Down Silent Switcher diff --git a/MAINTAINERS b/MAINTAINERS index 42decde38320..9c381b4a1db4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -537,6 +537,13 @@ F: drivers/leds/leds-adp5520.c F: drivers/mfd/adp5520.c F: drivers/video/backlight/adp5520_bl.c +ADP5585 GPIO EXPANDER, PWM AND KEYPAD CONTROLLER DRIVER +M: Laurent Pinchart +L: linux-gpio@vger.kernel.org +L: linux-pwm@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/*/adi,adp5585*.yaml + ADP5588 QWERTY KEYPAD AND IO EXPANDER DRIVER (ADP5588/ADP5587) M: Michael Hennerich S: Supported From 480a8ad683d7a54e8d38c9c7778fb6b15aac241a Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Mon, 22 Jul 2024 15:10:58 +0300 Subject: [PATCH 051/573] mfd: adp5585: Add Analog Devices ADP5585 core support The ADP5585 is a 10/11 input/output port expander with a built in keypad matrix decoder, programmable logic, reset generator, and PWM generator. This driver supports the chip by modelling it as an MFD device, with two child devices for the GPIO and PWM functions. The driver is derived from an initial implementation from NXP, available in commit 8059835bee19 ("MLK-25917-1 mfd: adp5585: add ADI adp5585 core support") in their BSP kernel tree. It has been extensively rewritten. Signed-off-by: Haibo Chen Signed-off-by: Laurent Pinchart Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20240722121100.2855-3-laurent.pinchart@ideasonboard.com Signed-off-by: Lee Jones --- MAINTAINERS | 2 + drivers/mfd/Kconfig | 12 +++ drivers/mfd/Makefile | 1 + drivers/mfd/adp5585.c | 205 ++++++++++++++++++++++++++++++++++++ include/linux/mfd/adp5585.h | 126 ++++++++++++++++++++++ 5 files changed, 346 insertions(+) create mode 100644 drivers/mfd/adp5585.c create mode 100644 include/linux/mfd/adp5585.h diff --git a/MAINTAINERS b/MAINTAINERS index 9c381b4a1db4..af152de0436b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -543,6 +543,8 @@ L: linux-gpio@vger.kernel.org L: linux-pwm@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/*/adi,adp5585*.yaml +F: drivers/mfd/adp5585.c +F: include/linux/mfd/adp5585.h ADP5588 QWERTY KEYPAD AND IO EXPANDER DRIVER (ADP5588/ADP5587) M: Michael Hennerich diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index bc8be2e593b6..f9325bcce1b9 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -20,6 +20,18 @@ config MFD_CS5535 This is the core driver for CS5535/CS5536 MFD functions. This is necessary for using the board's GPIO and MFGPT functionality. +config MFD_ADP5585 + tristate "Analog Devices ADP5585 keypad decoder and I/O expander driver" + select MFD_CORE + select REGMAP_I2C + depends on I2C + depends on OF || COMPILE_TEST + help + Say yes here to add support for the Analog Devices ADP5585 GPIO + expander, PWM and keypad controller. This includes the I2C driver and + the core APIs _only_, you have to select individual components like + the GPIO and PWM functions under the corresponding menus. + config MFD_ALTERA_A10SR bool "Altera Arria10 DevKit System Resource chip" depends on ARCH_INTEL_SOCFPGA && SPI_MASTER=y && OF diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 02b651cd7535..2a9f91e81af8 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -193,6 +193,7 @@ obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o obj-$(CONFIG_PMIC_ADP5520) += adp5520.o +obj-$(CONFIG_MFD_ADP5585) += adp5585.o obj-$(CONFIG_MFD_KEMPLD) += kempld-core.o obj-$(CONFIG_MFD_INTEL_QUARK_I2C_GPIO) += intel_quark_i2c_gpio.o obj-$(CONFIG_LPC_SCH) += lpc_sch.o diff --git a/drivers/mfd/adp5585.c b/drivers/mfd/adp5585.c new file mode 100644 index 000000000000..160e0b38106a --- /dev/null +++ b/drivers/mfd/adp5585.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices ADP5585 I/O expander, PWM controller and keypad controller + * + * Copyright 2022 NXP + * Copyright 2024 Ideas on Board Oy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct mfd_cell adp5585_devs[] = { + { .name = "adp5585-gpio", }, + { .name = "adp5585-pwm", }, +}; + +static const struct regmap_range adp5585_volatile_ranges[] = { + regmap_reg_range(ADP5585_ID, ADP5585_GPI_STATUS_B), +}; + +static const struct regmap_access_table adp5585_volatile_regs = { + .yes_ranges = adp5585_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(adp5585_volatile_ranges), +}; + +/* + * Chip variants differ in the default configuration of pull-up and pull-down + * resistors, and therefore have different default register values: + * + * - The -00, -01 and -03 variants (collectively referred to as + * ADP5585_REGMAP_00) have pull-up on all GPIO pins by default. + * - The -02 variant has no default pull-up or pull-down resistors. + * - The -04 variant has default pull-down resistors on all GPIO pins. + */ + +static const u8 adp5585_regmap_defaults_00[ADP5585_MAX_REG + 1] = { + /* 0x00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x08 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x10 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x18 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x30 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x38 */ 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const u8 adp5585_regmap_defaults_02[ADP5585_MAX_REG + 1] = { + /* 0x00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x08 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x10 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, + /* 0x18 */ 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x30 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x38 */ 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const u8 adp5585_regmap_defaults_04[ADP5585_MAX_REG + 1] = { + /* 0x00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x08 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x10 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, + /* 0x18 */ 0x05, 0x55, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x30 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x38 */ 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +enum adp5585_regmap_type { + ADP5585_REGMAP_00, + ADP5585_REGMAP_02, + ADP5585_REGMAP_04, +}; + +static const struct regmap_config adp5585_regmap_configs[] = { + [ADP5585_REGMAP_00] = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ADP5585_MAX_REG, + .volatile_table = &adp5585_volatile_regs, + .cache_type = REGCACHE_MAPLE, + .reg_defaults_raw = adp5585_regmap_defaults_00, + .num_reg_defaults_raw = sizeof(adp5585_regmap_defaults_00), + }, + [ADP5585_REGMAP_02] = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ADP5585_MAX_REG, + .volatile_table = &adp5585_volatile_regs, + .cache_type = REGCACHE_MAPLE, + .reg_defaults_raw = adp5585_regmap_defaults_02, + .num_reg_defaults_raw = sizeof(adp5585_regmap_defaults_02), + }, + [ADP5585_REGMAP_04] = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ADP5585_MAX_REG, + .volatile_table = &adp5585_volatile_regs, + .cache_type = REGCACHE_MAPLE, + .reg_defaults_raw = adp5585_regmap_defaults_04, + .num_reg_defaults_raw = sizeof(adp5585_regmap_defaults_04), + }, +}; + +static int adp5585_i2c_probe(struct i2c_client *i2c) +{ + const struct regmap_config *regmap_config; + struct adp5585_dev *adp5585; + unsigned int id; + int ret; + + adp5585 = devm_kzalloc(&i2c->dev, sizeof(*adp5585), GFP_KERNEL); + if (!adp5585) + return -ENOMEM; + + i2c_set_clientdata(i2c, adp5585); + + regmap_config = i2c_get_match_data(i2c); + adp5585->regmap = devm_regmap_init_i2c(i2c, regmap_config); + if (IS_ERR(adp5585->regmap)) + return dev_err_probe(&i2c->dev, PTR_ERR(adp5585->regmap), + "Failed to initialize register map\n"); + + ret = regmap_read(adp5585->regmap, ADP5585_ID, &id); + if (ret) + return dev_err_probe(&i2c->dev, ret, + "Failed to read device ID\n"); + + if ((id & ADP5585_MAN_ID_MASK) != ADP5585_MAN_ID_VALUE) + return dev_err_probe(&i2c->dev, -ENODEV, + "Invalid device ID 0x%02x\n", id); + + ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, + adp5585_devs, ARRAY_SIZE(adp5585_devs), + NULL, 0, NULL); + if (ret) + return dev_err_probe(&i2c->dev, ret, + "Failed to add child devices\n"); + + return 0; +} + +static int adp5585_suspend(struct device *dev) +{ + struct adp5585_dev *adp5585 = dev_get_drvdata(dev); + + regcache_cache_only(adp5585->regmap, true); + + return 0; +} + +static int adp5585_resume(struct device *dev) +{ + struct adp5585_dev *adp5585 = dev_get_drvdata(dev); + + regcache_cache_only(adp5585->regmap, false); + regcache_mark_dirty(adp5585->regmap); + + return regcache_sync(adp5585->regmap); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(adp5585_pm, adp5585_suspend, adp5585_resume); + +static const struct of_device_id adp5585_of_match[] = { + { + .compatible = "adi,adp5585-00", + .data = &adp5585_regmap_configs[ADP5585_REGMAP_00], + }, { + .compatible = "adi,adp5585-01", + .data = &adp5585_regmap_configs[ADP5585_REGMAP_00], + }, { + .compatible = "adi,adp5585-02", + .data = &adp5585_regmap_configs[ADP5585_REGMAP_02], + }, { + .compatible = "adi,adp5585-03", + .data = &adp5585_regmap_configs[ADP5585_REGMAP_00], + }, { + .compatible = "adi,adp5585-04", + .data = &adp5585_regmap_configs[ADP5585_REGMAP_04], + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, adp5585_of_match); + +static struct i2c_driver adp5585_i2c_driver = { + .driver = { + .name = "adp5585", + .of_match_table = adp5585_of_match, + .pm = pm_sleep_ptr(&adp5585_pm), + }, + .probe = adp5585_i2c_probe, +}; +module_i2c_driver(adp5585_i2c_driver); + +MODULE_DESCRIPTION("ADP5585 core driver"); +MODULE_AUTHOR("Haibo Chen "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/adp5585.h b/include/linux/mfd/adp5585.h new file mode 100644 index 000000000000..016033cd68e4 --- /dev/null +++ b/include/linux/mfd/adp5585.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Analog Devices ADP5585 I/O expander, PWM controller and keypad controller + * + * Copyright 2022 NXP + * Copyright 2024 Ideas on Board Oy + */ + +#ifndef __MFD_ADP5585_H_ +#define __MFD_ADP5585_H_ + +#include + +#define ADP5585_ID 0x00 +#define ADP5585_MAN_ID_VALUE 0x20 +#define ADP5585_MAN_ID_MASK GENMASK(7, 4) +#define ADP5585_INT_STATUS 0x01 +#define ADP5585_STATUS 0x02 +#define ADP5585_FIFO_1 0x03 +#define ADP5585_FIFO_2 0x04 +#define ADP5585_FIFO_3 0x05 +#define ADP5585_FIFO_4 0x06 +#define ADP5585_FIFO_5 0x07 +#define ADP5585_FIFO_6 0x08 +#define ADP5585_FIFO_7 0x09 +#define ADP5585_FIFO_8 0x0a +#define ADP5585_FIFO_9 0x0b +#define ADP5585_FIFO_10 0x0c +#define ADP5585_FIFO_11 0x0d +#define ADP5585_FIFO_12 0x0e +#define ADP5585_FIFO_13 0x0f +#define ADP5585_FIFO_14 0x10 +#define ADP5585_FIFO_15 0x11 +#define ADP5585_FIFO_16 0x12 +#define ADP5585_GPI_INT_STAT_A 0x13 +#define ADP5585_GPI_INT_STAT_B 0x14 +#define ADP5585_GPI_STATUS_A 0x15 +#define ADP5585_GPI_STATUS_B 0x16 +#define ADP5585_RPULL_CONFIG_A 0x17 +#define ADP5585_RPULL_CONFIG_B 0x18 +#define ADP5585_RPULL_CONFIG_C 0x19 +#define ADP5585_RPULL_CONFIG_D 0x1a +#define ADP5585_Rx_PULL_CFG_PU_300K 0 +#define ADP5585_Rx_PULL_CFG_PD_300K 1 +#define ADP5585_Rx_PULL_CFG_PU_100K 2 +#define ADP5585_Rx_PULL_CFG_DISABLE 3 +#define ADP5585_Rx_PULL_CFG_MASK 3 +#define ADP5585_GPI_INT_LEVEL_A 0x1b +#define ADP5585_GPI_INT_LEVEL_B 0x1c +#define ADP5585_GPI_EVENT_EN_A 0x1d +#define ADP5585_GPI_EVENT_EN_B 0x1e +#define ADP5585_GPI_INTERRUPT_EN_A 0x1f +#define ADP5585_GPI_INTERRUPT_EN_B 0x20 +#define ADP5585_DEBOUNCE_DIS_A 0x21 +#define ADP5585_DEBOUNCE_DIS_B 0x22 +#define ADP5585_GPO_DATA_OUT_A 0x23 +#define ADP5585_GPO_DATA_OUT_B 0x24 +#define ADP5585_GPO_OUT_MODE_A 0x25 +#define ADP5585_GPO_OUT_MODE_B 0x26 +#define ADP5585_GPIO_DIRECTION_A 0x27 +#define ADP5585_GPIO_DIRECTION_B 0x28 +#define ADP5585_RESET1_EVENT_A 0x29 +#define ADP5585_RESET1_EVENT_B 0x2a +#define ADP5585_RESET1_EVENT_C 0x2b +#define ADP5585_RESET2_EVENT_A 0x2c +#define ADP5585_RESET2_EVENT_B 0x2d +#define ADP5585_RESET_CFG 0x2e +#define ADP5585_PWM_OFFT_LOW 0x2f +#define ADP5585_PWM_OFFT_HIGH 0x30 +#define ADP5585_PWM_ONT_LOW 0x31 +#define ADP5585_PWM_ONT_HIGH 0x32 +#define ADP5585_PWM_CFG 0x33 +#define ADP5585_PWM_IN_AND BIT(2) +#define ADP5585_PWM_MODE BIT(1) +#define ADP5585_PWM_EN BIT(0) +#define ADP5585_LOGIC_CFG 0x34 +#define ADP5585_LOGIC_FF_CFG 0x35 +#define ADP5585_LOGIC_INT_EVENT_EN 0x36 +#define ADP5585_POLL_PTIME_CFG 0x37 +#define ADP5585_PIN_CONFIG_A 0x38 +#define ADP5585_PIN_CONFIG_B 0x39 +#define ADP5585_PIN_CONFIG_C 0x3a +#define ADP5585_PULL_SELECT BIT(7) +#define ADP5585_C4_EXTEND_CFG_GPIO11 (0U << 6) +#define ADP5585_C4_EXTEND_CFG_RESET2 (1U << 6) +#define ADP5585_C4_EXTEND_CFG_MASK GENMASK(6, 6) +#define ADP5585_R4_EXTEND_CFG_GPIO5 (0U << 5) +#define ADP5585_R4_EXTEND_CFG_RESET1 (1U << 5) +#define ADP5585_R4_EXTEND_CFG_MASK GENMASK(5, 5) +#define ADP5585_R3_EXTEND_CFG_GPIO4 (0U << 2) +#define ADP5585_R3_EXTEND_CFG_LC (1U << 2) +#define ADP5585_R3_EXTEND_CFG_PWM_OUT (2U << 2) +#define ADP5585_R3_EXTEND_CFG_MASK GENMASK(3, 2) +#define ADP5585_R0_EXTEND_CFG_GPIO1 (0U << 0) +#define ADP5585_R0_EXTEND_CFG_LY (1U << 0) +#define ADP5585_R0_EXTEND_CFG_MASK GENMASK(0, 0) +#define ADP5585_GENERAL_CFG 0x3b +#define ADP5585_OSC_EN BIT(7) +#define ADP5585_OSC_FREQ_50KHZ (0U << 5) +#define ADP5585_OSC_FREQ_100KHZ (1U << 5) +#define ADP5585_OSC_FREQ_200KHZ (2U << 5) +#define ADP5585_OSC_FREQ_500KHZ (3U << 5) +#define ADP5585_OSC_FREQ_MASK GENMASK(6, 5) +#define ADP5585_INT_CFG BIT(1) +#define ADP5585_RST_CFG BIT(0) +#define ADP5585_INT_EN 0x3c + +#define ADP5585_MAX_REG ADP5585_INT_EN + +/* + * Bank 0 covers pins "GPIO 1/R0" to "GPIO 6/R5", numbered 0 to 5 by the + * driver, and bank 1 covers pins "GPIO 7/C0" to "GPIO 11/C4", numbered 6 to + * 10. Some variants of the ADP5585 don't support "GPIO 6/R5". As the driver + * uses identical GPIO numbering for all variants to avoid confusion, GPIO 5 is + * marked as reserved in the device tree for variants that don't support it. + */ +#define ADP5585_BANK(n) ((n) >= 6 ? 1 : 0) +#define ADP5585_BIT(n) ((n) >= 6 ? BIT((n) - 6) : BIT(n)) + +struct regmap; + +struct adp5585_dev { + struct regmap *regmap; +}; + +#endif From 738bbc660cae6437dda214d0d97ae68d39479fcd Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Mon, 22 Jul 2024 15:10:59 +0300 Subject: [PATCH 052/573] gpio: adp5585: Add Analog Devices ADP5585 support The ADP5585 is a 10/11 input/output port expander with a built in keypad matrix decoder, programmable logic, reset generator, and PWM generator. This driver supports the GPIO function using the platform device registered by the core MFD driver. The driver is derived from an initial implementation from NXP, available in commit 451f61b46b76 ("MLK-25917-2 gpio: adp5585-gpio: add adp5585-gpio support") in their BSP kernel tree. It has been extensively rewritten. Signed-off-by: Haibo Chen Reviewed-by: Linus Walleij Acked-by: Bartosz Golaszewski Co-developed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20240722121100.2855-4-laurent.pinchart@ideasonboard.com Signed-off-by: Lee Jones --- MAINTAINERS | 1 + drivers/gpio/Kconfig | 7 ++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-adp5585.c | 229 ++++++++++++++++++++++++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 drivers/gpio/gpio-adp5585.c diff --git a/MAINTAINERS b/MAINTAINERS index af152de0436b..d34b599e9413 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -543,6 +543,7 @@ L: linux-gpio@vger.kernel.org L: linux-pwm@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/*/adi,adp5585*.yaml +F: drivers/gpio/gpio-adp5585.c F: drivers/mfd/adp5585.c F: include/linux/mfd/adp5585.h diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 58f43bcced7c..d93cd4f722b4 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1233,6 +1233,13 @@ config GPIO_ADP5520 This option enables support for on-chip GPIO found on Analog Devices ADP5520 PMICs. +config GPIO_ADP5585 + tristate "GPIO Support for ADP5585" + depends on MFD_ADP5585 + help + This option enables support for the GPIO function found in the Analog + Devices ADP5585. + config GPIO_ALTERA_A10SR tristate "Altera Arria10 System Resource GPIO" depends on MFD_ALTERA_A10SR diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 64dd6d9d730d..1429e8c0229b 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_GPIO_74X164) += gpio-74x164.o obj-$(CONFIG_GPIO_74XX_MMIO) += gpio-74xx-mmio.o obj-$(CONFIG_GPIO_ADNP) += gpio-adnp.o obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o +obj-$(CONFIG_GPIO_ADP5585) += gpio-adp5585.o obj-$(CONFIG_GPIO_AGGREGATOR) += gpio-aggregator.o obj-$(CONFIG_GPIO_ALTERA_A10SR) += gpio-altera-a10sr.o obj-$(CONFIG_GPIO_ALTERA) += gpio-altera.o diff --git a/drivers/gpio/gpio-adp5585.c b/drivers/gpio/gpio-adp5585.c new file mode 100644 index 000000000000..000d31f09671 --- /dev/null +++ b/drivers/gpio/gpio-adp5585.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices ADP5585 GPIO driver + * + * Copyright 2022 NXP + * Copyright 2024 Ideas on Board Oy + */ + +#include +#include +#include +#include +#include +#include +#include + +#define ADP5585_GPIO_MAX 11 + +struct adp5585_gpio_dev { + struct gpio_chip gpio_chip; + struct regmap *regmap; +}; + +static int adp5585_gpio_get_direction(struct gpio_chip *chip, unsigned int off) +{ + struct adp5585_gpio_dev *adp5585_gpio = gpiochip_get_data(chip); + unsigned int bank = ADP5585_BANK(off); + unsigned int bit = ADP5585_BIT(off); + unsigned int val; + + regmap_read(adp5585_gpio->regmap, ADP5585_GPIO_DIRECTION_A + bank, &val); + + return val & bit ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; +} + +static int adp5585_gpio_direction_input(struct gpio_chip *chip, unsigned int off) +{ + struct adp5585_gpio_dev *adp5585_gpio = gpiochip_get_data(chip); + unsigned int bank = ADP5585_BANK(off); + unsigned int bit = ADP5585_BIT(off); + + return regmap_clear_bits(adp5585_gpio->regmap, + ADP5585_GPIO_DIRECTION_A + bank, bit); +} + +static int adp5585_gpio_direction_output(struct gpio_chip *chip, unsigned int off, int val) +{ + struct adp5585_gpio_dev *adp5585_gpio = gpiochip_get_data(chip); + unsigned int bank = ADP5585_BANK(off); + unsigned int bit = ADP5585_BIT(off); + int ret; + + ret = regmap_update_bits(adp5585_gpio->regmap, + ADP5585_GPO_DATA_OUT_A + bank, bit, + val ? bit : 0); + if (ret) + return ret; + + return regmap_set_bits(adp5585_gpio->regmap, + ADP5585_GPIO_DIRECTION_A + bank, bit); +} + +static int adp5585_gpio_get_value(struct gpio_chip *chip, unsigned int off) +{ + struct adp5585_gpio_dev *adp5585_gpio = gpiochip_get_data(chip); + unsigned int bank = ADP5585_BANK(off); + unsigned int bit = ADP5585_BIT(off); + unsigned int reg; + unsigned int val; + + /* + * The input status register doesn't reflect the pin state when the + * GPIO is configured as an output. Check the direction, and read the + * input status from GPI_STATUS or output value from GPO_DATA_OUT + * accordingly. + * + * We don't need any locking, as concurrent access to the same GPIO + * isn't allowed by the GPIO API, so there's no risk of the + * .direction_input(), .direction_output() or .set() operations racing + * with this. + */ + regmap_read(adp5585_gpio->regmap, ADP5585_GPIO_DIRECTION_A + bank, &val); + reg = val & bit ? ADP5585_GPO_DATA_OUT_A : ADP5585_GPI_STATUS_A; + regmap_read(adp5585_gpio->regmap, reg + bank, &val); + + return !!(val & bit); +} + +static void adp5585_gpio_set_value(struct gpio_chip *chip, unsigned int off, int val) +{ + struct adp5585_gpio_dev *adp5585_gpio = gpiochip_get_data(chip); + unsigned int bank = ADP5585_BANK(off); + unsigned int bit = ADP5585_BIT(off); + + regmap_update_bits(adp5585_gpio->regmap, ADP5585_GPO_DATA_OUT_A + bank, + bit, val ? bit : 0); +} + +static int adp5585_gpio_set_bias(struct adp5585_gpio_dev *adp5585_gpio, + unsigned int off, unsigned int bias) +{ + unsigned int bit, reg, mask, val; + + /* + * The bias configuration fields are 2 bits wide and laid down in + * consecutive registers ADP5585_RPULL_CONFIG_*, with a hole of 4 bits + * after R5. + */ + bit = off * 2 + (off > 5 ? 4 : 0); + reg = ADP5585_RPULL_CONFIG_A + bit / 8; + mask = ADP5585_Rx_PULL_CFG_MASK << (bit % 8); + val = bias << (bit % 8); + + return regmap_update_bits(adp5585_gpio->regmap, reg, mask, val); +} + +static int adp5585_gpio_set_drive(struct adp5585_gpio_dev *adp5585_gpio, + unsigned int off, enum pin_config_param drive) +{ + unsigned int bank = ADP5585_BANK(off); + unsigned int bit = ADP5585_BIT(off); + + return regmap_update_bits(adp5585_gpio->regmap, + ADP5585_GPO_OUT_MODE_A + bank, bit, + drive == PIN_CONFIG_DRIVE_OPEN_DRAIN ? bit : 0); +} + +static int adp5585_gpio_set_debounce(struct adp5585_gpio_dev *adp5585_gpio, + unsigned int off, unsigned int debounce) +{ + unsigned int bank = ADP5585_BANK(off); + unsigned int bit = ADP5585_BIT(off); + + return regmap_update_bits(adp5585_gpio->regmap, + ADP5585_DEBOUNCE_DIS_A + bank, bit, + debounce ? 0 : bit); +} + +static int adp5585_gpio_set_config(struct gpio_chip *chip, unsigned int off, + unsigned long config) +{ + struct adp5585_gpio_dev *adp5585_gpio = gpiochip_get_data(chip); + enum pin_config_param param = pinconf_to_config_param(config); + u32 arg = pinconf_to_config_argument(config); + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + return adp5585_gpio_set_bias(adp5585_gpio, off, + ADP5585_Rx_PULL_CFG_DISABLE); + + case PIN_CONFIG_BIAS_PULL_DOWN: + return adp5585_gpio_set_bias(adp5585_gpio, off, arg ? + ADP5585_Rx_PULL_CFG_PD_300K : + ADP5585_Rx_PULL_CFG_DISABLE); + + case PIN_CONFIG_BIAS_PULL_UP: + return adp5585_gpio_set_bias(adp5585_gpio, off, arg ? + ADP5585_Rx_PULL_CFG_PU_300K : + ADP5585_Rx_PULL_CFG_DISABLE); + + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + case PIN_CONFIG_DRIVE_PUSH_PULL: + return adp5585_gpio_set_drive(adp5585_gpio, off, param); + + case PIN_CONFIG_INPUT_DEBOUNCE: + return adp5585_gpio_set_debounce(adp5585_gpio, off, arg); + + default: + return -ENOTSUPP; + }; +} + +static int adp5585_gpio_probe(struct platform_device *pdev) +{ + struct adp5585_dev *adp5585 = dev_get_drvdata(pdev->dev.parent); + struct adp5585_gpio_dev *adp5585_gpio; + struct device *dev = &pdev->dev; + struct gpio_chip *gc; + int ret; + + adp5585_gpio = devm_kzalloc(dev, sizeof(*adp5585_gpio), GFP_KERNEL); + if (!adp5585_gpio) + return -ENOMEM; + + adp5585_gpio->regmap = adp5585->regmap; + + device_set_of_node_from_dev(dev, dev->parent); + + gc = &adp5585_gpio->gpio_chip; + gc->parent = dev; + gc->get_direction = adp5585_gpio_get_direction; + gc->direction_input = adp5585_gpio_direction_input; + gc->direction_output = adp5585_gpio_direction_output; + gc->get = adp5585_gpio_get_value; + gc->set = adp5585_gpio_set_value; + gc->set_config = adp5585_gpio_set_config; + gc->can_sleep = true; + + gc->base = -1; + gc->ngpio = ADP5585_GPIO_MAX; + gc->label = pdev->name; + gc->owner = THIS_MODULE; + + ret = devm_gpiochip_add_data(dev, &adp5585_gpio->gpio_chip, + adp5585_gpio); + if (ret) + return dev_err_probe(dev, ret, "failed to add GPIO chip\n"); + + return 0; +} + +static const struct platform_device_id adp5585_gpio_id_table[] = { + { "adp5585-gpio" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, adp5585_gpio_id_table); + +static struct platform_driver adp5585_gpio_driver = { + .driver = { + .name = "adp5585-gpio", + }, + .probe = adp5585_gpio_probe, + .id_table = adp5585_gpio_id_table, +}; +module_platform_driver(adp5585_gpio_driver); + +MODULE_AUTHOR("Haibo Chen "); +MODULE_DESCRIPTION("GPIO ADP5585 Driver"); +MODULE_LICENSE("GPL"); From e9b503879fd2b6332eaf8b719d1e07199fc70c6b Mon Sep 17 00:00:00 2001 From: Clark Wang Date: Mon, 22 Jul 2024 15:11:00 +0300 Subject: [PATCH 053/573] pwm: adp5585: Add Analog Devices ADP5585 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ADP5585 is a 10/11 input/output port expander with a built in keypad matrix decoder, programmable logic, reset generator, and PWM generator. This driver supports the PWM function using the platform device registered by the core MFD driver. The driver is derived from an initial implementation from NXP, available in commit 113113742208 ("MLK-25922-1 pwm: adp5585: add adp5585 PWM support") in their BSP kernel tree. It has been extensively rewritten. Signed-off-by: Clark Wang Reviewed-by: Frank Li Co-developed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart Acked-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20240722121100.2855-5-laurent.pinchart@ideasonboard.com Signed-off-by: Lee Jones --- MAINTAINERS | 1 + drivers/pwm/Kconfig | 7 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-adp5585.c | 184 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 drivers/pwm/pwm-adp5585.c diff --git a/MAINTAINERS b/MAINTAINERS index d34b599e9413..fa2c8d902122 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -545,6 +545,7 @@ S: Maintained F: Documentation/devicetree/bindings/*/adi,adp5585*.yaml F: drivers/gpio/gpio-adp5585.c F: drivers/mfd/adp5585.c +F: drivers/pwm/pwm-adp5585.c F: include/linux/mfd/adp5585.h ADP5588 QWERTY KEYPAD AND IO EXPANDER DRIVER (ADP5588/ADP5587) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 3e53838990f5..0915c1e7df16 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -47,6 +47,13 @@ config PWM_AB8500 To compile this driver as a module, choose M here: the module will be called pwm-ab8500. +config PWM_ADP5585 + tristate "ADP5585 PWM support" + depends on MFD_ADP5585 + help + This option enables support for the PWM function found in the Analog + Devices ADP5585. + config PWM_APPLE tristate "Apple SoC PWM support" depends on ARCH_APPLE || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 0be4f3e6dd43..9081e0c0e9e0 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o +obj-$(CONFIG_PWM_ADP5585) += pwm-adp5585.o obj-$(CONFIG_PWM_APPLE) += pwm-apple.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o diff --git a/drivers/pwm/pwm-adp5585.c b/drivers/pwm/pwm-adp5585.c new file mode 100644 index 000000000000..ed7e8c6bcf32 --- /dev/null +++ b/drivers/pwm/pwm-adp5585.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices ADP5585 PWM driver + * + * Copyright 2022 NXP + * Copyright 2024 Ideas on Board Oy + * + * Limitations: + * - The .apply() operation executes atomically, but may not wait for the + * period to complete (this is not documented and would need to be tested). + * - Disabling the PWM drives the output pin to a low level immediately. + * - The hardware can only generate normal polarity output. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADP5585_PWM_CHAN_NUM 1 + +#define ADP5585_PWM_OSC_FREQ_HZ 1000000U +#define ADP5585_PWM_MIN_PERIOD_NS (2ULL * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) +#define ADP5585_PWM_MAX_PERIOD_NS (2ULL * 0xffff * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) + +static int pwm_adp5585_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct regmap *regmap = pwmchip_get_drvdata(chip); + + /* Configure the R3 pin as PWM output. */ + return regmap_update_bits(regmap, ADP5585_PIN_CONFIG_C, + ADP5585_R3_EXTEND_CFG_MASK, + ADP5585_R3_EXTEND_CFG_PWM_OUT); +} + +static void pwm_adp5585_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct regmap *regmap = pwmchip_get_drvdata(chip); + + regmap_update_bits(regmap, ADP5585_PIN_CONFIG_C, + ADP5585_R3_EXTEND_CFG_MASK, + ADP5585_R3_EXTEND_CFG_GPIO4); +} + +static int pwm_adp5585_apply(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct regmap *regmap = pwmchip_get_drvdata(chip); + u64 period, duty_cycle; + u32 on, off; + __le16 val; + int ret; + + if (!state->enabled) { + regmap_clear_bits(regmap, ADP5585_GENERAL_CFG, ADP5585_OSC_EN); + regmap_clear_bits(regmap, ADP5585_PWM_CFG, ADP5585_PWM_EN); + return 0; + } + + if (state->polarity != PWM_POLARITY_NORMAL) + return -EINVAL; + + if (state->period < ADP5585_PWM_MIN_PERIOD_NS) + return -EINVAL; + + period = min(state->period, ADP5585_PWM_MAX_PERIOD_NS); + duty_cycle = min(state->duty_cycle, period); + + /* + * Compute the on and off time. As the internal oscillator frequency is + * 1MHz, the calculation can be simplified without loss of precision. + */ + on = div_u64(duty_cycle, NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ); + off = div_u64(period, NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) - on; + + val = cpu_to_le16(off); + ret = regmap_bulk_write(regmap, ADP5585_PWM_OFFT_LOW, &val, 2); + if (ret) + return ret; + + val = cpu_to_le16(on); + ret = regmap_bulk_write(regmap, ADP5585_PWM_ONT_LOW, &val, 2); + if (ret) + return ret; + + /* Enable PWM in continuous mode and no external AND'ing. */ + ret = regmap_update_bits(regmap, ADP5585_PWM_CFG, + ADP5585_PWM_IN_AND | ADP5585_PWM_MODE | + ADP5585_PWM_EN, ADP5585_PWM_EN); + if (ret) + return ret; + + return regmap_set_bits(regmap, ADP5585_PWM_CFG, ADP5585_PWM_EN); +} + +static int pwm_adp5585_get_state(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct regmap *regmap = pwmchip_get_drvdata(chip); + unsigned int on, off; + unsigned int val; + __le16 on_off; + int ret; + + ret = regmap_bulk_read(regmap, ADP5585_PWM_OFFT_LOW, &on_off, 2); + if (ret) + return ret; + off = le16_to_cpu(on_off); + + ret = regmap_bulk_read(regmap, ADP5585_PWM_ONT_LOW, &on_off, 2); + if (ret) + return ret; + on = le16_to_cpu(on_off); + + state->duty_cycle = on * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ); + state->period = (on + off) * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ); + + state->polarity = PWM_POLARITY_NORMAL; + + regmap_read(regmap, ADP5585_PWM_CFG, &val); + state->enabled = !!(val & ADP5585_PWM_EN); + + return 0; +} + +static const struct pwm_ops adp5585_pwm_ops = { + .request = pwm_adp5585_request, + .free = pwm_adp5585_free, + .apply = pwm_adp5585_apply, + .get_state = pwm_adp5585_get_state, +}; + +static int adp5585_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct adp5585_dev *adp5585 = dev_get_drvdata(dev->parent); + struct pwm_chip *chip; + int ret; + + chip = devm_pwmchip_alloc(dev, ADP5585_PWM_CHAN_NUM, 0); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + device_set_of_node_from_dev(dev, dev->parent); + + pwmchip_set_drvdata(chip, adp5585->regmap); + chip->ops = &adp5585_pwm_ops; + + ret = devm_pwmchip_add(dev, chip); + if (ret) + return dev_err_probe(dev, ret, "failed to add PWM chip\n"); + + return 0; +} + +static const struct platform_device_id adp5585_pwm_id_table[] = { + { "adp5585-pwm" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, adp5585_pwm_id_table); + +static struct platform_driver adp5585_pwm_driver = { + .driver = { + .name = "adp5585-pwm", + }, + .probe = adp5585_pwm_probe, + .id_table = adp5585_pwm_id_table, +}; +module_platform_driver(adp5585_pwm_driver); + +MODULE_AUTHOR("Xiaoning Wang "); +MODULE_DESCRIPTION("ADP5585 PWM Driver"); +MODULE_LICENSE("GPL"); From 79bd233010859463e46a0a4b3926eaaba25a6110 Mon Sep 17 00:00:00 2001 From: Ben Gainey Date: Tue, 30 Jul 2024 09:44:14 +0100 Subject: [PATCH 054/573] perf: Rename perf_event_context.nr_pending to nr_no_switch_fast. nr_pending counts the number of events in the context that either pending_sigtrap or pending_work, but it is used to prevent taking the fast path in perf_event_context_sched_out. Renamed to reflect what it is used for, rather than what it counts. This change allows using the field to track other event properties that also require skipping the fast path without possible confusion over the name. Signed-off-by: Ben Gainey Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/20240730084417.7693-2-ben.gainey@arm.com --- include/linux/perf_event.h | 5 +++-- kernel/events/core.c | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 6bb0c21d6335..655f66b18418 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -966,12 +966,13 @@ struct perf_event_context { struct rcu_head rcu_head; /* - * Sum (event->pending_work + event->pending_work) + * The count of events for which using the switch-out fast path + * should be avoided. * * The SIGTRAP is targeted at ctx->task, as such it won't do changing * that until the signal is delivered. */ - local_t nr_pending; + local_t nr_no_switch_fast; }; struct perf_cpu_pmu_context { diff --git a/kernel/events/core.c b/kernel/events/core.c index aa3450bdc227..e6cc354a3cee 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -3516,9 +3516,9 @@ perf_event_context_sched_out(struct task_struct *task, struct task_struct *next) perf_ctx_disable(ctx, false); - /* PMIs are disabled; ctx->nr_pending is stable. */ - if (local_read(&ctx->nr_pending) || - local_read(&next_ctx->nr_pending)) { + /* PMIs are disabled; ctx->nr_no_switch_fast is stable. */ + if (local_read(&ctx->nr_no_switch_fast) || + local_read(&next_ctx->nr_no_switch_fast)) { /* * Must not swap out ctx when there's pending * events that rely on the ctx->task relation. @@ -5204,7 +5204,7 @@ static void perf_pending_task_sync(struct perf_event *event) */ if (task_work_cancel(current, head)) { event->pending_work = 0; - local_dec(&event->ctx->nr_pending); + local_dec(&event->ctx->nr_no_switch_fast); return; } @@ -6868,7 +6868,7 @@ static void perf_pending_task(struct callback_head *head) if (event->pending_work) { event->pending_work = 0; perf_sigtrap(event); - local_dec(&event->ctx->nr_pending); + local_dec(&event->ctx->nr_no_switch_fast); rcuwait_wake_up(&event->pending_work_wait); } rcu_read_unlock(); @@ -9740,7 +9740,7 @@ static int __perf_event_overflow(struct perf_event *event, if (!event->pending_work && !task_work_add(current, &event->pending_task, notify_mode)) { event->pending_work = pending_id; - local_inc(&event->ctx->nr_pending); + local_inc(&event->ctx->nr_no_switch_fast); event->pending_addr = 0; if (valid_sample && (data->sample_flags & PERF_SAMPLE_ADDR)) From 7e8b255650fcfa1d05a57e4093d8405e6d8dd488 Mon Sep 17 00:00:00 2001 From: Ben Gainey Date: Tue, 30 Jul 2024 09:44:15 +0100 Subject: [PATCH 055/573] perf: Support PERF_SAMPLE_READ with inherit This change allows events to use PERF_SAMPLE_READ with inherit so long as PERF_SAMPLE_TID is also set. This enables sample based profiling of a group of counters over a hierarchy of processes or threads. This is useful, for example, for collecting per-thread counters/metrics, event based sampling of multiple counters as a unit, access to the enabled and running time when using multiplexing and so on. Prior to this, users were restricted to either collecting aggregate statistics for a multi-threaded/-process application (e.g. with "perf stat"), or to sample individual threads, or to profile the entire system (which requires root or CAP_PERFMON, and may produce much more data than is required). Theoretically a tool could poll for or otherwise monitor thread/process creation and construct whatever events the user is interested in using perf_event_open, for each new thread or process, but this is racy, can lead to file-descriptor exhaustion, and ultimately just replicates the behaviour of inherit, but in userspace. This configuration differs from inherit without PERF_SAMPLE_READ in that the accumulated event count, and consequently any sample (such as if triggered by overflow of sample_period) will be on a per-thread rather than on an aggregate basis. The meaning of read_format::value field of both PERF_RECORD_READ and PERF_RECORD_SAMPLE is changed such that if the sampled event uses this new configuration then the values reported will be per-thread rather than the global aggregate value. This is a change from the existing semantics of read_format (where PERF_SAMPLE_READ is used without inherit), but it is necessary to expose the per-thread counter values, and it avoids reinventing a separate "read_format_thread" field that otherwise replicates the same behaviour. This change should not break existing tools, since this configuration was not previously valid and was rejected by the kernel. Tools that opt into this new mode will need to account for this when calculating the counter delta for a given sample. Tools that wish to have both the per-thread and aggregate value can perform the global aggregation themselves from the per-thread values. The change to read_format::value does not affect existing valid perf_event_attr configurations, nor does it change the behaviour of calls to "read" on an event descriptor. Both continue to report the aggregate value for the entire thread/process hierarchy. The difference between the results reported by "read" and PERF_RECORD_SAMPLE in this new configuration is justified on the basis that it is not (easily) possible for "read" to target a specific thread (the caller only has the fd for the original parent event). Signed-off-by: Ben Gainey Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/20240730084417.7693-3-ben.gainey@arm.com --- include/linux/perf_event.h | 3 +++ kernel/events/core.c | 55 ++++++++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 655f66b18418..701549967c18 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -969,6 +969,9 @@ struct perf_event_context { * The count of events for which using the switch-out fast path * should be avoided. * + * Sum (event->pending_work + events with + * (attr->inherit && (attr->sample_type & PERF_SAMPLE_READ))) + * * The SIGTRAP is targeted at ctx->task, as such it won't do changing * that until the signal is delivered. */ diff --git a/kernel/events/core.c b/kernel/events/core.c index e6cc354a3cee..c01a32687dad 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -1767,6 +1767,14 @@ perf_event_groups_next(struct perf_event *event, struct pmu *pmu) event = rb_entry_safe(rb_next(&event->group_node), \ typeof(*event), group_node)) +/* + * Does the event attribute request inherit with PERF_SAMPLE_READ + */ +static inline bool has_inherit_and_sample_read(struct perf_event_attr *attr) +{ + return attr->inherit && (attr->sample_type & PERF_SAMPLE_READ); +} + /* * Add an event from the lists for its context. * Must be called with ctx->mutex and ctx->lock held. @@ -1797,6 +1805,8 @@ list_add_event(struct perf_event *event, struct perf_event_context *ctx) ctx->nr_user++; if (event->attr.inherit_stat) ctx->nr_stat++; + if (has_inherit_and_sample_read(&event->attr)) + local_inc(&ctx->nr_no_switch_fast); if (event->state > PERF_EVENT_STATE_OFF) perf_cgroup_event_enable(event, ctx); @@ -2021,6 +2031,8 @@ list_del_event(struct perf_event *event, struct perf_event_context *ctx) ctx->nr_user--; if (event->attr.inherit_stat) ctx->nr_stat--; + if (has_inherit_and_sample_read(&event->attr)) + local_dec(&ctx->nr_no_switch_fast); list_del_rcu(&event->event_entry); @@ -3522,6 +3534,11 @@ perf_event_context_sched_out(struct task_struct *task, struct task_struct *next) /* * Must not swap out ctx when there's pending * events that rely on the ctx->task relation. + * + * Likewise, when a context contains inherit + + * SAMPLE_READ events they should be switched + * out using the slow path so that they are + * treated as if they were distinct contexts. */ raw_spin_unlock(&next_ctx->lock); rcu_read_unlock(); @@ -4538,8 +4555,11 @@ unlock: raw_spin_unlock(&ctx->lock); } -static inline u64 perf_event_count(struct perf_event *event) +static inline u64 perf_event_count(struct perf_event *event, bool self) { + if (self) + return local64_read(&event->count); + return local64_read(&event->count) + atomic64_read(&event->child_count); } @@ -5498,7 +5518,7 @@ static u64 __perf_event_read_value(struct perf_event *event, u64 *enabled, u64 * mutex_lock(&event->child_mutex); (void)perf_event_read(event, false); - total += perf_event_count(event); + total += perf_event_count(event, false); *enabled += event->total_time_enabled + atomic64_read(&event->child_total_time_enabled); @@ -5507,7 +5527,7 @@ static u64 __perf_event_read_value(struct perf_event *event, u64 *enabled, u64 * list_for_each_entry(child, &event->child_list, child_list) { (void)perf_event_read(child, false); - total += perf_event_count(child); + total += perf_event_count(child, false); *enabled += child->total_time_enabled; *running += child->total_time_running; } @@ -5589,14 +5609,14 @@ static int __perf_read_group_add(struct perf_event *leader, /* * Write {count,id} tuples for every sibling. */ - values[n++] += perf_event_count(leader); + values[n++] += perf_event_count(leader, false); if (read_format & PERF_FORMAT_ID) values[n++] = primary_event_id(leader); if (read_format & PERF_FORMAT_LOST) values[n++] = atomic64_read(&leader->lost_samples); for_each_sibling_event(sub, leader) { - values[n++] += perf_event_count(sub); + values[n++] += perf_event_count(sub, false); if (read_format & PERF_FORMAT_ID) values[n++] = primary_event_id(sub); if (read_format & PERF_FORMAT_LOST) @@ -6176,7 +6196,7 @@ void perf_event_update_userpage(struct perf_event *event) ++userpg->lock; barrier(); userpg->index = perf_event_index(event); - userpg->offset = perf_event_count(event); + userpg->offset = perf_event_count(event, false); if (userpg->index) userpg->offset -= local64_read(&event->hw.prev_count); @@ -7250,7 +7270,7 @@ static void perf_output_read_one(struct perf_output_handle *handle, u64 values[5]; int n = 0; - values[n++] = perf_event_count(event); + values[n++] = perf_event_count(event, has_inherit_and_sample_read(&event->attr)); if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) { values[n++] = enabled + atomic64_read(&event->child_total_time_enabled); @@ -7268,14 +7288,15 @@ static void perf_output_read_one(struct perf_output_handle *handle, } static void perf_output_read_group(struct perf_output_handle *handle, - struct perf_event *event, - u64 enabled, u64 running) + struct perf_event *event, + u64 enabled, u64 running) { struct perf_event *leader = event->group_leader, *sub; u64 read_format = event->attr.read_format; unsigned long flags; u64 values[6]; int n = 0; + bool self = has_inherit_and_sample_read(&event->attr); /* * Disabling interrupts avoids all counter scheduling @@ -7295,7 +7316,7 @@ static void perf_output_read_group(struct perf_output_handle *handle, (leader->state == PERF_EVENT_STATE_ACTIVE)) leader->pmu->read(leader); - values[n++] = perf_event_count(leader); + values[n++] = perf_event_count(leader, self); if (read_format & PERF_FORMAT_ID) values[n++] = primary_event_id(leader); if (read_format & PERF_FORMAT_LOST) @@ -7310,7 +7331,7 @@ static void perf_output_read_group(struct perf_output_handle *handle, (sub->state == PERF_EVENT_STATE_ACTIVE)) sub->pmu->read(sub); - values[n++] = perf_event_count(sub); + values[n++] = perf_event_count(sub, self); if (read_format & PERF_FORMAT_ID) values[n++] = primary_event_id(sub); if (read_format & PERF_FORMAT_LOST) @@ -7331,6 +7352,10 @@ static void perf_output_read_group(struct perf_output_handle *handle, * The problem is that its both hard and excessively expensive to iterate the * child list, not to mention that its impossible to IPI the children running * on another CPU, from interrupt/NMI context. + * + * Instead the combination of PERF_SAMPLE_READ and inherit will track per-thread + * counts rather than attempting to accumulate some value across all children on + * all cores. */ static void perf_output_read(struct perf_output_handle *handle, struct perf_event *event) @@ -12057,10 +12082,12 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu, local64_set(&hwc->period_left, hwc->sample_period); /* - * We currently do not support PERF_SAMPLE_READ on inherited events. + * We do not support PERF_SAMPLE_READ on inherited events unless + * PERF_SAMPLE_TID is also selected, which allows inherited events to + * collect per-thread samples. * See perf_output_read(). */ - if (attr->inherit && (attr->sample_type & PERF_SAMPLE_READ)) + if (has_inherit_and_sample_read(attr) && !(attr->sample_type & PERF_SAMPLE_TID)) goto err_ns; if (!has_branch_stack(event)) @@ -13084,7 +13111,7 @@ static void sync_child_event(struct perf_event *child_event) perf_event_read_event(child_event, task); } - child_val = perf_event_count(child_event); + child_val = perf_event_count(child_event, false); /* * Add back the child's count to the parent's count: From cfa7f3d2c526c224a6271cc78a4a27a0de06f4f0 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Mon, 29 Jul 2024 10:52:23 -0700 Subject: [PATCH 056/573] perf,x86: avoid missing caller address in stack traces captured in uprobe When tracing user functions with uprobe functionality, it's common to install the probe (e.g., a BPF program) at the first instruction of the function. This is often going to be `push %rbp` instruction in function preamble, which means that within that function frame pointer hasn't been established yet. This leads to consistently missing an actual caller of the traced function, because perf_callchain_user() only records current IP (capturing traced function) and then following frame pointer chain (which would be caller's frame, containing the address of caller's caller). So when we have target_1 -> target_2 -> target_3 call chain and we are tracing an entry to target_3, captured stack trace will report target_1 -> target_3 call chain, which is wrong and confusing. This patch proposes a x86-64-specific heuristic to detect `push %rbp` (`push %ebp` on 32-bit architecture) instruction being traced. Given entire kernel implementation of user space stack trace capturing works under assumption that user space code was compiled with frame pointer register (%rbp/%ebp) preservation, it seems pretty reasonable to use this instruction as a strong indicator that this is the entry to the function. In that case, return address is still pointed to by %rsp/%esp, so we fetch it and add to stack trace before proceeding to unwind the rest using frame pointer-based logic. We also check for `endbr64` (for 64-bit modes) as another common pattern for function entry, as suggested by Josh Poimboeuf. Even if we get this wrong sometimes for uprobes attached not at the function entry, it's OK because stack trace will still be overall meaningful, just with one extra bogus entry. If we don't detect this, we end up with guaranteed to be missing caller function entry in the stack trace, which is worse overall. Signed-off-by: Andrii Nakryiko Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/20240729175223.23914-1-andrii@kernel.org --- arch/x86/events/core.c | 63 +++++++++++++++++++++++++++++++++++++++++ include/linux/uprobes.h | 2 ++ kernel/events/uprobes.c | 2 ++ 3 files changed, 67 insertions(+) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 12f2a0c14d33..e6d133dda70f 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -41,6 +41,8 @@ #include #include #include +#include +#include #include "perf_event.h" @@ -2814,6 +2816,46 @@ static unsigned long get_segment_base(unsigned int segment) return get_desc_base(desc); } +#ifdef CONFIG_UPROBES +/* + * Heuristic-based check if uprobe is installed at the function entry. + * + * Under assumption of user code being compiled with frame pointers, + * `push %rbp/%ebp` is a good indicator that we indeed are. + * + * Similarly, `endbr64` (assuming 64-bit mode) is also a common pattern. + * If we get this wrong, captured stack trace might have one extra bogus + * entry, but the rest of stack trace will still be meaningful. + */ +static bool is_uprobe_at_func_entry(struct pt_regs *regs) +{ + struct arch_uprobe *auprobe; + + if (!current->utask) + return false; + + auprobe = current->utask->auprobe; + if (!auprobe) + return false; + + /* push %rbp/%ebp */ + if (auprobe->insn[0] == 0x55) + return true; + + /* endbr64 (64-bit only) */ + if (user_64bit_mode(regs) && is_endbr(*(u32 *)auprobe->insn)) + return true; + + return false; +} + +#else +static bool is_uprobe_at_func_entry(struct pt_regs *regs) +{ + return false; +} +#endif /* CONFIG_UPROBES */ + #ifdef CONFIG_IA32_EMULATION #include @@ -2825,6 +2867,7 @@ perf_callchain_user32(struct pt_regs *regs, struct perf_callchain_entry_ctx *ent unsigned long ss_base, cs_base; struct stack_frame_ia32 frame; const struct stack_frame_ia32 __user *fp; + u32 ret_addr; if (user_64bit_mode(regs)) return 0; @@ -2834,6 +2877,12 @@ perf_callchain_user32(struct pt_regs *regs, struct perf_callchain_entry_ctx *ent fp = compat_ptr(ss_base + regs->bp); pagefault_disable(); + + /* see perf_callchain_user() below for why we do this */ + if (is_uprobe_at_func_entry(regs) && + !get_user(ret_addr, (const u32 __user *)regs->sp)) + perf_callchain_store(entry, ret_addr); + while (entry->nr < entry->max_stack) { if (!valid_user_frame(fp, sizeof(frame))) break; @@ -2862,6 +2911,7 @@ perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pt_regs *regs { struct stack_frame frame; const struct stack_frame __user *fp; + unsigned long ret_addr; if (perf_guest_state()) { /* TODO: We don't support guest os callchain now */ @@ -2885,6 +2935,19 @@ perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pt_regs *regs return; pagefault_disable(); + + /* + * If we are called from uprobe handler, and we are indeed at the very + * entry to user function (which is normally a `push %rbp` instruction, + * under assumption of application being compiled with frame pointers), + * we should read return address from *regs->sp before proceeding + * to follow frame pointers, otherwise we'll skip immediate caller + * as %rbp is not yet setup. + */ + if (is_uprobe_at_func_entry(regs) && + !get_user(ret_addr, (const unsigned long __user *)regs->sp)) + perf_callchain_store(entry, ret_addr); + while (entry->nr < entry->max_stack) { if (!valid_user_frame(fp, sizeof(frame))) break; diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h index b503fafb7fb3..a270a5892ab4 100644 --- a/include/linux/uprobes.h +++ b/include/linux/uprobes.h @@ -76,6 +76,8 @@ struct uprobe_task { struct uprobe *active_uprobe; unsigned long xol_vaddr; + struct arch_uprobe *auprobe; + struct return_instance *return_instances; unsigned int depth; }; diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index 73cc47708679..f69ecd39b1a8 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -2082,6 +2082,7 @@ static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs) bool need_prep = false; /* prepare return uprobe, when needed */ down_read(&uprobe->register_rwsem); + current->utask->auprobe = &uprobe->arch; for (uc = uprobe->consumers; uc; uc = uc->next) { int rc = 0; @@ -2096,6 +2097,7 @@ static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs) remove &= rc; } + current->utask->auprobe = NULL; if (need_prep && !remove) prepare_uretprobe(uprobe, regs); /* put bp at return */ From 84455e6923c79a37812930787aaa141e82afe315 Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Thu, 1 Aug 2024 15:27:09 +0200 Subject: [PATCH 057/573] uprobes: document the usage of mm->mmap_lock The comment above uprobe_write_opcode() is wrong, unapply_uprobe() calls it under mmap_read_lock() and this is correct. And it is completely unclear why register_for_each_vma() takes mmap_lock for writing, add a comment to explain that mmap_write_lock() is needed to avoid the following race: - A task T hits the bp installed by uprobe and calls find_active_uprobe() - uprobe_unregister() removes this uprobe/bp - T calls find_uprobe() which returns NULL - another uprobe_register() installs the bp at the same address - T calls is_trap_at_addr() which returns true - T returns to handle_swbp() and gets SIGTRAP. Reported-by: Andrii Nakryiko Signed-off-by: Oleg Nesterov Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Jiri Olsa Acked-by: Andrii Nakryiko Acked-by: "Masami Hiramatsu (Google)" Link: https://lore.kernel.org/r/20240801132709.GA8780@redhat.com --- kernel/events/uprobes.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index f69ecd39b1a8..2d1457eee965 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -453,7 +453,7 @@ static int update_ref_ctr(struct uprobe *uprobe, struct mm_struct *mm, * @vaddr: the virtual address to store the opcode. * @opcode: opcode to be written at @vaddr. * - * Called with mm->mmap_lock held for write. + * Called with mm->mmap_lock held for read or write. * Return 0 (success) or a negative errno. */ int uprobe_write_opcode(struct arch_uprobe *auprobe, struct mm_struct *mm, @@ -1046,7 +1046,13 @@ register_for_each_vma(struct uprobe *uprobe, struct uprobe_consumer *new) if (err && is_register) goto free; - + /* + * We take mmap_lock for writing to avoid the race with + * find_active_uprobe() which takes mmap_lock for reading. + * Thus this install_breakpoint() can not make + * is_trap_at_addr() true right after find_uprobe() + * returns NULL in find_active_uprobe(). + */ mmap_write_lock(mm); vma = find_vma(mm, info->vaddr); if (!vma || !valid_vma(vma, is_register) || From 300b05621a3f85621130e356ca6ae90f6a4eec0e Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Thu, 1 Aug 2024 15:27:14 +0200 Subject: [PATCH 058/573] uprobes: is_trap_at_addr: don't use get_user_pages_remote() get_user_pages_remote() and the comment above it make no sense. There is no task_struct passed into get_user_pages_remote() anymore, and nowadays mm_account_fault() increments the current->min/maj_flt counters regardless of FAULT_FLAG_REMOTE. Reported-by: Andrii Nakryiko Signed-off-by: Oleg Nesterov Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Jiri Olsa Acked-by: Andrii Nakryiko Link: https://lore.kernel.org/r/20240801132714.GA8783@redhat.com --- kernel/events/uprobes.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index 2d1457eee965..698bb22f7102 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -2035,13 +2035,7 @@ static int is_trap_at_addr(struct mm_struct *mm, unsigned long vaddr) if (likely(result == 0)) goto out; - /* - * The NULL 'tsk' here ensures that any faults that occur here - * will not be accounted to the task. 'mm' *is* current->mm, - * but we treat this as a 'remote' access since it is - * essentially a kernel access to the memory. - */ - result = get_user_pages_remote(mm, vaddr, 1, FOLL_FORCE, &page, NULL); + result = get_user_pages(vaddr, 1, FOLL_FORCE, &page); if (result < 0) return result; From 7c2bae2d9c27a89280b63ff3567d2dac2d89db28 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Thu, 1 Aug 2024 15:27:19 +0200 Subject: [PATCH 059/573] uprobes: simplify error handling for alloc_uprobe() Return -ENOMEM instead of NULL, which makes caller's error handling just a touch simpler. Signed-off-by: Andrii Nakryiko Signed-off-by: Oleg Nesterov Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: "Masami Hiramatsu (Google)" Reviewed-by: Jiri Olsa Link: https://lore.kernel.org/r/20240801132719.GA8788@redhat.com --- kernel/events/uprobes.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index 698bb22f7102..e9b092acc71b 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -725,7 +725,7 @@ static struct uprobe *alloc_uprobe(struct inode *inode, loff_t offset, uprobe = kzalloc(sizeof(struct uprobe), GFP_KERNEL); if (!uprobe) - return NULL; + return ERR_PTR(-ENOMEM); uprobe->inode = inode; uprobe->offset = offset; @@ -1167,8 +1167,6 @@ static int __uprobe_register(struct inode *inode, loff_t offset, retry: uprobe = alloc_uprobe(inode, offset, ref_ctr_offset); - if (!uprobe) - return -ENOMEM; if (IS_ERR(uprobe)) return PTR_ERR(uprobe); From db61e6a4eee5a7884b2cafeaf407895f253bbaa7 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Thu, 1 Aug 2024 15:27:24 +0200 Subject: [PATCH 060/573] selftests/bpf: fix uprobe.path leak in bpf_testmod testmod_unregister_uprobe() forgets to path_put(&uprobe.path). Signed-off-by: Jiri Olsa Signed-off-by: Oleg Nesterov Signed-off-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/r/20240801132724.GA8791@redhat.com --- tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c index fd28c1157bd3..72f565af4f82 100644 --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c @@ -477,6 +477,7 @@ static void testmod_unregister_uprobe(void) if (uprobe.offset) { uprobe_unregister(d_real_inode(uprobe.path.dentry), uprobe.offset, &uprobe.consumer); + path_put(&uprobe.path); uprobe.offset = 0; } From e04332ebc8ac128fa551e83f1161ab1c094d13a9 Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Thu, 1 Aug 2024 15:27:28 +0200 Subject: [PATCH 061/573] uprobes: kill uprobe_register_refctr() It doesn't make any sense to have 2 versions of _register(). Note that trace_uprobe_enable(), the only user of uprobe_register(), doesn't need to check tu->ref_ctr_offset to decide which one should be used, it could safely pass ref_ctr_offset == 0 to uprobe_register_refctr(). Add this argument to uprobe_register(), update the callers, and kill uprobe_register_refctr(). Signed-off-by: Oleg Nesterov Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Jiri Olsa Acked-by: Andrii Nakryiko Link: https://lore.kernel.org/r/20240801132728.GA8800@redhat.com --- include/linux/uprobes.h | 9 ++----- kernel/events/uprobes.c | 24 +++++-------------- kernel/trace/bpf_trace.c | 8 +++---- kernel/trace/trace_uprobe.c | 7 +----- .../selftests/bpf/bpf_testmod/bpf_testmod.c | 4 ++-- 5 files changed, 15 insertions(+), 37 deletions(-) diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h index a270a5892ab4..788813c0b8fc 100644 --- a/include/linux/uprobes.h +++ b/include/linux/uprobes.h @@ -112,8 +112,7 @@ extern bool is_trap_insn(uprobe_opcode_t *insn); extern unsigned long uprobe_get_swbp_addr(struct pt_regs *regs); extern unsigned long uprobe_get_trap_addr(struct pt_regs *regs); extern int uprobe_write_opcode(struct arch_uprobe *auprobe, struct mm_struct *mm, unsigned long vaddr, uprobe_opcode_t); -extern int uprobe_register(struct inode *inode, loff_t offset, struct uprobe_consumer *uc); -extern int uprobe_register_refctr(struct inode *inode, loff_t offset, loff_t ref_ctr_offset, struct uprobe_consumer *uc); +extern int uprobe_register(struct inode *inode, loff_t offset, loff_t ref_ctr_offset, struct uprobe_consumer *uc); extern int uprobe_apply(struct inode *inode, loff_t offset, struct uprobe_consumer *uc, bool); extern void uprobe_unregister(struct inode *inode, loff_t offset, struct uprobe_consumer *uc); extern int uprobe_mmap(struct vm_area_struct *vma); @@ -154,11 +153,7 @@ static inline void uprobes_init(void) #define uprobe_get_trap_addr(regs) instruction_pointer(regs) static inline int -uprobe_register(struct inode *inode, loff_t offset, struct uprobe_consumer *uc) -{ - return -ENOSYS; -} -static inline int uprobe_register_refctr(struct inode *inode, loff_t offset, loff_t ref_ctr_offset, struct uprobe_consumer *uc) +uprobe_register(struct inode *inode, loff_t offset, loff_t ref_ctr_offset, struct uprobe_consumer *uc) { return -ENOSYS; } diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index e9b092acc71b..3a80154bc4c7 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -1121,25 +1121,26 @@ void uprobe_unregister(struct inode *inode, loff_t offset, struct uprobe_consume EXPORT_SYMBOL_GPL(uprobe_unregister); /* - * __uprobe_register - register a probe + * uprobe_register - register a probe * @inode: the file in which the probe has to be placed. * @offset: offset from the start of the file. + * @ref_ctr_offset: offset of SDT marker / reference counter * @uc: information on howto handle the probe.. * - * Apart from the access refcount, __uprobe_register() takes a creation + * Apart from the access refcount, uprobe_register() takes a creation * refcount (thro alloc_uprobe) if and only if this @uprobe is getting * inserted into the rbtree (i.e first consumer for a @inode:@offset * tuple). Creation refcount stops uprobe_unregister from freeing the * @uprobe even before the register operation is complete. Creation * refcount is released when the last @uc for the @uprobe - * unregisters. Caller of __uprobe_register() is required to keep @inode + * unregisters. Caller of uprobe_register() is required to keep @inode * (and the containing mount) referenced. * * Return errno if it cannot successully install probes * else return 0 (success) */ -static int __uprobe_register(struct inode *inode, loff_t offset, - loff_t ref_ctr_offset, struct uprobe_consumer *uc) +int uprobe_register(struct inode *inode, loff_t offset, loff_t ref_ctr_offset, + struct uprobe_consumer *uc) { struct uprobe *uprobe; int ret; @@ -1189,21 +1190,8 @@ static int __uprobe_register(struct inode *inode, loff_t offset, goto retry; return ret; } - -int uprobe_register(struct inode *inode, loff_t offset, - struct uprobe_consumer *uc) -{ - return __uprobe_register(inode, offset, 0, uc); -} EXPORT_SYMBOL_GPL(uprobe_register); -int uprobe_register_refctr(struct inode *inode, loff_t offset, - loff_t ref_ctr_offset, struct uprobe_consumer *uc) -{ - return __uprobe_register(inode, offset, ref_ctr_offset, uc); -} -EXPORT_SYMBOL_GPL(uprobe_register_refctr); - /* * uprobe_apply - unregister an already registered probe. * @inode: the file in which the probe has to be removed. diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c index cd098846e251..afa909e17824 100644 --- a/kernel/trace/bpf_trace.c +++ b/kernel/trace/bpf_trace.c @@ -3480,10 +3480,10 @@ int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *pr &bpf_uprobe_multi_link_lops, prog); for (i = 0; i < cnt; i++) { - err = uprobe_register_refctr(d_real_inode(link->path.dentry), - uprobes[i].offset, - uprobes[i].ref_ctr_offset, - &uprobes[i].consumer); + err = uprobe_register(d_real_inode(link->path.dentry), + uprobes[i].offset, + uprobes[i].ref_ctr_offset, + &uprobes[i].consumer); if (err) { bpf_uprobe_unregister(&path, uprobes, i); goto error_free; diff --git a/kernel/trace/trace_uprobe.c b/kernel/trace/trace_uprobe.c index c98e3b3386ba..1f590f989c1e 100644 --- a/kernel/trace/trace_uprobe.c +++ b/kernel/trace/trace_uprobe.c @@ -1089,12 +1089,7 @@ static int trace_uprobe_enable(struct trace_uprobe *tu, filter_func_t filter) tu->consumer.filter = filter; tu->inode = d_real_inode(tu->path.dentry); - if (tu->ref_ctr_offset) - ret = uprobe_register_refctr(tu->inode, tu->offset, - tu->ref_ctr_offset, &tu->consumer); - else - ret = uprobe_register(tu->inode, tu->offset, &tu->consumer); - + ret = uprobe_register(tu->inode, tu->offset, tu->ref_ctr_offset, &tu->consumer); if (ret) tu->inode = NULL; diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c index 72f565af4f82..55f6905de743 100644 --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c @@ -458,8 +458,8 @@ static int testmod_register_uprobe(loff_t offset) if (err) goto out; - err = uprobe_register_refctr(d_real_inode(uprobe.path.dentry), - offset, 0, &uprobe.consumer); + err = uprobe_register(d_real_inode(uprobe.path.dentry), + offset, 0, &uprobe.consumer); if (err) path_put(&uprobe.path); else From 3c83a9ad0295eb63bdeb81d821b8c3b9417fbcac Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Thu, 1 Aug 2024 15:27:34 +0200 Subject: [PATCH 062/573] uprobes: make uprobe_register() return struct uprobe * This way uprobe_unregister() and uprobe_apply() can use "struct uprobe *" rather than inode + offset. This simplifies the code and allows to avoid the unnecessary find_uprobe() + put_uprobe() in these functions. TODO: uprobe_unregister() still needs get_uprobe/put_uprobe to ensure that this uprobe can't be freed before up_write(&uprobe->register_rwsem). Co-developed-by: Andrii Nakryiko Signed-off-by: Andrii Nakryiko Signed-off-by: Oleg Nesterov Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Jiri Olsa Link: https://lore.kernel.org/r/20240801132734.GA8803@redhat.com --- include/linux/uprobes.h | 15 ++--- kernel/events/uprobes.c | 56 ++++++++----------- kernel/trace/bpf_trace.c | 25 ++++----- kernel/trace/trace_uprobe.c | 26 ++++----- .../selftests/bpf/bpf_testmod/bpf_testmod.c | 25 ++++----- 5 files changed, 67 insertions(+), 80 deletions(-) diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h index 788813c0b8fc..f50df1fa93e7 100644 --- a/include/linux/uprobes.h +++ b/include/linux/uprobes.h @@ -16,6 +16,7 @@ #include #include +struct uprobe; struct vm_area_struct; struct mm_struct; struct inode; @@ -112,9 +113,9 @@ extern bool is_trap_insn(uprobe_opcode_t *insn); extern unsigned long uprobe_get_swbp_addr(struct pt_regs *regs); extern unsigned long uprobe_get_trap_addr(struct pt_regs *regs); extern int uprobe_write_opcode(struct arch_uprobe *auprobe, struct mm_struct *mm, unsigned long vaddr, uprobe_opcode_t); -extern int uprobe_register(struct inode *inode, loff_t offset, loff_t ref_ctr_offset, struct uprobe_consumer *uc); -extern int uprobe_apply(struct inode *inode, loff_t offset, struct uprobe_consumer *uc, bool); -extern void uprobe_unregister(struct inode *inode, loff_t offset, struct uprobe_consumer *uc); +extern struct uprobe *uprobe_register(struct inode *inode, loff_t offset, loff_t ref_ctr_offset, struct uprobe_consumer *uc); +extern int uprobe_apply(struct uprobe *uprobe, struct uprobe_consumer *uc, bool); +extern void uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc); extern int uprobe_mmap(struct vm_area_struct *vma); extern void uprobe_munmap(struct vm_area_struct *vma, unsigned long start, unsigned long end); extern void uprobe_start_dup_mmap(void); @@ -152,18 +153,18 @@ static inline void uprobes_init(void) #define uprobe_get_trap_addr(regs) instruction_pointer(regs) -static inline int +static inline struct uprobe * uprobe_register(struct inode *inode, loff_t offset, loff_t ref_ctr_offset, struct uprobe_consumer *uc) { - return -ENOSYS; + return ERR_PTR(-ENOSYS); } static inline int -uprobe_apply(struct inode *inode, loff_t offset, struct uprobe_consumer *uc, bool add) +uprobe_apply(struct uprobe* uprobe, struct uprobe_consumer *uc, bool add) { return -ENOSYS; } static inline void -uprobe_unregister(struct inode *inode, loff_t offset, struct uprobe_consumer *uc) +uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc) { } static inline int uprobe_mmap(struct vm_area_struct *vma) diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index 3a80154bc4c7..b33f1397dae0 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -1099,20 +1099,14 @@ __uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc) delete_uprobe(uprobe); } -/* +/** * uprobe_unregister - unregister an already registered probe. - * @inode: the file in which the probe has to be removed. - * @offset: offset from the start of the file. + * @uprobe: uprobe to remove * @uc: identify which probe if multiple probes are colocated. */ -void uprobe_unregister(struct inode *inode, loff_t offset, struct uprobe_consumer *uc) +void uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc) { - struct uprobe *uprobe; - - uprobe = find_uprobe(inode, offset); - if (WARN_ON(!uprobe)) - return; - + get_uprobe(uprobe); down_write(&uprobe->register_rwsem); __uprobe_unregister(uprobe, uc); up_write(&uprobe->register_rwsem); @@ -1120,7 +1114,7 @@ void uprobe_unregister(struct inode *inode, loff_t offset, struct uprobe_consume } EXPORT_SYMBOL_GPL(uprobe_unregister); -/* +/** * uprobe_register - register a probe * @inode: the file in which the probe has to be placed. * @offset: offset from the start of the file. @@ -1136,40 +1130,40 @@ EXPORT_SYMBOL_GPL(uprobe_unregister); * unregisters. Caller of uprobe_register() is required to keep @inode * (and the containing mount) referenced. * - * Return errno if it cannot successully install probes - * else return 0 (success) + * Return: pointer to the new uprobe on success or an ERR_PTR on failure. */ -int uprobe_register(struct inode *inode, loff_t offset, loff_t ref_ctr_offset, - struct uprobe_consumer *uc) +struct uprobe *uprobe_register(struct inode *inode, + loff_t offset, loff_t ref_ctr_offset, + struct uprobe_consumer *uc) { struct uprobe *uprobe; int ret; /* Uprobe must have at least one set consumer */ if (!uc->handler && !uc->ret_handler) - return -EINVAL; + return ERR_PTR(-EINVAL); /* copy_insn() uses read_mapping_page() or shmem_read_mapping_page() */ if (!inode->i_mapping->a_ops->read_folio && !shmem_mapping(inode->i_mapping)) - return -EIO; + return ERR_PTR(-EIO); /* Racy, just to catch the obvious mistakes */ if (offset > i_size_read(inode)) - return -EINVAL; + return ERR_PTR(-EINVAL); /* * This ensures that copy_from_page(), copy_to_page() and * __update_ref_ctr() can't cross page boundary. */ if (!IS_ALIGNED(offset, UPROBE_SWBP_INSN_SIZE)) - return -EINVAL; + return ERR_PTR(-EINVAL); if (!IS_ALIGNED(ref_ctr_offset, sizeof(short))) - return -EINVAL; + return ERR_PTR(-EINVAL); retry: uprobe = alloc_uprobe(inode, offset, ref_ctr_offset); if (IS_ERR(uprobe)) - return PTR_ERR(uprobe); + return uprobe; /* * We can race with uprobe_unregister()->delete_uprobe(). @@ -1188,35 +1182,29 @@ int uprobe_register(struct inode *inode, loff_t offset, loff_t ref_ctr_offset, if (unlikely(ret == -EAGAIN)) goto retry; - return ret; + + return ret ? ERR_PTR(ret) : uprobe; } EXPORT_SYMBOL_GPL(uprobe_register); -/* - * uprobe_apply - unregister an already registered probe. - * @inode: the file in which the probe has to be removed. - * @offset: offset from the start of the file. +/** + * uprobe_apply - add or remove the breakpoints according to @uc->filter + * @uprobe: uprobe which "owns" the breakpoint * @uc: consumer which wants to add more or remove some breakpoints * @add: add or remove the breakpoints + * Return: 0 on success or negative error code. */ -int uprobe_apply(struct inode *inode, loff_t offset, - struct uprobe_consumer *uc, bool add) +int uprobe_apply(struct uprobe *uprobe, struct uprobe_consumer *uc, bool add) { - struct uprobe *uprobe; struct uprobe_consumer *con; int ret = -ENOENT; - uprobe = find_uprobe(inode, offset); - if (WARN_ON(!uprobe)) - return ret; - down_write(&uprobe->register_rwsem); for (con = uprobe->consumers; con && con != uc ; con = con->next) ; if (con) ret = register_for_each_vma(uprobe, add ? uc : NULL); up_write(&uprobe->register_rwsem); - put_uprobe(uprobe); return ret; } diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c index afa909e17824..4e391daafa64 100644 --- a/kernel/trace/bpf_trace.c +++ b/kernel/trace/bpf_trace.c @@ -3160,6 +3160,7 @@ struct bpf_uprobe { loff_t offset; unsigned long ref_ctr_offset; u64 cookie; + struct uprobe *uprobe; struct uprobe_consumer consumer; }; @@ -3178,15 +3179,12 @@ struct bpf_uprobe_multi_run_ctx { struct bpf_uprobe *uprobe; }; -static void bpf_uprobe_unregister(struct path *path, struct bpf_uprobe *uprobes, - u32 cnt) +static void bpf_uprobe_unregister(struct bpf_uprobe *uprobes, u32 cnt) { u32 i; - for (i = 0; i < cnt; i++) { - uprobe_unregister(d_real_inode(path->dentry), uprobes[i].offset, - &uprobes[i].consumer); - } + for (i = 0; i < cnt; i++) + uprobe_unregister(uprobes[i].uprobe, &uprobes[i].consumer); } static void bpf_uprobe_multi_link_release(struct bpf_link *link) @@ -3194,7 +3192,7 @@ static void bpf_uprobe_multi_link_release(struct bpf_link *link) struct bpf_uprobe_multi_link *umulti_link; umulti_link = container_of(link, struct bpf_uprobe_multi_link, link); - bpf_uprobe_unregister(&umulti_link->path, umulti_link->uprobes, umulti_link->cnt); + bpf_uprobe_unregister(umulti_link->uprobes, umulti_link->cnt); if (umulti_link->task) put_task_struct(umulti_link->task); path_put(&umulti_link->path); @@ -3480,12 +3478,13 @@ int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *pr &bpf_uprobe_multi_link_lops, prog); for (i = 0; i < cnt; i++) { - err = uprobe_register(d_real_inode(link->path.dentry), - uprobes[i].offset, - uprobes[i].ref_ctr_offset, - &uprobes[i].consumer); - if (err) { - bpf_uprobe_unregister(&path, uprobes, i); + uprobes[i].uprobe = uprobe_register(d_real_inode(link->path.dentry), + uprobes[i].offset, + uprobes[i].ref_ctr_offset, + &uprobes[i].consumer); + if (IS_ERR(uprobes[i].uprobe)) { + err = PTR_ERR(uprobes[i].uprobe); + bpf_uprobe_unregister(uprobes, i); goto error_free; } } diff --git a/kernel/trace/trace_uprobe.c b/kernel/trace/trace_uprobe.c index 1f590f989c1e..52e76a73fa7c 100644 --- a/kernel/trace/trace_uprobe.c +++ b/kernel/trace/trace_uprobe.c @@ -58,8 +58,8 @@ struct trace_uprobe { struct dyn_event devent; struct uprobe_consumer consumer; struct path path; - struct inode *inode; char *filename; + struct uprobe *uprobe; unsigned long offset; unsigned long ref_ctr_offset; unsigned long nhit; @@ -1084,16 +1084,16 @@ typedef bool (*filter_func_t)(struct uprobe_consumer *self, static int trace_uprobe_enable(struct trace_uprobe *tu, filter_func_t filter) { - int ret; + struct inode *inode = d_real_inode(tu->path.dentry); + struct uprobe *uprobe; tu->consumer.filter = filter; - tu->inode = d_real_inode(tu->path.dentry); + uprobe = uprobe_register(inode, tu->offset, tu->ref_ctr_offset, &tu->consumer); + if (IS_ERR(uprobe)) + return PTR_ERR(uprobe); - ret = uprobe_register(tu->inode, tu->offset, tu->ref_ctr_offset, &tu->consumer); - if (ret) - tu->inode = NULL; - - return ret; + tu->uprobe = uprobe; + return 0; } static void __probe_event_disable(struct trace_probe *tp) @@ -1104,11 +1104,11 @@ static void __probe_event_disable(struct trace_probe *tp) WARN_ON(!uprobe_filter_is_empty(tu->tp.event->filter)); list_for_each_entry(tu, trace_probe_probe_list(tp), tp.list) { - if (!tu->inode) + if (!tu->uprobe) continue; - uprobe_unregister(tu->inode, tu->offset, &tu->consumer); - tu->inode = NULL; + uprobe_unregister(tu->uprobe, &tu->consumer); + tu->uprobe = NULL; } } @@ -1305,7 +1305,7 @@ static int uprobe_perf_close(struct trace_event_call *call, return 0; list_for_each_entry(tu, trace_probe_probe_list(tp), tp.list) { - ret = uprobe_apply(tu->inode, tu->offset, &tu->consumer, false); + ret = uprobe_apply(tu->uprobe, &tu->consumer, false); if (ret) break; } @@ -1329,7 +1329,7 @@ static int uprobe_perf_open(struct trace_event_call *call, return 0; list_for_each_entry(tu, trace_probe_probe_list(tp), tp.list) { - err = uprobe_apply(tu->inode, tu->offset, &tu->consumer, true); + err = uprobe_apply(tu->uprobe, &tu->consumer, true); if (err) { uprobe_perf_close(call, event); break; diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c index 55f6905de743..3c0515a27842 100644 --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c @@ -432,7 +432,7 @@ uprobe_ret_handler(struct uprobe_consumer *self, unsigned long func, struct testmod_uprobe { struct path path; - loff_t offset; + struct uprobe *uprobe; struct uprobe_consumer consumer; }; @@ -446,25 +446,25 @@ static int testmod_register_uprobe(loff_t offset) { int err = -EBUSY; - if (uprobe.offset) + if (uprobe.uprobe) return -EBUSY; mutex_lock(&testmod_uprobe_mutex); - if (uprobe.offset) + if (uprobe.uprobe) goto out; err = kern_path("/proc/self/exe", LOOKUP_FOLLOW, &uprobe.path); if (err) goto out; - err = uprobe_register(d_real_inode(uprobe.path.dentry), - offset, 0, &uprobe.consumer); - if (err) + uprobe.uprobe = uprobe_register(d_real_inode(uprobe.path.dentry), + offset, 0, &uprobe.consumer); + if (IS_ERR(uprobe.uprobe)) { + err = PTR_ERR(uprobe.uprobe); path_put(&uprobe.path); - else - uprobe.offset = offset; - + uprobe.uprobe = NULL; + } out: mutex_unlock(&testmod_uprobe_mutex); return err; @@ -474,11 +474,10 @@ static void testmod_unregister_uprobe(void) { mutex_lock(&testmod_uprobe_mutex); - if (uprobe.offset) { - uprobe_unregister(d_real_inode(uprobe.path.dentry), - uprobe.offset, &uprobe.consumer); + if (uprobe.uprobe) { + uprobe_unregister(uprobe.uprobe, &uprobe.consumer); path_put(&uprobe.path); - uprobe.offset = 0; + uprobe.uprobe = NULL; } mutex_unlock(&testmod_uprobe_mutex); From bb18c5de1c288050ef8bd4af4ca16896ad4cd3fc Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Thu, 1 Aug 2024 15:27:39 +0200 Subject: [PATCH 063/573] uprobes: change uprobe_register() to use uprobe_unregister() instead of __uprobe_unregister() If register_for_each_vma() fails uprobe_register() can safely drop uprobe->register_rwsem and use uprobe_unregister(). There is no worry about the races with another register/unregister, consumer_add() was already called so this case doesn't differ from _unregister() right after the successful _register(). Yes this means the extra up_write() + down_write(), but this is the slow and unlikely case anyway. Signed-off-by: Oleg Nesterov Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Jiri Olsa Acked-by: Andrii Nakryiko Acked-by: "Masami Hiramatsu (Google)" Link: https://lore.kernel.org/r/20240801132739.GA8809@redhat.com --- kernel/events/uprobes.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index b33f1397dae0..eacf287ecd89 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -1174,16 +1174,18 @@ struct uprobe *uprobe_register(struct inode *inode, if (likely(uprobe_is_active(uprobe))) { consumer_add(uprobe, uc); ret = register_for_each_vma(uprobe, uc); - if (ret) - __uprobe_unregister(uprobe, uc); } up_write(&uprobe->register_rwsem); put_uprobe(uprobe); - if (unlikely(ret == -EAGAIN)) - goto retry; + if (ret) { + if (unlikely(ret == -EAGAIN)) + goto retry; + uprobe_unregister(uprobe, uc); + return ERR_PTR(ret); + } - return ret ? ERR_PTR(ret) : uprobe; + return uprobe; } EXPORT_SYMBOL_GPL(uprobe_register); From 70408bebba94a63ea11471ed00168cd8606a9328 Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Thu, 1 Aug 2024 15:27:44 +0200 Subject: [PATCH 064/573] uprobes: fold __uprobe_unregister() into uprobe_unregister() Fold __uprobe_unregister() into its single caller, uprobe_unregister(). A separate patch to simplify the next change. Signed-off-by: Oleg Nesterov Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Jiri Olsa Acked-by: Andrii Nakryiko Acked-by: "Masami Hiramatsu (Google)" Link: https://lore.kernel.org/r/20240801132744.GA8814@redhat.com --- kernel/events/uprobes.c | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index eacf287ecd89..175058b209a9 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -1085,20 +1085,6 @@ register_for_each_vma(struct uprobe *uprobe, struct uprobe_consumer *new) return err; } -static void -__uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc) -{ - int err; - - if (WARN_ON(!consumer_del(uprobe, uc))) - return; - - err = register_for_each_vma(uprobe, NULL); - /* TODO : cant unregister? schedule a worker thread */ - if (!uprobe->consumers && !err) - delete_uprobe(uprobe); -} - /** * uprobe_unregister - unregister an already registered probe. * @uprobe: uprobe to remove @@ -1106,9 +1092,18 @@ __uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc) */ void uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc) { + int err; + get_uprobe(uprobe); down_write(&uprobe->register_rwsem); - __uprobe_unregister(uprobe, uc); + if (WARN_ON(!consumer_del(uprobe, uc))) + err = -ENOENT; + else + err = register_for_each_vma(uprobe, NULL); + + /* TODO : cant unregister? schedule a worker thread */ + if (!err && !uprobe->consumers) + delete_uprobe(uprobe); up_write(&uprobe->register_rwsem); put_uprobe(uprobe); } From 12026d2034dfeb575e0eb28f33431cbf03dc732c Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Thu, 1 Aug 2024 15:27:49 +0200 Subject: [PATCH 065/573] uprobes: shift put_uprobe() from delete_uprobe() to uprobe_unregister() Kill the extra get_uprobe() + put_uprobe() in uprobe_unregister() and move the possibly final put_uprobe() from delete_uprobe() to its only caller, uprobe_unregister(). Signed-off-by: Oleg Nesterov Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Jiri Olsa Acked-by: Andrii Nakryiko Acked-by: "Masami Hiramatsu (Google)" Link: https://lore.kernel.org/r/20240801132749.GA8817@redhat.com --- kernel/events/uprobes.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index 175058b209a9..30348f13d4a7 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -939,7 +939,6 @@ static void delete_uprobe(struct uprobe *uprobe) rb_erase(&uprobe->rb_node, &uprobes_tree); write_unlock(&uprobes_treelock); RB_CLEAR_NODE(&uprobe->rb_node); /* for uprobe_is_active() */ - put_uprobe(uprobe); } struct map_info { @@ -1094,7 +1093,6 @@ void uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc) { int err; - get_uprobe(uprobe); down_write(&uprobe->register_rwsem); if (WARN_ON(!consumer_del(uprobe, uc))) err = -ENOENT; @@ -1102,10 +1100,16 @@ void uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc) err = register_for_each_vma(uprobe, NULL); /* TODO : cant unregister? schedule a worker thread */ - if (!err && !uprobe->consumers) - delete_uprobe(uprobe); + if (!err) { + if (!uprobe->consumers) + delete_uprobe(uprobe); + else + err = -EBUSY; + } up_write(&uprobe->register_rwsem); - put_uprobe(uprobe); + + if (!err) + put_uprobe(uprobe); } EXPORT_SYMBOL_GPL(uprobe_unregister); From 3e7ebf271f935a316e9593d63f495498cde22f80 Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Thu, 1 Aug 2024 17:30:27 +0800 Subject: [PATCH 066/573] gpio: gpio-vf610: use u32 mask to handle 32 number gpios This gpio controller support up to 32 pins per port. And all the register width is 32 bit. So here use u32 to replace the original unsigned long. Signed-off-by: Haibo Chen Reviewed-by: Stefan Wahren Link: https://lore.kernel.org/r/20240801093028.732338-2-haibo.chen@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-vf610.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-vf610.c b/drivers/gpio/gpio-vf610.c index 07e5e6323e86..db68d8541597 100644 --- a/drivers/gpio/gpio-vf610.c +++ b/drivers/gpio/gpio-vf610.c @@ -97,7 +97,7 @@ static inline u32 vf610_gpio_readl(void __iomem *reg) static int vf610_gpio_get(struct gpio_chip *gc, unsigned int gpio) { struct vf610_gpio_port *port = gpiochip_get_data(gc); - unsigned long mask = BIT(gpio); + u32 mask = BIT(gpio); unsigned long offset = GPIO_PDIR; if (port->sdata->have_paddr) { @@ -112,16 +112,16 @@ static int vf610_gpio_get(struct gpio_chip *gc, unsigned int gpio) static void vf610_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val) { struct vf610_gpio_port *port = gpiochip_get_data(gc); - unsigned long mask = BIT(gpio); + u32 mask = BIT(gpio); unsigned long offset = val ? GPIO_PSOR : GPIO_PCOR; vf610_gpio_writel(mask, port->gpio_base + offset); } -static int vf610_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) +static int vf610_gpio_direction_input(struct gpio_chip *chip, unsigned int gpio) { struct vf610_gpio_port *port = gpiochip_get_data(chip); - unsigned long mask = BIT(gpio); + u32 mask = BIT(gpio); u32 val; if (port->sdata->have_paddr) { @@ -133,11 +133,11 @@ static int vf610_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) return pinctrl_gpio_direction_input(chip, gpio); } -static int vf610_gpio_direction_output(struct gpio_chip *chip, unsigned gpio, +static int vf610_gpio_direction_output(struct gpio_chip *chip, unsigned int gpio, int value) { struct vf610_gpio_port *port = gpiochip_get_data(chip); - unsigned long mask = BIT(gpio); + u32 mask = BIT(gpio); u32 val; vf610_gpio_set(chip, gpio, value); From 26b95b7b588d70b5075b597ff808543503d36ac6 Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Thu, 1 Aug 2024 17:30:28 +0800 Subject: [PATCH 067/573] gpio: vf610: add get_direction() support For IP which do not contain PDDR, currently use the pinmux API pinctrl_gpio_direction_input() to config the output/input, pinmux currently do not support get_direction(). So here add the GPIO get_direction() support only for the IP which has Port Data Direction Register (PDDR). Signed-off-by: Haibo Chen Link: https://lore.kernel.org/r/20240801093028.732338-3-haibo.chen@nxp.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-vf610.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/gpio/gpio-vf610.c b/drivers/gpio/gpio-vf610.c index db68d8541597..27eff741fe9a 100644 --- a/drivers/gpio/gpio-vf610.c +++ b/drivers/gpio/gpio-vf610.c @@ -151,6 +151,19 @@ static int vf610_gpio_direction_output(struct gpio_chip *chip, unsigned int gpio return pinctrl_gpio_direction_output(chip, gpio); } +static int vf610_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio) +{ + struct vf610_gpio_port *port = gpiochip_get_data(gc); + u32 mask = BIT(gpio); + + mask &= vf610_gpio_readl(port->gpio_base + GPIO_PDDR); + + if (mask) + return GPIO_LINE_DIRECTION_OUT; + + return GPIO_LINE_DIRECTION_IN; +} + static void vf610_gpio_irq_handler(struct irq_desc *desc) { struct vf610_gpio_port *port = @@ -362,6 +375,12 @@ static int vf610_gpio_probe(struct platform_device *pdev) gc->get = vf610_gpio_get; gc->direction_output = vf610_gpio_direction_output; gc->set = vf610_gpio_set; + /* + * only IP has Port Data Direction Register(PDDR) can + * support get direction + */ + if (port->sdata->have_paddr) + gc->get_direction = vf610_gpio_get_direction; /* Mask all GPIO interrupts */ for (i = 0; i < gc->ngpio; i++) From 328fc9b29810819c4496c6e9fd74491ca458045e Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Tue, 30 Jul 2024 15:02:22 +0200 Subject: [PATCH 068/573] pmdomain: amlogic: remove obsolete vpu domain driver meson-gx-pwrc-vpu has been superseded by meson-ee-pwrc since commit 53773f2dfd9c ("soc: amlogic: meson-ee-pwrc: add support for the Meson GX SoCs"), so v5.8. This driver is obsolete and no longer used or tested. There is no reason to keep it around so remove it. Signed-off-by: Jerome Brunet Link: https://lore.kernel.org/r/20240730130227.712894-1-jbrunet@baylibre.com Signed-off-by: Ulf Hansson --- drivers/pmdomain/amlogic/Kconfig | 11 - drivers/pmdomain/amlogic/Makefile | 1 - drivers/pmdomain/amlogic/meson-gx-pwrc-vpu.c | 380 ------------------- 3 files changed, 392 deletions(-) delete mode 100644 drivers/pmdomain/amlogic/meson-gx-pwrc-vpu.c diff --git a/drivers/pmdomain/amlogic/Kconfig b/drivers/pmdomain/amlogic/Kconfig index 2108729909b5..e72b664174af 100644 --- a/drivers/pmdomain/amlogic/Kconfig +++ b/drivers/pmdomain/amlogic/Kconfig @@ -1,17 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only menu "Amlogic PM Domains" -config MESON_GX_PM_DOMAINS - tristate "Amlogic Meson GX Power Domains driver" - depends on ARCH_MESON || COMPILE_TEST - depends on PM && OF - default ARCH_MESON - select PM_GENERIC_DOMAINS - select PM_GENERIC_DOMAINS_OF - help - Say yes to expose Amlogic Meson GX Power Domains as - Generic Power Domains. - config MESON_EE_PM_DOMAINS tristate "Amlogic Meson Everything-Else Power Domains driver" depends on ARCH_MESON || COMPILE_TEST diff --git a/drivers/pmdomain/amlogic/Makefile b/drivers/pmdomain/amlogic/Makefile index 3d58abd574f9..99f195f09957 100644 --- a/drivers/pmdomain/amlogic/Makefile +++ b/drivers/pmdomain/amlogic/Makefile @@ -1,4 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_MESON_GX_PM_DOMAINS) += meson-gx-pwrc-vpu.o obj-$(CONFIG_MESON_EE_PM_DOMAINS) += meson-ee-pwrc.o obj-$(CONFIG_MESON_SECURE_PM_DOMAINS) += meson-secure-pwrc.o diff --git a/drivers/pmdomain/amlogic/meson-gx-pwrc-vpu.c b/drivers/pmdomain/amlogic/meson-gx-pwrc-vpu.c deleted file mode 100644 index 6028e91664a4..000000000000 --- a/drivers/pmdomain/amlogic/meson-gx-pwrc-vpu.c +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (c) 2017 BayLibre, SAS - * Author: Neil Armstrong - * - * SPDX-License-Identifier: GPL-2.0+ - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* AO Offsets */ - -#define AO_RTI_GEN_PWR_SLEEP0 (0x3a << 2) - -#define GEN_PWR_VPU_HDMI BIT(8) -#define GEN_PWR_VPU_HDMI_ISO BIT(9) - -/* HHI Offsets */ - -#define HHI_MEM_PD_REG0 (0x40 << 2) -#define HHI_VPU_MEM_PD_REG0 (0x41 << 2) -#define HHI_VPU_MEM_PD_REG1 (0x42 << 2) -#define HHI_VPU_MEM_PD_REG2 (0x4d << 2) - -struct meson_gx_pwrc_vpu { - struct generic_pm_domain genpd; - struct regmap *regmap_ao; - struct regmap *regmap_hhi; - struct reset_control *rstc; - struct clk *vpu_clk; - struct clk *vapb_clk; -}; - -static inline -struct meson_gx_pwrc_vpu *genpd_to_pd(struct generic_pm_domain *d) -{ - return container_of(d, struct meson_gx_pwrc_vpu, genpd); -} - -static int meson_gx_pwrc_vpu_power_off(struct generic_pm_domain *genpd) -{ - struct meson_gx_pwrc_vpu *pd = genpd_to_pd(genpd); - int i; - - regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, - GEN_PWR_VPU_HDMI_ISO, GEN_PWR_VPU_HDMI_ISO); - udelay(20); - - /* Power Down Memories */ - for (i = 0; i < 32; i += 2) { - regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, - 0x3 << i, 0x3 << i); - udelay(5); - } - for (i = 0; i < 32; i += 2) { - regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, - 0x3 << i, 0x3 << i); - udelay(5); - } - for (i = 8; i < 16; i++) { - regmap_update_bits(pd->regmap_hhi, HHI_MEM_PD_REG0, - BIT(i), BIT(i)); - udelay(5); - } - udelay(20); - - regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, - GEN_PWR_VPU_HDMI, GEN_PWR_VPU_HDMI); - - msleep(20); - - clk_disable_unprepare(pd->vpu_clk); - clk_disable_unprepare(pd->vapb_clk); - - return 0; -} - -static int meson_g12a_pwrc_vpu_power_off(struct generic_pm_domain *genpd) -{ - struct meson_gx_pwrc_vpu *pd = genpd_to_pd(genpd); - int i; - - regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, - GEN_PWR_VPU_HDMI_ISO, GEN_PWR_VPU_HDMI_ISO); - udelay(20); - - /* Power Down Memories */ - for (i = 0; i < 32; i += 2) { - regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, - 0x3 << i, 0x3 << i); - udelay(5); - } - for (i = 0; i < 32; i += 2) { - regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, - 0x3 << i, 0x3 << i); - udelay(5); - } - for (i = 0; i < 32; i += 2) { - regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG2, - 0x3 << i, 0x3 << i); - udelay(5); - } - for (i = 8; i < 16; i++) { - regmap_update_bits(pd->regmap_hhi, HHI_MEM_PD_REG0, - BIT(i), BIT(i)); - udelay(5); - } - udelay(20); - - regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, - GEN_PWR_VPU_HDMI, GEN_PWR_VPU_HDMI); - - msleep(20); - - clk_disable_unprepare(pd->vpu_clk); - clk_disable_unprepare(pd->vapb_clk); - - return 0; -} - -static int meson_gx_pwrc_vpu_setup_clk(struct meson_gx_pwrc_vpu *pd) -{ - int ret; - - ret = clk_prepare_enable(pd->vpu_clk); - if (ret) - return ret; - - ret = clk_prepare_enable(pd->vapb_clk); - if (ret) - clk_disable_unprepare(pd->vpu_clk); - - return ret; -} - -static int meson_gx_pwrc_vpu_power_on(struct generic_pm_domain *genpd) -{ - struct meson_gx_pwrc_vpu *pd = genpd_to_pd(genpd); - int ret; - int i; - - regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, - GEN_PWR_VPU_HDMI, 0); - udelay(20); - - /* Power Up Memories */ - for (i = 0; i < 32; i += 2) { - regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, - 0x3 << i, 0); - udelay(5); - } - - for (i = 0; i < 32; i += 2) { - regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, - 0x3 << i, 0); - udelay(5); - } - - for (i = 8; i < 16; i++) { - regmap_update_bits(pd->regmap_hhi, HHI_MEM_PD_REG0, - BIT(i), 0); - udelay(5); - } - udelay(20); - - ret = reset_control_assert(pd->rstc); - if (ret) - return ret; - - regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, - GEN_PWR_VPU_HDMI_ISO, 0); - - ret = reset_control_deassert(pd->rstc); - if (ret) - return ret; - - ret = meson_gx_pwrc_vpu_setup_clk(pd); - if (ret) - return ret; - - return 0; -} - -static int meson_g12a_pwrc_vpu_power_on(struct generic_pm_domain *genpd) -{ - struct meson_gx_pwrc_vpu *pd = genpd_to_pd(genpd); - int ret; - int i; - - regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, - GEN_PWR_VPU_HDMI, 0); - udelay(20); - - /* Power Up Memories */ - for (i = 0; i < 32; i += 2) { - regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, - 0x3 << i, 0); - udelay(5); - } - - for (i = 0; i < 32; i += 2) { - regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, - 0x3 << i, 0); - udelay(5); - } - - for (i = 0; i < 32; i += 2) { - regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG2, - 0x3 << i, 0); - udelay(5); - } - - for (i = 8; i < 16; i++) { - regmap_update_bits(pd->regmap_hhi, HHI_MEM_PD_REG0, - BIT(i), 0); - udelay(5); - } - udelay(20); - - ret = reset_control_assert(pd->rstc); - if (ret) - return ret; - - regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, - GEN_PWR_VPU_HDMI_ISO, 0); - - ret = reset_control_deassert(pd->rstc); - if (ret) - return ret; - - ret = meson_gx_pwrc_vpu_setup_clk(pd); - if (ret) - return ret; - - return 0; -} - -static bool meson_gx_pwrc_vpu_get_power(struct meson_gx_pwrc_vpu *pd) -{ - u32 reg; - - regmap_read(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, ®); - - return (reg & GEN_PWR_VPU_HDMI); -} - -static struct meson_gx_pwrc_vpu vpu_hdmi_pd = { - .genpd = { - .name = "vpu_hdmi", - .power_off = meson_gx_pwrc_vpu_power_off, - .power_on = meson_gx_pwrc_vpu_power_on, - }, -}; - -static struct meson_gx_pwrc_vpu vpu_hdmi_pd_g12a = { - .genpd = { - .name = "vpu_hdmi", - .power_off = meson_g12a_pwrc_vpu_power_off, - .power_on = meson_g12a_pwrc_vpu_power_on, - }, -}; - -static int meson_gx_pwrc_vpu_probe(struct platform_device *pdev) -{ - const struct meson_gx_pwrc_vpu *vpu_pd_match; - struct regmap *regmap_ao, *regmap_hhi; - struct meson_gx_pwrc_vpu *vpu_pd; - struct device_node *parent_np; - struct reset_control *rstc; - struct clk *vpu_clk; - struct clk *vapb_clk; - bool powered_off; - int ret; - - vpu_pd_match = of_device_get_match_data(&pdev->dev); - if (!vpu_pd_match) { - dev_err(&pdev->dev, "failed to get match data\n"); - return -ENODEV; - } - - vpu_pd = devm_kzalloc(&pdev->dev, sizeof(*vpu_pd), GFP_KERNEL); - if (!vpu_pd) - return -ENOMEM; - - memcpy(vpu_pd, vpu_pd_match, sizeof(*vpu_pd)); - - parent_np = of_get_parent(pdev->dev.of_node); - regmap_ao = syscon_node_to_regmap(parent_np); - of_node_put(parent_np); - if (IS_ERR(regmap_ao)) { - dev_err(&pdev->dev, "failed to get regmap\n"); - return PTR_ERR(regmap_ao); - } - - regmap_hhi = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, - "amlogic,hhi-sysctrl"); - if (IS_ERR(regmap_hhi)) { - dev_err(&pdev->dev, "failed to get HHI regmap\n"); - return PTR_ERR(regmap_hhi); - } - - rstc = devm_reset_control_array_get_exclusive(&pdev->dev); - if (IS_ERR(rstc)) - return dev_err_probe(&pdev->dev, PTR_ERR(rstc), - "failed to get reset lines\n"); - - vpu_clk = devm_clk_get(&pdev->dev, "vpu"); - if (IS_ERR(vpu_clk)) { - dev_err(&pdev->dev, "vpu clock request failed\n"); - return PTR_ERR(vpu_clk); - } - - vapb_clk = devm_clk_get(&pdev->dev, "vapb"); - if (IS_ERR(vapb_clk)) { - dev_err(&pdev->dev, "vapb clock request failed\n"); - return PTR_ERR(vapb_clk); - } - - vpu_pd->regmap_ao = regmap_ao; - vpu_pd->regmap_hhi = regmap_hhi; - vpu_pd->rstc = rstc; - vpu_pd->vpu_clk = vpu_clk; - vpu_pd->vapb_clk = vapb_clk; - - platform_set_drvdata(pdev, vpu_pd); - - powered_off = meson_gx_pwrc_vpu_get_power(vpu_pd); - - /* If already powered, sync the clock states */ - if (!powered_off) { - ret = meson_gx_pwrc_vpu_setup_clk(vpu_pd); - if (ret) - return ret; - } - - vpu_pd->genpd.flags = GENPD_FLAG_ALWAYS_ON; - pm_genpd_init(&vpu_pd->genpd, NULL, powered_off); - - return of_genpd_add_provider_simple(pdev->dev.of_node, - &vpu_pd->genpd); -} - -static void meson_gx_pwrc_vpu_shutdown(struct platform_device *pdev) -{ - struct meson_gx_pwrc_vpu *vpu_pd = platform_get_drvdata(pdev); - bool powered_off; - - powered_off = meson_gx_pwrc_vpu_get_power(vpu_pd); - if (!powered_off) - vpu_pd->genpd.power_off(&vpu_pd->genpd); -} - -static const struct of_device_id meson_gx_pwrc_vpu_match_table[] = { - { .compatible = "amlogic,meson-gx-pwrc-vpu", .data = &vpu_hdmi_pd }, - { - .compatible = "amlogic,meson-g12a-pwrc-vpu", - .data = &vpu_hdmi_pd_g12a - }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, meson_gx_pwrc_vpu_match_table); - -static struct platform_driver meson_gx_pwrc_vpu_driver = { - .probe = meson_gx_pwrc_vpu_probe, - .shutdown = meson_gx_pwrc_vpu_shutdown, - .driver = { - .name = "meson_gx_pwrc_vpu", - .of_match_table = meson_gx_pwrc_vpu_match_table, - }, -}; -module_platform_driver(meson_gx_pwrc_vpu_driver); -MODULE_DESCRIPTION("Amlogic Meson GX Power Domains driver"); -MODULE_LICENSE("GPL v2"); From d7bdb8e6aabe218fd980768a1486434e42761539 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 27 May 2024 16:25:51 +0200 Subject: [PATCH 069/573] pmdomain: core: Enable s2idle for CPU PM domains on PREEMPT_RT To allow a genpd provider for a CPU PM domain to enter a domain-idle-state during s2idle on a PREEMPT_RT based configuration, we can't use the regular spinlock, as they are turned into sleepable locks on PREEMPT_RT. To address this problem, let's convert into using the raw spinlock, but only for genpd providers that have the GENPD_FLAG_CPU_DOMAIN bit set. In this way, the lock can still be acquired/released in atomic context, which is needed in the idle-path for PREEMPT_RT. Do note that the genpd power-on/off notifiers may also be fired during s2idle, but these are already prepared for PREEMPT_RT as they are based on the raw notifiers. However, consumers of them may need to adopt accordingly to work properly on PREEMPT_RT. Signed-off-by: Ulf Hansson Tested-by: Raghavendra Kakarla # qcm6490 with PREEMPT_RT set Acked-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20240527142557.321610-2-ulf.hansson@linaro.org --- drivers/pmdomain/core.c | 47 ++++++++++++++++++++++++++++++++++++++- include/linux/pm_domain.h | 5 ++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index 7a61aa88c061..8c798a46ffec 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -117,6 +117,48 @@ static const struct genpd_lock_ops genpd_spin_ops = { .unlock = genpd_unlock_spin, }; +static void genpd_lock_raw_spin(struct generic_pm_domain *genpd) + __acquires(&genpd->raw_slock) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&genpd->raw_slock, flags); + genpd->raw_lock_flags = flags; +} + +static void genpd_lock_nested_raw_spin(struct generic_pm_domain *genpd, + int depth) + __acquires(&genpd->raw_slock) +{ + unsigned long flags; + + raw_spin_lock_irqsave_nested(&genpd->raw_slock, flags, depth); + genpd->raw_lock_flags = flags; +} + +static int genpd_lock_interruptible_raw_spin(struct generic_pm_domain *genpd) + __acquires(&genpd->raw_slock) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&genpd->raw_slock, flags); + genpd->raw_lock_flags = flags; + return 0; +} + +static void genpd_unlock_raw_spin(struct generic_pm_domain *genpd) + __releases(&genpd->raw_slock) +{ + raw_spin_unlock_irqrestore(&genpd->raw_slock, genpd->raw_lock_flags); +} + +static const struct genpd_lock_ops genpd_raw_spin_ops = { + .lock = genpd_lock_raw_spin, + .lock_nested = genpd_lock_nested_raw_spin, + .lock_interruptible = genpd_lock_interruptible_raw_spin, + .unlock = genpd_unlock_raw_spin, +}; + #define genpd_lock(p) p->lock_ops->lock(p) #define genpd_lock_nested(p, d) p->lock_ops->lock_nested(p, d) #define genpd_lock_interruptible(p) p->lock_ops->lock_interruptible(p) @@ -2143,7 +2185,10 @@ static void genpd_free_data(struct generic_pm_domain *genpd) static void genpd_lock_init(struct generic_pm_domain *genpd) { - if (genpd_is_irq_safe(genpd)) { + if (genpd_is_cpu_domain(genpd)) { + raw_spin_lock_init(&genpd->raw_slock); + genpd->lock_ops = &genpd_raw_spin_ops; + } else if (genpd_is_irq_safe(genpd)) { spin_lock_init(&genpd->slock); genpd->lock_ops = &genpd_spin_ops; } else { diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 858c8e7851fb..b86bb52858ac 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -198,8 +198,11 @@ struct generic_pm_domain { spinlock_t slock; unsigned long lock_flags; }; + struct { + raw_spinlock_t raw_slock; + unsigned long raw_lock_flags; + }; }; - }; static inline struct generic_pm_domain *pd_to_genpd(struct dev_pm_domain *pd) From b87eee38605c396f0e1fa435939960e5c6cd41d6 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 27 May 2024 16:25:52 +0200 Subject: [PATCH 070/573] pmdomain: core: Don't hold the genpd-lock when calling dev_pm_domain_set() There is no need to hold the genpd-lock, while assigning the dev->pm_domain. In fact, it becomes a problem on a PREEMPT_RT based configuration as the genpd-lock may be a raw spinlock, while the lock acquired through the call to dev_pm_domain_set() is a regular spinlock. To fix the problem, let's simply move the calls to dev_pm_domain_set() outside the genpd-lock. Signed-off-by: Ulf Hansson Tested-by: Raghavendra Kakarla # qcm6490 with PREEMPT_RT set Acked-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20240527142557.321610-3-ulf.hansson@linaro.org --- drivers/pmdomain/core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index 8c798a46ffec..ef61486d41ee 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -1800,7 +1800,6 @@ static int genpd_add_device(struct generic_pm_domain *genpd, struct device *dev, genpd_lock(genpd); genpd_set_cpumask(genpd, gpd_data->cpu); - dev_pm_domain_set(dev, &genpd->domain); genpd->device_count++; if (gd) @@ -1809,6 +1808,7 @@ static int genpd_add_device(struct generic_pm_domain *genpd, struct device *dev, list_add_tail(&gpd_data->base.list_node, &genpd->dev_list); genpd_unlock(genpd); + dev_pm_domain_set(dev, &genpd->domain); out: if (ret) genpd_free_dev_data(dev, gpd_data); @@ -1865,12 +1865,13 @@ static int genpd_remove_device(struct generic_pm_domain *genpd, genpd->gd->max_off_time_changed = true; genpd_clear_cpumask(genpd, gpd_data->cpu); - dev_pm_domain_set(dev, NULL); list_del_init(&pdd->list_node); genpd_unlock(genpd); + dev_pm_domain_set(dev, NULL); + if (genpd->detach_dev) genpd->detach_dev(genpd, dev); From 9094e53ff5c86ebe372ad3960c3216c9817a1a04 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 27 May 2024 16:25:53 +0200 Subject: [PATCH 071/573] pmdomain: core: Use dev_name() instead of kobject_get_path() in debugfs Using kobject_get_path() means a dynamic memory allocation gets done, which doesn't work on a PREEMPT_RT based configuration while holding genpd's raw spinlock. To fix the problem, let's convert into using the simpler dev_name(). This means the information about the path doesn't get presented in debugfs, but hopefully this shouldn't be an issue. Signed-off-by: Ulf Hansson Tested-by: Raghavendra Kakarla # qcm6490 with PREEMPT_RT set Acked-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20240527142557.321610-4-ulf.hansson@linaro.org --- drivers/pmdomain/core.c | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index ef61486d41ee..2731b285e017 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -3255,7 +3255,6 @@ static int genpd_summary_one(struct seq_file *s, [GENPD_STATE_OFF] = "off" }; struct pm_domain_data *pm_data; - const char *kobj_path; struct gpd_link *link; char state[16]; int ret; @@ -3288,17 +3287,10 @@ static int genpd_summary_one(struct seq_file *s, } list_for_each_entry(pm_data, &genpd->dev_list, list_node) { - kobj_path = kobject_get_path(&pm_data->dev->kobj, - genpd_is_irq_safe(genpd) ? - GFP_ATOMIC : GFP_KERNEL); - if (kobj_path == NULL) - continue; - - seq_printf(s, "\n %-50s ", kobj_path); + seq_printf(s, "\n %-50s ", dev_name(pm_data->dev)); rtpm_status_str(s, pm_data->dev); perf_status_str(s, pm_data->dev); mode_status_str(s, pm_data->dev); - kfree(kobj_path); } seq_puts(s, "\n"); @@ -3467,23 +3459,14 @@ static int devices_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; struct pm_domain_data *pm_data; - const char *kobj_path; int ret = 0; ret = genpd_lock_interruptible(genpd); if (ret) return -ERESTARTSYS; - list_for_each_entry(pm_data, &genpd->dev_list, list_node) { - kobj_path = kobject_get_path(&pm_data->dev->kobj, - genpd_is_irq_safe(genpd) ? - GFP_ATOMIC : GFP_KERNEL); - if (kobj_path == NULL) - continue; - - seq_printf(s, "%s\n", kobj_path); - kfree(kobj_path); - } + list_for_each_entry(pm_data, &genpd->dev_list, list_node) + seq_printf(s, "%s\n", dev_name(pm_data->dev)); genpd_unlock(genpd); return ret; From c7b45284ab3012d21a02cf4448df32c655c32afb Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 27 May 2024 16:25:54 +0200 Subject: [PATCH 072/573] cpuidle: psci-domain: Enable system-wide suspend on PREEMPT_RT The domain-idle-states are currently disabled on a PREEMPT_RT based configuration for the cpuidle-psci-domain. To enable them to be used for system-wide suspend and in particular during s2idle, let's set the GENPD_FLAG_RPM_ALWAYS_ON instead of GENPD_FLAG_ALWAYS_ON for the corresponding genpd provider. In this way, the runtime PM path remains disabled in genpd for its attached devices, while powering-on/off the PM domain during system-wide suspend becomes allowed. Signed-off-by: Ulf Hansson Tested-by: Raghavendra Kakarla # qcm6490 with PREEMPT_RT set Acked-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20240527142557.321610-5-ulf.hansson@linaro.org --- drivers/cpuidle/cpuidle-psci-domain.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/cpuidle/cpuidle-psci-domain.c b/drivers/cpuidle/cpuidle-psci-domain.c index fae958794339..ea28b73ef3fb 100644 --- a/drivers/cpuidle/cpuidle-psci-domain.c +++ b/drivers/cpuidle/cpuidle-psci-domain.c @@ -67,12 +67,16 @@ static int psci_pd_init(struct device_node *np, bool use_osi) /* * Allow power off when OSI has been successfully enabled. - * PREEMPT_RT is not yet ready to enter domain idle states. + * On a PREEMPT_RT based configuration the domain idle states are + * supported, but only during system-wide suspend. */ - if (use_osi && !IS_ENABLED(CONFIG_PREEMPT_RT)) + if (use_osi) { pd->power_off = psci_pd_power_off; - else + if (IS_ENABLED(CONFIG_PREEMPT_RT)) + pd->flags |= GENPD_FLAG_RPM_ALWAYS_ON; + } else { pd->flags |= GENPD_FLAG_ALWAYS_ON; + } /* Use governor for CPU PM domains if it has some states to manage. */ pd_gov = pd->states ? &pm_domain_cpu_gov : NULL; From 88bf68b76694873c3b101b65a26c4b315fab06df Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 27 May 2024 16:25:55 +0200 Subject: [PATCH 073/573] cpuidle: psci: Drop redundant assignment of CPUIDLE_FLAG_RCU_IDLE When using the hierarchical topology and PSCI OSI-mode we may end up overriding the deepest idle-state's ->enter|enter_s2idle() callbacks, but there is no point to also re-assign the CPUIDLE_FLAG_RCU_IDLE for the idle-state in question, as that has already been set when parsing the states from DT. See init_state_node(). Signed-off-by: Ulf Hansson Tested-by: Raghavendra Kakarla # qcm6490 with PREEMPT_RT set Acked-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20240527142557.321610-6-ulf.hansson@linaro.org --- drivers/cpuidle/cpuidle-psci.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/cpuidle/cpuidle-psci.c b/drivers/cpuidle/cpuidle-psci.c index 782030a27703..d82a8bc1b194 100644 --- a/drivers/cpuidle/cpuidle-psci.c +++ b/drivers/cpuidle/cpuidle-psci.c @@ -234,7 +234,6 @@ static int psci_dt_cpu_init_topology(struct cpuidle_driver *drv, * of a shared state for the domain, assumes the domain states are all * deeper states. */ - drv->states[state_count - 1].flags |= CPUIDLE_FLAG_RCU_IDLE; drv->states[state_count - 1].enter = psci_enter_domain_idle_state; drv->states[state_count - 1].enter_s2idle = psci_enter_s2idle_domain_idle_state; psci_cpuidle_use_cpuhp = true; From 4517b1c383807fadac8374f99f2361fe7eb4c0b4 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 27 May 2024 16:25:56 +0200 Subject: [PATCH 074/573] cpuidle: psci: Enable the hierarchical topology for s2ram on PREEMPT_RT The hierarchical PM domain topology are currently disabled on a PREEMPT_RT based configuration. As a first step to enable it to be used, let's try to attach the CPU devices to their PM domains on PREEMPT_RT. In this way the syscore ops becomes available, allowing the PM domain topology to be managed during s2ram. For the moment let's leave the support for CPU hotplug outside PREEMPT_RT, as it's depending on using runtime PM. For s2ram, this isn't a problem as all CPUs are managed via the syscore ops. Signed-off-by: Ulf Hansson Tested-by: Raghavendra Kakarla # qcm6490 with PREEMPT_RT set Acked-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20240527142557.321610-7-ulf.hansson@linaro.org --- drivers/cpuidle/cpuidle-psci.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/cpuidle/cpuidle-psci.c b/drivers/cpuidle/cpuidle-psci.c index d82a8bc1b194..ad6ce9fe12b4 100644 --- a/drivers/cpuidle/cpuidle-psci.c +++ b/drivers/cpuidle/cpuidle-psci.c @@ -37,6 +37,7 @@ struct psci_cpuidle_data { static DEFINE_PER_CPU_READ_MOSTLY(struct psci_cpuidle_data, psci_cpuidle_data); static DEFINE_PER_CPU(u32, domain_state); +static bool psci_cpuidle_use_syscore; static bool psci_cpuidle_use_cpuhp; void psci_set_domain_state(u32 state) @@ -166,6 +167,12 @@ static struct syscore_ops psci_idle_syscore_ops = { .resume = psci_idle_syscore_resume, }; +static void psci_idle_init_syscore(void) +{ + if (psci_cpuidle_use_syscore) + register_syscore_ops(&psci_idle_syscore_ops); +} + static void psci_idle_init_cpuhp(void) { int err; @@ -173,8 +180,6 @@ static void psci_idle_init_cpuhp(void) if (!psci_cpuidle_use_cpuhp) return; - register_syscore_ops(&psci_idle_syscore_ops); - err = cpuhp_setup_state_nocalls(CPUHP_AP_CPU_PM_STARTING, "cpuidle/psci:online", psci_idle_cpuhp_up, @@ -222,13 +227,16 @@ static int psci_dt_cpu_init_topology(struct cpuidle_driver *drv, if (!psci_has_osi_support()) return 0; - if (IS_ENABLED(CONFIG_PREEMPT_RT)) - return 0; - data->dev = dt_idle_attach_cpu(cpu, "psci"); if (IS_ERR_OR_NULL(data->dev)) return PTR_ERR_OR_ZERO(data->dev); + psci_cpuidle_use_syscore = true; + + /* The hierarchical topology is limited to s2ram on PREEMPT_RT. */ + if (IS_ENABLED(CONFIG_PREEMPT_RT)) + return 0; + /* * Using the deepest state for the CPU to trigger a potential selection * of a shared state for the domain, assumes the domain states are all @@ -312,6 +320,7 @@ static void psci_cpu_deinit_idle(int cpu) struct psci_cpuidle_data *data = per_cpu_ptr(&psci_cpuidle_data, cpu); dt_idle_detach_cpu(data->dev); + psci_cpuidle_use_syscore = false; psci_cpuidle_use_cpuhp = false; } @@ -408,6 +417,7 @@ static int psci_cpuidle_probe(struct platform_device *pdev) goto out_fail; } + psci_idle_init_syscore(); psci_idle_init_cpuhp(); return 0; From 1c4b2932bd629fe800282114ceb465d3eb5d0737 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 27 May 2024 16:25:57 +0200 Subject: [PATCH 075/573] cpuidle: psci: Enable the hierarchical topology for s2idle on PREEMPT_RT To enable the domain-idle-states to be used during s2idle on a PREEMPT_RT based configuration, let's allow the re-assignment of the ->enter_s2idle() callback to psci_enter_s2idle_domain_idle_state(). Similar to s2ram, let's leave the support for CPU hotplug outside PREEMPT_RT, as it's depending on using runtime PM. For s2idle, this means that an offline CPU's PM domain will remain powered-on. In practise this may lead to that a shallower idle-state than necessary gets selected, which shouldn't be an issue (besides wasting power). Signed-off-by: Ulf Hansson Tested-by: Raghavendra Kakarla # qcm6490 with PREEMPT_RT set Acked-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20240527142557.321610-8-ulf.hansson@linaro.org --- drivers/cpuidle/cpuidle-psci.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/cpuidle/cpuidle-psci.c b/drivers/cpuidle/cpuidle-psci.c index ad6ce9fe12b4..2562dc001fc1 100644 --- a/drivers/cpuidle/cpuidle-psci.c +++ b/drivers/cpuidle/cpuidle-psci.c @@ -233,18 +233,17 @@ static int psci_dt_cpu_init_topology(struct cpuidle_driver *drv, psci_cpuidle_use_syscore = true; - /* The hierarchical topology is limited to s2ram on PREEMPT_RT. */ - if (IS_ENABLED(CONFIG_PREEMPT_RT)) - return 0; - /* * Using the deepest state for the CPU to trigger a potential selection * of a shared state for the domain, assumes the domain states are all - * deeper states. + * deeper states. On PREEMPT_RT the hierarchical topology is limited to + * s2ram and s2idle. */ - drv->states[state_count - 1].enter = psci_enter_domain_idle_state; drv->states[state_count - 1].enter_s2idle = psci_enter_s2idle_domain_idle_state; - psci_cpuidle_use_cpuhp = true; + if (!IS_ENABLED(CONFIG_PREEMPT_RT)) { + drv->states[state_count - 1].enter = psci_enter_domain_idle_state; + psci_cpuidle_use_cpuhp = true; + } return 0; } From e0f49f15f6344ef3eeb0a04a8b5dedde2a454136 Mon Sep 17 00:00:00 2001 From: Kan Liang Date: Wed, 31 Jul 2024 07:13:49 -0700 Subject: [PATCH 076/573] perf/x86/intel/uncore: Add Arrow Lake support >From the perspective of the uncore PMU, the Arrow Lake is the same as the previous Meteor Lake. The only difference is the event list, which will be supported in the perf tool later. Signed-off-by: Kan Liang Signed-off-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/r/20240731141353.759643-1-kan.liang@linux.intel.com --- arch/x86/events/intel/uncore.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c index 64ca8625eb58..42968ad5101b 100644 --- a/arch/x86/events/intel/uncore.c +++ b/arch/x86/events/intel/uncore.c @@ -1893,6 +1893,9 @@ static const struct x86_cpu_id intel_uncore_match[] __initconst = { X86_MATCH_VFM(INTEL_RAPTORLAKE_S, &adl_uncore_init), X86_MATCH_VFM(INTEL_METEORLAKE, &mtl_uncore_init), X86_MATCH_VFM(INTEL_METEORLAKE_L, &mtl_uncore_init), + X86_MATCH_VFM(INTEL_ARROWLAKE, &mtl_uncore_init), + X86_MATCH_VFM(INTEL_ARROWLAKE_U, &mtl_uncore_init), + X86_MATCH_VFM(INTEL_ARROWLAKE_H, &mtl_uncore_init), X86_MATCH_VFM(INTEL_SAPPHIRERAPIDS_X, &spr_uncore_init), X86_MATCH_VFM(INTEL_EMERALDRAPIDS_X, &spr_uncore_init), X86_MATCH_VFM(INTEL_GRANITERAPIDS_X, &gnr_uncore_init), From efb0c9c0b9f78d964fb23ec6fdebe5a493f477f3 Mon Sep 17 00:00:00 2001 From: Kan Liang Date: Wed, 31 Jul 2024 07:13:50 -0700 Subject: [PATCH 077/573] perf/x86/intel/uncore: Factor out common MMIO init and ops functions Some uncore PMON registers are located in the MMIO space. For the client machine, the MMIO space is usually located at D0:F0 but in a different BAR. For example, some uncore PMON registers are located in the SAF BAR, not the MCHBAR in the Lunar Lake. The current __uncore_imc_init_box() hard code the BAR information. Factor out the uncore_get_box_mmio_addr() which uses the BAR information as a parameter. The only change is the error output message. The hardcode name 'MCHBAR' is replaced by the offset of a BAR. Add a new macro, MMIO_UNCORE_COMMON_OPS(), since the MMIO ops functions are usually the same among different generations. Signed-off-by: Kan Liang Signed-off-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/r/20240731141353.759643-2-kan.liang@linux.intel.com --- arch/x86/events/intel/uncore_snb.c | 47 +++++++++++++++++++----------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/arch/x86/events/intel/uncore_snb.c b/arch/x86/events/intel/uncore_snb.c index 9462fd9f3b7a..05fe6e90bd8e 100644 --- a/arch/x86/events/intel/uncore_snb.c +++ b/arch/x86/events/intel/uncore_snb.c @@ -1481,33 +1481,35 @@ static struct pci_dev *tgl_uncore_get_mc_dev(void) #define TGL_UNCORE_MMIO_IMC_MEM_OFFSET 0x10000 #define TGL_UNCORE_PCI_IMC_MAP_SIZE 0xe000 -static void __uncore_imc_init_box(struct intel_uncore_box *box, - unsigned int base_offset) +static void +uncore_get_box_mmio_addr(struct intel_uncore_box *box, + unsigned int base_offset, + int bar_offset, int step) { struct pci_dev *pdev = tgl_uncore_get_mc_dev(); struct intel_uncore_pmu *pmu = box->pmu; struct intel_uncore_type *type = pmu->type; resource_size_t addr; - u32 mch_bar; + u32 bar; if (!pdev) { pr_warn("perf uncore: Cannot find matched IMC device.\n"); return; } - pci_read_config_dword(pdev, SNB_UNCORE_PCI_IMC_BAR_OFFSET, &mch_bar); - /* MCHBAR is disabled */ - if (!(mch_bar & BIT(0))) { - pr_warn("perf uncore: MCHBAR is disabled. Failed to map IMC free-running counters.\n"); + pci_read_config_dword(pdev, bar_offset, &bar); + if (!(bar & BIT(0))) { + pr_warn("perf uncore: BAR 0x%x is disabled. Failed to map %s counters.\n", + bar_offset, type->name); pci_dev_put(pdev); return; } - mch_bar &= ~BIT(0); - addr = (resource_size_t)(mch_bar + TGL_UNCORE_MMIO_IMC_MEM_OFFSET * pmu->pmu_idx); + bar &= ~BIT(0); + addr = (resource_size_t)(bar + step * pmu->pmu_idx); #ifdef CONFIG_PHYS_ADDR_T_64BIT - pci_read_config_dword(pdev, SNB_UNCORE_PCI_IMC_BAR_OFFSET + 4, &mch_bar); - addr |= ((resource_size_t)mch_bar << 32); + pci_read_config_dword(pdev, bar_offset + 4, &bar); + addr |= ((resource_size_t)bar << 32); #endif addr += base_offset; @@ -1518,6 +1520,14 @@ static void __uncore_imc_init_box(struct intel_uncore_box *box, pci_dev_put(pdev); } +static void __uncore_imc_init_box(struct intel_uncore_box *box, + unsigned int base_offset) +{ + uncore_get_box_mmio_addr(box, base_offset, + SNB_UNCORE_PCI_IMC_BAR_OFFSET, + TGL_UNCORE_MMIO_IMC_MEM_OFFSET); +} + static void tgl_uncore_imc_freerunning_init_box(struct intel_uncore_box *box) { __uncore_imc_init_box(box, 0); @@ -1612,14 +1622,17 @@ static void adl_uncore_mmio_enable_box(struct intel_uncore_box *box) writel(0, box->io_addr + uncore_mmio_box_ctl(box)); } +#define MMIO_UNCORE_COMMON_OPS() \ + .exit_box = uncore_mmio_exit_box, \ + .disable_box = adl_uncore_mmio_disable_box, \ + .enable_box = adl_uncore_mmio_enable_box, \ + .disable_event = intel_generic_uncore_mmio_disable_event, \ + .enable_event = intel_generic_uncore_mmio_enable_event, \ + .read_counter = uncore_mmio_read_counter, + static struct intel_uncore_ops adl_uncore_mmio_ops = { .init_box = adl_uncore_imc_init_box, - .exit_box = uncore_mmio_exit_box, - .disable_box = adl_uncore_mmio_disable_box, - .enable_box = adl_uncore_mmio_enable_box, - .disable_event = intel_generic_uncore_mmio_disable_event, - .enable_event = intel_generic_uncore_mmio_enable_event, - .read_counter = uncore_mmio_read_counter, + MMIO_UNCORE_COMMON_OPS() }; #define ADL_UNC_CTL_CHMASK_MASK 0x00000f00 From 9bd7dfe3a5262d3b29debdc66e1410201a235019 Mon Sep 17 00:00:00 2001 From: Kan Liang Date: Wed, 31 Jul 2024 07:13:51 -0700 Subject: [PATCH 078/573] perf/x86/intel/uncore: Add Lunar Lake support The uncore subsystem for Lunar Lake is similar to the previous Meteor Lake. The uncore PerfMon registers are located at both MSR and MMIO space. The ARB and iMC are kept. There is no difference from the Meteor Lake. Move the global control initialization to the first box of the CBOX. The sNCU is moved to the MMIO space. The HBO is newly added and only be accessed from the MMIO space. Signed-off-by: Kan Liang Signed-off-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/r/20240731141353.759643-3-kan.liang@linux.intel.com --- arch/x86/events/intel/uncore.c | 6 ++ arch/x86/events/intel/uncore.h | 2 + arch/x86/events/intel/uncore_snb.c | 133 +++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c index 42968ad5101b..d98fac567684 100644 --- a/arch/x86/events/intel/uncore.c +++ b/arch/x86/events/intel/uncore.c @@ -1816,6 +1816,11 @@ static const struct intel_uncore_init_fun mtl_uncore_init __initconst = { .mmio_init = adl_uncore_mmio_init, }; +static const struct intel_uncore_init_fun lnl_uncore_init __initconst = { + .cpu_init = lnl_uncore_cpu_init, + .mmio_init = lnl_uncore_mmio_init, +}; + static const struct intel_uncore_init_fun icx_uncore_init __initconst = { .cpu_init = icx_uncore_cpu_init, .pci_init = icx_uncore_pci_init, @@ -1896,6 +1901,7 @@ static const struct x86_cpu_id intel_uncore_match[] __initconst = { X86_MATCH_VFM(INTEL_ARROWLAKE, &mtl_uncore_init), X86_MATCH_VFM(INTEL_ARROWLAKE_U, &mtl_uncore_init), X86_MATCH_VFM(INTEL_ARROWLAKE_H, &mtl_uncore_init), + X86_MATCH_VFM(INTEL_LUNARLAKE_M, &lnl_uncore_init), X86_MATCH_VFM(INTEL_SAPPHIRERAPIDS_X, &spr_uncore_init), X86_MATCH_VFM(INTEL_EMERALDRAPIDS_X, &spr_uncore_init), X86_MATCH_VFM(INTEL_GRANITERAPIDS_X, &gnr_uncore_init), diff --git a/arch/x86/events/intel/uncore.h b/arch/x86/events/intel/uncore.h index 027ef292c602..79ff32e13dcc 100644 --- a/arch/x86/events/intel/uncore.h +++ b/arch/x86/events/intel/uncore.h @@ -611,10 +611,12 @@ void skl_uncore_cpu_init(void); void icl_uncore_cpu_init(void); void tgl_uncore_cpu_init(void); void adl_uncore_cpu_init(void); +void lnl_uncore_cpu_init(void); void mtl_uncore_cpu_init(void); void tgl_uncore_mmio_init(void); void tgl_l_uncore_mmio_init(void); void adl_uncore_mmio_init(void); +void lnl_uncore_mmio_init(void); int snb_pci2phy_map_init(int devid); /* uncore_snbep.c */ diff --git a/arch/x86/events/intel/uncore_snb.c b/arch/x86/events/intel/uncore_snb.c index 05fe6e90bd8e..983beede73ac 100644 --- a/arch/x86/events/intel/uncore_snb.c +++ b/arch/x86/events/intel/uncore_snb.c @@ -252,6 +252,7 @@ DEFINE_UNCORE_FORMAT_ATTR(inv, inv, "config:23"); DEFINE_UNCORE_FORMAT_ATTR(cmask5, cmask, "config:24-28"); DEFINE_UNCORE_FORMAT_ATTR(cmask8, cmask, "config:24-31"); DEFINE_UNCORE_FORMAT_ATTR(threshold, threshold, "config:24-29"); +DEFINE_UNCORE_FORMAT_ATTR(threshold2, threshold, "config:24-31"); /* Sandy Bridge uncore support */ static void snb_uncore_msr_enable_event(struct intel_uncore_box *box, struct perf_event *event) @@ -746,6 +747,34 @@ void mtl_uncore_cpu_init(void) uncore_msr_uncores = mtl_msr_uncores; } +static struct intel_uncore_type *lnl_msr_uncores[] = { + &mtl_uncore_cbox, + &mtl_uncore_arb, + NULL +}; + +#define LNL_UNC_MSR_GLOBAL_CTL 0x240e + +static void lnl_uncore_msr_init_box(struct intel_uncore_box *box) +{ + if (box->pmu->pmu_idx == 0) + wrmsrl(LNL_UNC_MSR_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN); +} + +static struct intel_uncore_ops lnl_uncore_msr_ops = { + .init_box = lnl_uncore_msr_init_box, + .disable_event = snb_uncore_msr_disable_event, + .enable_event = snb_uncore_msr_enable_event, + .read_counter = uncore_msr_read_counter, +}; + +void lnl_uncore_cpu_init(void) +{ + mtl_uncore_cbox.num_boxes = 4; + mtl_uncore_cbox.ops = &lnl_uncore_msr_ops; + uncore_msr_uncores = lnl_msr_uncores; +} + enum { SNB_PCI_UNCORE_IMC, }; @@ -1716,3 +1745,107 @@ void adl_uncore_mmio_init(void) } /* end of Alder Lake MMIO uncore support */ + +/* Lunar Lake MMIO uncore support */ +#define LNL_UNCORE_PCI_SAFBAR_OFFSET 0x68 +#define LNL_UNCORE_MAP_SIZE 0x1000 +#define LNL_UNCORE_SNCU_BASE 0xE4B000 +#define LNL_UNCORE_SNCU_CTR 0x390 +#define LNL_UNCORE_SNCU_CTRL 0x398 +#define LNL_UNCORE_SNCU_BOX_CTL 0x380 +#define LNL_UNCORE_GLOBAL_CTL 0x700 +#define LNL_UNCORE_HBO_BASE 0xE54000 +#define LNL_UNCORE_HBO_OFFSET -4096 +#define LNL_UNCORE_HBO_CTR 0x570 +#define LNL_UNCORE_HBO_CTRL 0x550 +#define LNL_UNCORE_HBO_BOX_CTL 0x548 + +#define LNL_UNC_CTL_THRESHOLD 0xff000000 +#define LNL_UNC_RAW_EVENT_MASK (SNB_UNC_CTL_EV_SEL_MASK | \ + SNB_UNC_CTL_UMASK_MASK | \ + SNB_UNC_CTL_EDGE_DET | \ + SNB_UNC_CTL_INVERT | \ + LNL_UNC_CTL_THRESHOLD) + +static struct attribute *lnl_uncore_formats_attr[] = { + &format_attr_event.attr, + &format_attr_umask.attr, + &format_attr_edge.attr, + &format_attr_inv.attr, + &format_attr_threshold2.attr, + NULL +}; + +static const struct attribute_group lnl_uncore_format_group = { + .name = "format", + .attrs = lnl_uncore_formats_attr, +}; + +static void lnl_uncore_hbo_init_box(struct intel_uncore_box *box) +{ + uncore_get_box_mmio_addr(box, LNL_UNCORE_HBO_BASE, + LNL_UNCORE_PCI_SAFBAR_OFFSET, + LNL_UNCORE_HBO_OFFSET); +} + +static struct intel_uncore_ops lnl_uncore_hbo_ops = { + .init_box = lnl_uncore_hbo_init_box, + MMIO_UNCORE_COMMON_OPS() +}; + +static struct intel_uncore_type lnl_uncore_hbo = { + .name = "hbo", + .num_counters = 4, + .num_boxes = 2, + .perf_ctr_bits = 64, + .perf_ctr = LNL_UNCORE_HBO_CTR, + .event_ctl = LNL_UNCORE_HBO_CTRL, + .event_mask = LNL_UNC_RAW_EVENT_MASK, + .box_ctl = LNL_UNCORE_HBO_BOX_CTL, + .mmio_map_size = LNL_UNCORE_MAP_SIZE, + .ops = &lnl_uncore_hbo_ops, + .format_group = &lnl_uncore_format_group, +}; + +static void lnl_uncore_sncu_init_box(struct intel_uncore_box *box) +{ + uncore_get_box_mmio_addr(box, LNL_UNCORE_SNCU_BASE, + LNL_UNCORE_PCI_SAFBAR_OFFSET, + 0); + + if (box->io_addr) + writel(ADL_UNCORE_IMC_CTL_INT, box->io_addr + LNL_UNCORE_GLOBAL_CTL); +} + +static struct intel_uncore_ops lnl_uncore_sncu_ops = { + .init_box = lnl_uncore_sncu_init_box, + MMIO_UNCORE_COMMON_OPS() +}; + +static struct intel_uncore_type lnl_uncore_sncu = { + .name = "sncu", + .num_counters = 2, + .num_boxes = 1, + .perf_ctr_bits = 64, + .perf_ctr = LNL_UNCORE_SNCU_CTR, + .event_ctl = LNL_UNCORE_SNCU_CTRL, + .event_mask = LNL_UNC_RAW_EVENT_MASK, + .box_ctl = LNL_UNCORE_SNCU_BOX_CTL, + .mmio_map_size = LNL_UNCORE_MAP_SIZE, + .ops = &lnl_uncore_sncu_ops, + .format_group = &lnl_uncore_format_group, +}; + +static struct intel_uncore_type *lnl_mmio_uncores[] = { + &adl_uncore_imc, + &lnl_uncore_hbo, + &lnl_uncore_sncu, + NULL +}; + +void lnl_uncore_mmio_init(void) +{ + uncore_mmio_uncores = lnl_mmio_uncores; +} + +/* end of Lunar Lake MMIO uncore support */ From 9ac57c456fcb17f07f8792219479b0c841d75ba7 Mon Sep 17 00:00:00 2001 From: Zhenyu Wang Date: Wed, 31 Jul 2024 07:13:52 -0700 Subject: [PATCH 079/573] perf/x86/intel/uncore: Add LNL uncore iMC freerunning support LNL uncore imc freerunning counters keep same as previous HW. Signed-off-by: Zhenyu Wang Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Kan Liang Link: https://lore.kernel.org/r/20240731141353.759643-4-kan.liang@linux.intel.com --- arch/x86/events/intel/uncore_snb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/x86/events/intel/uncore_snb.c b/arch/x86/events/intel/uncore_snb.c index 983beede73ac..f7402bd4da6e 100644 --- a/arch/x86/events/intel/uncore_snb.c +++ b/arch/x86/events/intel/uncore_snb.c @@ -1840,6 +1840,7 @@ static struct intel_uncore_type *lnl_mmio_uncores[] = { &adl_uncore_imc, &lnl_uncore_hbo, &lnl_uncore_sncu, + &adl_uncore_imc_free_running, NULL }; From aaad0e2aa50723969f96b690f72e2f4aefa433f2 Mon Sep 17 00:00:00 2001 From: Zhenyu Wang Date: Wed, 31 Jul 2024 07:13:53 -0700 Subject: [PATCH 080/573] perf/x86/intel/uncore: Use D0:F0 as a default device Some uncore PMON registers are located in the MMIO space of the Host Bridge and DRAM Controller device, which is located at D0:F0 for Tiger Lake and later client generation. Use D0:F0 as a default device. So it doesn't need to keep adding the complete Device ID list for each generation anymore. Signed-off-by: Zhenyu Wang Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Kan Liang Link: https://lore.kernel.org/r/20240731141353.759643-5-kan.liang@linux.intel.com --- arch/x86/events/intel/uncore_snb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/x86/events/intel/uncore_snb.c b/arch/x86/events/intel/uncore_snb.c index f7402bd4da6e..3934e1e4e3b1 100644 --- a/arch/x86/events/intel/uncore_snb.c +++ b/arch/x86/events/intel/uncore_snb.c @@ -1504,6 +1504,10 @@ static struct pci_dev *tgl_uncore_get_mc_dev(void) ids++; } + /* Just try to grab 00:00.0 device */ + if (!mc_dev) + mc_dev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0)); + return mc_dev; } From ea1992f36b894fe60cc3537a7f6a7af4087b999a Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 2 Aug 2024 11:52:55 +0100 Subject: [PATCH 081/573] perf/x86/intel/bts: Fix comment about default perf_event_paranoid setting The default paranoid setting was updated in commit 0161028b7c8a ("perf/core: Change the default paranoia level to 2") so this comment is no longer true. Signed-off-by: James Clark Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/20240802105256.335961-1-james.clark@linaro.org --- arch/x86/events/intel/bts.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/x86/events/intel/bts.c b/arch/x86/events/intel/bts.c index 974e917e65b2..8f78b0c900ef 100644 --- a/arch/x86/events/intel/bts.c +++ b/arch/x86/events/intel/bts.c @@ -557,9 +557,6 @@ static int bts_event_init(struct perf_event *event) * disabled, so disallow intel_bts driver for unprivileged * users on paranoid systems since it provides trace data * to the user in a zero-copy fashion. - * - * Note that the default paranoia setting permits unprivileged - * users to profile the kernel. */ if (event->attr.exclude_kernel) { ret = perf_allow_kernel(&event->attr); From c114e9948c2b6a0b400266e59cc656b59e795bca Mon Sep 17 00:00:00 2001 From: Roman Kisel Date: Thu, 18 Jul 2024 11:27:24 -0700 Subject: [PATCH 082/573] coredump: Standartize and fix logging The coredump code does not log the process ID and the comm consistently, logs unescaped comm when it does log it, and does not always use the ratelimited logging. That makes it harder to analyze logs and puts the system at the risk of spamming the system log incase something crashes many times over and over again. Fix that by logging TGID and comm (escaped) consistently and using the ratelimited logging always. Signed-off-by: Roman Kisel Tested-by: Allen Pais Link: https://lore.kernel.org/r/20240718182743.1959160-2-romank@linux.microsoft.com Signed-off-by: Kees Cook --- fs/coredump.c | 43 +++++++++++++++------------------------- include/linux/coredump.h | 22 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index 7f12ff6ad1d3..87ff71a59fbe 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -586,8 +586,7 @@ void do_coredump(const kernel_siginfo_t *siginfo) struct subprocess_info *sub_info; if (ispipe < 0) { - printk(KERN_WARNING "format_corename failed\n"); - printk(KERN_WARNING "Aborting core\n"); + coredump_report_failure("format_corename failed, aborting core"); goto fail_unlock; } @@ -607,27 +606,21 @@ void do_coredump(const kernel_siginfo_t *siginfo) * right pid if a thread in a multi-threaded * core_pattern process dies. */ - printk(KERN_WARNING - "Process %d(%s) has RLIMIT_CORE set to 1\n", - task_tgid_vnr(current), current->comm); - printk(KERN_WARNING "Aborting core\n"); + coredump_report_failure("RLIMIT_CORE is set to 1, aborting core"); goto fail_unlock; } cprm.limit = RLIM_INFINITY; dump_count = atomic_inc_return(&core_dump_count); if (core_pipe_limit && (core_pipe_limit < dump_count)) { - printk(KERN_WARNING "Pid %d(%s) over core_pipe_limit\n", - task_tgid_vnr(current), current->comm); - printk(KERN_WARNING "Skipping core dump\n"); + coredump_report_failure("over core_pipe_limit, skipping core dump"); goto fail_dropcount; } helper_argv = kmalloc_array(argc + 1, sizeof(*helper_argv), GFP_KERNEL); if (!helper_argv) { - printk(KERN_WARNING "%s failed to allocate memory\n", - __func__); + coredump_report_failure("%s failed to allocate memory", __func__); goto fail_dropcount; } for (argi = 0; argi < argc; argi++) @@ -644,8 +637,7 @@ void do_coredump(const kernel_siginfo_t *siginfo) kfree(helper_argv); if (retval) { - printk(KERN_INFO "Core dump to |%s pipe failed\n", - cn.corename); + coredump_report_failure("|%s pipe failed", cn.corename); goto close_fail; } } else { @@ -658,10 +650,8 @@ void do_coredump(const kernel_siginfo_t *siginfo) goto fail_unlock; if (need_suid_safe && cn.corename[0] != '/') { - printk(KERN_WARNING "Pid %d(%s) can only dump core "\ - "to fully qualified path!\n", - task_tgid_vnr(current), current->comm); - printk(KERN_WARNING "Skipping core dump\n"); + coredump_report_failure( + "this process can only dump core to a fully qualified path, skipping core dump"); goto fail_unlock; } @@ -730,13 +720,13 @@ void do_coredump(const kernel_siginfo_t *siginfo) idmap = file_mnt_idmap(cprm.file); if (!vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode), current_fsuid())) { - pr_info_ratelimited("Core dump to %s aborted: cannot preserve file owner\n", - cn.corename); + coredump_report_failure("Core dump to %s aborted: " + "cannot preserve file owner", cn.corename); goto close_fail; } if ((inode->i_mode & 0677) != 0600) { - pr_info_ratelimited("Core dump to %s aborted: cannot preserve file permissions\n", - cn.corename); + coredump_report_failure("Core dump to %s aborted: " + "cannot preserve file permissions", cn.corename); goto close_fail; } if (!(cprm.file->f_mode & FMODE_CAN_WRITE)) @@ -757,7 +747,7 @@ void do_coredump(const kernel_siginfo_t *siginfo) * have this set to NULL. */ if (!cprm.file) { - pr_info("Core dump to |%s disabled\n", cn.corename); + coredump_report_failure("Core dump to |%s disabled", cn.corename); goto close_fail; } if (!dump_vma_snapshot(&cprm)) @@ -983,11 +973,10 @@ void validate_coredump_safety(void) { if (suid_dumpable == SUID_DUMP_ROOT && core_pattern[0] != '/' && core_pattern[0] != '|') { - pr_warn( -"Unsafe core_pattern used with fs.suid_dumpable=2.\n" -"Pipe handler or fully qualified core dump path required.\n" -"Set kernel.core_pattern before fs.suid_dumpable.\n" - ); + + coredump_report_failure("Unsafe core_pattern used with fs.suid_dumpable=2: " + "pipe handler or fully qualified core dump path required. " + "Set kernel.core_pattern before fs.suid_dumpable."); } } diff --git a/include/linux/coredump.h b/include/linux/coredump.h index 0904ba010341..45e598fe3476 100644 --- a/include/linux/coredump.h +++ b/include/linux/coredump.h @@ -43,8 +43,30 @@ extern int dump_align(struct coredump_params *cprm, int align); int dump_user_range(struct coredump_params *cprm, unsigned long start, unsigned long len); extern void do_coredump(const kernel_siginfo_t *siginfo); + +/* + * Logging for the coredump code, ratelimited. + * The TGID and comm fields are added to the message. + */ + +#define __COREDUMP_PRINTK(Level, Format, ...) \ + do { \ + char comm[TASK_COMM_LEN]; \ + \ + get_task_comm(comm, current); \ + printk_ratelimited(Level "coredump: %d(%*pE): " Format "\n", \ + task_tgid_vnr(current), (int)strlen(comm), comm, ##__VA_ARGS__); \ + } while (0) \ + +#define coredump_report(fmt, ...) __COREDUMP_PRINTK(KERN_INFO, fmt, ##__VA_ARGS__) +#define coredump_report_failure(fmt, ...) __COREDUMP_PRINTK(KERN_WARNING, fmt, ##__VA_ARGS__) + #else static inline void do_coredump(const kernel_siginfo_t *siginfo) {} + +#define coredump_report(...) +#define coredump_report_failure(...) + #endif #if defined(CONFIG_COREDUMP) && defined(CONFIG_SYSCTL) From fb97d2eb542faf19a8725afbd75cbc2518903210 Mon Sep 17 00:00:00 2001 From: Roman Kisel Date: Thu, 18 Jul 2024 11:27:25 -0700 Subject: [PATCH 083/573] binfmt_elf, coredump: Log the reason of the failed core dumps Missing, failed, or corrupted core dumps might impede crash investigations. To improve reliability of that process and consequently the programs themselves, one needs to trace the path from producing a core dumpfile to analyzing it. That path starts from the core dump file written to the disk by the kernel or to the standard input of a user mode helper program to which the kernel streams the coredump contents. There are cases where the kernel will interrupt writing the core out or produce a truncated/not-well-formed core dump without leaving a note. Add logging for the core dump collection failure paths to be able to reason what has gone wrong when the core dump is malformed or missing. Report the size of the data written to aid in diagnosing the user mode helper. Signed-off-by: Roman Kisel Link: https://lore.kernel.org/r/20240718182743.1959160-3-romank@linux.microsoft.com Signed-off-by: Kees Cook --- fs/binfmt_elf.c | 48 +++++++++++++----- fs/coredump.c | 107 ++++++++++++++++++++++++++++++++------- include/linux/coredump.h | 8 ++- kernel/signal.c | 21 +++++++- 4 files changed, 150 insertions(+), 34 deletions(-) diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index 19fa49cd9907..bf9bfd1a0007 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -2027,8 +2027,10 @@ static int elf_core_dump(struct coredump_params *cprm) * Collect all the non-memory information about the process for the * notes. This also sets up the file header. */ - if (!fill_note_info(&elf, e_phnum, &info, cprm)) + if (!fill_note_info(&elf, e_phnum, &info, cprm)) { + coredump_report_failure("Error collecting note info"); goto end_coredump; + } has_dumped = 1; @@ -2043,8 +2045,10 @@ static int elf_core_dump(struct coredump_params *cprm) sz += elf_coredump_extra_notes_size(); phdr4note = kmalloc(sizeof(*phdr4note), GFP_KERNEL); - if (!phdr4note) + if (!phdr4note) { + coredump_report_failure("Error allocating program headers note entry"); goto end_coredump; + } fill_elf_note_phdr(phdr4note, sz, offset); offset += sz; @@ -2058,18 +2062,24 @@ static int elf_core_dump(struct coredump_params *cprm) if (e_phnum == PN_XNUM) { shdr4extnum = kmalloc(sizeof(*shdr4extnum), GFP_KERNEL); - if (!shdr4extnum) + if (!shdr4extnum) { + coredump_report_failure("Error allocating extra program headers"); goto end_coredump; + } fill_extnum_info(&elf, shdr4extnum, e_shoff, segs); } offset = dataoff; - if (!dump_emit(cprm, &elf, sizeof(elf))) + if (!dump_emit(cprm, &elf, sizeof(elf))) { + coredump_report_failure("Error emitting the ELF headers"); goto end_coredump; + } - if (!dump_emit(cprm, phdr4note, sizeof(*phdr4note))) + if (!dump_emit(cprm, phdr4note, sizeof(*phdr4note))) { + coredump_report_failure("Error emitting the program header for notes"); goto end_coredump; + } /* Write program headers for segments dump */ for (i = 0; i < cprm->vma_count; i++) { @@ -2092,20 +2102,28 @@ static int elf_core_dump(struct coredump_params *cprm) phdr.p_flags |= PF_X; phdr.p_align = ELF_EXEC_PAGESIZE; - if (!dump_emit(cprm, &phdr, sizeof(phdr))) + if (!dump_emit(cprm, &phdr, sizeof(phdr))) { + coredump_report_failure("Error emitting program headers"); goto end_coredump; + } } - if (!elf_core_write_extra_phdrs(cprm, offset)) + if (!elf_core_write_extra_phdrs(cprm, offset)) { + coredump_report_failure("Error writing out extra program headers"); goto end_coredump; + } /* write out the notes section */ - if (!write_note_info(&info, cprm)) + if (!write_note_info(&info, cprm)) { + coredump_report_failure("Error writing out notes"); goto end_coredump; + } /* For cell spufs */ - if (elf_coredump_extra_notes_write(cprm)) + if (elf_coredump_extra_notes_write(cprm)) { + coredump_report_failure("Error writing out extra notes"); goto end_coredump; + } /* Align to page */ dump_skip_to(cprm, dataoff); @@ -2113,16 +2131,22 @@ static int elf_core_dump(struct coredump_params *cprm) for (i = 0; i < cprm->vma_count; i++) { struct core_vma_metadata *meta = cprm->vma_meta + i; - if (!dump_user_range(cprm, meta->start, meta->dump_size)) + if (!dump_user_range(cprm, meta->start, meta->dump_size)) { + coredump_report_failure("Error writing out the process memory"); goto end_coredump; + } } - if (!elf_core_write_extra_data(cprm)) + if (!elf_core_write_extra_data(cprm)) { + coredump_report_failure("Error writing out extra data"); goto end_coredump; + } if (e_phnum == PN_XNUM) { - if (!dump_emit(cprm, shdr4extnum, sizeof(*shdr4extnum))) + if (!dump_emit(cprm, shdr4extnum, sizeof(*shdr4extnum))) { + coredump_report_failure("Error emitting extra program headers"); goto end_coredump; + } } end_coredump: diff --git a/fs/coredump.c b/fs/coredump.c index 87ff71a59fbe..5814a6d781ce 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -464,7 +464,17 @@ static bool dump_interrupted(void) * but then we need to teach dump_write() to restart and clear * TIF_SIGPENDING. */ - return fatal_signal_pending(current) || freezing(current); + if (fatal_signal_pending(current)) { + coredump_report_failure("interrupted: fatal signal pending"); + return true; + } + + if (freezing(current)) { + coredump_report_failure("interrupted: freezing"); + return true; + } + + return false; } static void wait_for_dump_helpers(struct file *file) @@ -519,7 +529,7 @@ static int umh_pipe_setup(struct subprocess_info *info, struct cred *new) return err; } -void do_coredump(const kernel_siginfo_t *siginfo) +int do_coredump(const kernel_siginfo_t *siginfo) { struct core_state core_state; struct core_name cn; @@ -527,7 +537,7 @@ void do_coredump(const kernel_siginfo_t *siginfo) struct linux_binfmt * binfmt; const struct cred *old_cred; struct cred *cred; - int retval = 0; + int retval; int ispipe; size_t *argv = NULL; int argc = 0; @@ -551,14 +561,20 @@ void do_coredump(const kernel_siginfo_t *siginfo) audit_core_dumps(siginfo->si_signo); binfmt = mm->binfmt; - if (!binfmt || !binfmt->core_dump) + if (!binfmt || !binfmt->core_dump) { + retval = -ENOEXEC; goto fail; - if (!__get_dumpable(cprm.mm_flags)) + } + if (!__get_dumpable(cprm.mm_flags)) { + retval = -EACCES; goto fail; + } cred = prepare_creds(); - if (!cred) + if (!cred) { + retval = -EPERM; goto fail; + } /* * We cannot trust fsuid as being the "true" uid of the process * nor do we know its entire history. We only know it was tainted @@ -587,6 +603,7 @@ void do_coredump(const kernel_siginfo_t *siginfo) if (ispipe < 0) { coredump_report_failure("format_corename failed, aborting core"); + retval = ispipe; goto fail_unlock; } @@ -607,6 +624,7 @@ void do_coredump(const kernel_siginfo_t *siginfo) * core_pattern process dies. */ coredump_report_failure("RLIMIT_CORE is set to 1, aborting core"); + retval = -EPERM; goto fail_unlock; } cprm.limit = RLIM_INFINITY; @@ -614,6 +632,7 @@ void do_coredump(const kernel_siginfo_t *siginfo) dump_count = atomic_inc_return(&core_dump_count); if (core_pipe_limit && (core_pipe_limit < dump_count)) { coredump_report_failure("over core_pipe_limit, skipping core dump"); + retval = -E2BIG; goto fail_dropcount; } @@ -621,6 +640,7 @@ void do_coredump(const kernel_siginfo_t *siginfo) GFP_KERNEL); if (!helper_argv) { coredump_report_failure("%s failed to allocate memory", __func__); + retval = -ENOMEM; goto fail_dropcount; } for (argi = 0; argi < argc; argi++) @@ -646,12 +666,16 @@ void do_coredump(const kernel_siginfo_t *siginfo) int open_flags = O_CREAT | O_WRONLY | O_NOFOLLOW | O_LARGEFILE | O_EXCL; - if (cprm.limit < binfmt->min_coredump) + if (cprm.limit < binfmt->min_coredump) { + coredump_report_failure("over coredump resource limit, skipping core dump"); + retval = -E2BIG; goto fail_unlock; + } if (need_suid_safe && cn.corename[0] != '/') { coredump_report_failure( "this process can only dump core to a fully qualified path, skipping core dump"); + retval = -EPERM; goto fail_unlock; } @@ -697,20 +721,28 @@ void do_coredump(const kernel_siginfo_t *siginfo) } else { cprm.file = filp_open(cn.corename, open_flags, 0600); } - if (IS_ERR(cprm.file)) + if (IS_ERR(cprm.file)) { + retval = PTR_ERR(cprm.file); goto fail_unlock; + } inode = file_inode(cprm.file); - if (inode->i_nlink > 1) + if (inode->i_nlink > 1) { + retval = -EMLINK; goto close_fail; - if (d_unhashed(cprm.file->f_path.dentry)) + } + if (d_unhashed(cprm.file->f_path.dentry)) { + retval = -EEXIST; goto close_fail; + } /* * AK: actually i see no reason to not allow this for named * pipes etc, but keep the previous behaviour for now. */ - if (!S_ISREG(inode->i_mode)) + if (!S_ISREG(inode->i_mode)) { + retval = -EISDIR; goto close_fail; + } /* * Don't dump core if the filesystem changed owner or mode * of the file during file creation. This is an issue when @@ -722,17 +754,22 @@ void do_coredump(const kernel_siginfo_t *siginfo) current_fsuid())) { coredump_report_failure("Core dump to %s aborted: " "cannot preserve file owner", cn.corename); + retval = -EPERM; goto close_fail; } if ((inode->i_mode & 0677) != 0600) { coredump_report_failure("Core dump to %s aborted: " "cannot preserve file permissions", cn.corename); + retval = -EPERM; goto close_fail; } - if (!(cprm.file->f_mode & FMODE_CAN_WRITE)) + if (!(cprm.file->f_mode & FMODE_CAN_WRITE)) { + retval = -EACCES; goto close_fail; - if (do_truncate(idmap, cprm.file->f_path.dentry, - 0, 0, cprm.file)) + } + retval = do_truncate(idmap, cprm.file->f_path.dentry, + 0, 0, cprm.file); + if (retval) goto close_fail; } @@ -748,10 +785,15 @@ void do_coredump(const kernel_siginfo_t *siginfo) */ if (!cprm.file) { coredump_report_failure("Core dump to |%s disabled", cn.corename); + retval = -EPERM; goto close_fail; } - if (!dump_vma_snapshot(&cprm)) + if (!dump_vma_snapshot(&cprm)) { + coredump_report_failure("Can't get VMA snapshot for core dump |%s", + cn.corename); + retval = -EACCES; goto close_fail; + } file_start_write(cprm.file); core_dumped = binfmt->core_dump(&cprm); @@ -767,9 +809,21 @@ void do_coredump(const kernel_siginfo_t *siginfo) } file_end_write(cprm.file); free_vma_snapshot(&cprm); + } else { + coredump_report_failure("Core dump to %s%s has been interrupted", + ispipe ? "|" : "", cn.corename); + retval = -EAGAIN; + goto fail; } + coredump_report( + "written to %s%s: VMAs: %d, size %zu; core: %lld bytes, pos %lld", + ispipe ? "|" : "", cn.corename, + cprm.vma_count, cprm.vma_data_size, cprm.written, cprm.pos); if (ispipe && core_pipe_limit) wait_for_dump_helpers(cprm.file); + + retval = 0; + close_fail: if (cprm.file) filp_close(cprm.file, NULL); @@ -784,7 +838,7 @@ fail_unlock: fail_creds: put_cred(cred); fail: - return; + return retval; } /* @@ -804,8 +858,16 @@ static int __dump_emit(struct coredump_params *cprm, const void *addr, int nr) if (dump_interrupted()) return 0; n = __kernel_write(file, addr, nr, &pos); - if (n != nr) + if (n != nr) { + if (n < 0) + coredump_report_failure("failed when writing out, error %zd", n); + else + coredump_report_failure( + "partially written out, only %zd(of %d) bytes written", + n, nr); + return 0; + } file->f_pos = pos; cprm->written += n; cprm->pos += n; @@ -818,9 +880,16 @@ static int __dump_skip(struct coredump_params *cprm, size_t nr) static char zeroes[PAGE_SIZE]; struct file *file = cprm->file; if (file->f_mode & FMODE_LSEEK) { - if (dump_interrupted() || - vfs_llseek(file, nr, SEEK_CUR) < 0) + int ret; + + if (dump_interrupted()) return 0; + + ret = vfs_llseek(file, nr, SEEK_CUR); + if (ret < 0) { + coredump_report_failure("failed when seeking, error %d", ret); + return 0; + } cprm->pos += nr; return 1; } else { diff --git a/include/linux/coredump.h b/include/linux/coredump.h index 45e598fe3476..edeb8532ce0f 100644 --- a/include/linux/coredump.h +++ b/include/linux/coredump.h @@ -42,7 +42,7 @@ extern int dump_emit(struct coredump_params *cprm, const void *addr, int nr); extern int dump_align(struct coredump_params *cprm, int align); int dump_user_range(struct coredump_params *cprm, unsigned long start, unsigned long len); -extern void do_coredump(const kernel_siginfo_t *siginfo); +extern int do_coredump(const kernel_siginfo_t *siginfo); /* * Logging for the coredump code, ratelimited. @@ -62,7 +62,11 @@ extern void do_coredump(const kernel_siginfo_t *siginfo); #define coredump_report_failure(fmt, ...) __COREDUMP_PRINTK(KERN_WARNING, fmt, ##__VA_ARGS__) #else -static inline void do_coredump(const kernel_siginfo_t *siginfo) {} +static inline int do_coredump(const kernel_siginfo_t *siginfo) +{ + /* Coredump support is not available, can't fail. */ + return 0; +} #define coredump_report(...) #define coredump_report_failure(...) diff --git a/kernel/signal.c b/kernel/signal.c index 60c737e423a1..8c3417550d71 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -2888,6 +2888,8 @@ relock: current->flags |= PF_SIGNALED; if (sig_kernel_coredump(signr)) { + int ret; + if (print_fatal_signals) print_fatal_signal(signr); proc_coredump_connector(current); @@ -2899,7 +2901,24 @@ relock: * first and our do_group_exit call below will use * that value and ignore the one we pass it. */ - do_coredump(&ksig->info); + ret = do_coredump(&ksig->info); + if (ret) + coredump_report_failure("coredump has not been created, error %d", + ret); + else if (!IS_ENABLED(CONFIG_COREDUMP)) { + /* + * Coredumps are not available, can't fail collecting + * the coredump. + * + * Leave a note though that the coredump is going to be + * not created. This is not an error or a warning as disabling + * support in the kernel for coredumps isn't commonplace, and + * the user must've built the kernel with the custom config so + * let them know all works as desired. + */ + coredump_report("no coredump collected as " + "that is disabled in the kernel configuration"); + } } /* From 62be134abf4250474a7a694837064bc783d2b291 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 6 Aug 2024 21:54:09 -0500 Subject: [PATCH 084/573] platform/chrome: cros_ec_lpc: switch primary DMI data for Framework Laptop Framework Computer appears to be moving away from the Microchip embedded controller seen in the Framework Laptop 13 with Intel Core 11th, 12th and 13th generation processors. All newer models use a Nuvoton NPCX embedded controller. Changing the default DMI match for Framework's products to match their newer product lines will reduce churn in this part of the cros_ec_lpc driver. The new match tables are: - Microchip EC models - "Laptop" (product, exact match) for the 11th gen. Intel Core - "12th Gen Intel Core" (product, exact match) - "13th Gen Intel Core" (product, exact match) - Nuvoton NPCX models - "Laptop" (product family, partial match) Signed-off-by: Dustin L. Howett Reviewed-by: Alexandru M Stan Link: https://lore.kernel.org/r/20240806-platform-chrome-cros_ec_lpcs-change-the-default-disposition-of-the-framework-laptop-v1-1-09e0d602b215@howett.net Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/cros_ec_lpc.c | 36 ++++++++++++++++++++------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index f0470248b109..c784119ab5dc 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -631,12 +631,12 @@ static const struct acpi_device_id cros_ec_lpc_acpi_device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, cros_ec_lpc_acpi_device_ids); -static const struct lpc_driver_data framework_laptop_amd_lpc_driver_data __initconst = { +static const struct lpc_driver_data framework_laptop_npcx_lpc_driver_data __initconst = { .quirks = CROS_EC_LPC_QUIRK_REMAP_MEMORY, .quirk_mmio_memory_base = 0xE00, }; -static const struct lpc_driver_data framework_laptop_11_lpc_driver_data __initconst = { +static const struct lpc_driver_data framework_laptop_mec_lpc_driver_data __initconst = { .quirks = CROS_EC_LPC_QUIRK_ACPI_ID|CROS_EC_LPC_QUIRK_AML_MUTEX, .quirk_acpi_id = "PNP0C09", .quirk_aml_mutex_name = "ECMT", @@ -696,21 +696,39 @@ static const struct dmi_system_id cros_ec_lpc_dmi_table[] __initconst = { }, /* A small number of non-Chromebook/box machines also use the ChromeOS EC */ { - /* the Framework Laptop 13 (AMD Ryzen) and 16 (AMD Ryzen) */ + /* Framework Laptop (11th Gen Intel Core) */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Framework"), - DMI_MATCH(DMI_PRODUCT_NAME, "AMD Ryzen"), - DMI_MATCH(DMI_PRODUCT_FAMILY, "Laptop"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Laptop"), }, - .driver_data = (void *)&framework_laptop_amd_lpc_driver_data, + .driver_data = (void *)&framework_laptop_mec_lpc_driver_data, }, { - /* the Framework Laptop (Intel 11th, 12th, 13th Generation) */ + /* Framework Laptop (12th Gen Intel Core) */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Framework"), - DMI_MATCH(DMI_PRODUCT_NAME, "Laptop"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "12th Gen Intel Core"), }, - .driver_data = (void *)&framework_laptop_11_lpc_driver_data, + .driver_data = (void *)&framework_laptop_mec_lpc_driver_data, + }, + { + /* Framework Laptop (13th Gen Intel Core) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Framework"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "13th Gen Intel Core"), + }, + .driver_data = (void *)&framework_laptop_mec_lpc_driver_data, + }, + { + /* + * All remaining Framework Laptop models (13 AMD Ryzen, 16 AMD + * Ryzen, Intel Core Ultra) + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Framework"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Laptop"), + }, + .driver_data = (void *)&framework_laptop_npcx_lpc_driver_data, }, { /* sentinel */ } }; From 2d17cf1abcbe8a45b7dc41a768ed22aac158ddd8 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Wed, 7 Aug 2024 13:29:25 +0200 Subject: [PATCH 085/573] perf: Optimize context reschedule for single PMU cases Currently re-scheduling a context will reschedule all active PMUs for that context, even if it is known only a single event is added. Namhyung reported that changing this to only reschedule the affected PMU when possible provides significant performance gains under certain conditions. Therefore, allow partial context reschedules for a specific PMU, that of the event modified. While the patch looks somewhat noisy, it mostly just propagates a new @pmu argument through the callchain and modifies the epc loop to only pick the 'epc->pmu == @pmu' case. Reported-by: Namhyung Kim Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Kan Liang Reviewed-by: Namhyung Kim Link: https://lore.kernel.org/r/20240807115549.920950699@infradead.org --- kernel/events/core.c | 164 +++++++++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 76 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index c01a32687dad..dad2b9ac42c0 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -685,30 +685,32 @@ do { \ ___p; \ }) +#define for_each_epc(_epc, _ctx, _pmu, _cgroup) \ + list_for_each_entry(_epc, &((_ctx)->pmu_ctx_list), pmu_ctx_entry) \ + if (_cgroup && !_epc->nr_cgroups) \ + continue; \ + else if (_pmu && _epc->pmu != _pmu) \ + continue; \ + else + static void perf_ctx_disable(struct perf_event_context *ctx, bool cgroup) { struct perf_event_pmu_context *pmu_ctx; - list_for_each_entry(pmu_ctx, &ctx->pmu_ctx_list, pmu_ctx_entry) { - if (cgroup && !pmu_ctx->nr_cgroups) - continue; + for_each_epc(pmu_ctx, ctx, NULL, cgroup) perf_pmu_disable(pmu_ctx->pmu); - } } static void perf_ctx_enable(struct perf_event_context *ctx, bool cgroup) { struct perf_event_pmu_context *pmu_ctx; - list_for_each_entry(pmu_ctx, &ctx->pmu_ctx_list, pmu_ctx_entry) { - if (cgroup && !pmu_ctx->nr_cgroups) - continue; + for_each_epc(pmu_ctx, ctx, NULL, cgroup) perf_pmu_enable(pmu_ctx->pmu); - } } -static void ctx_sched_out(struct perf_event_context *ctx, enum event_type_t event_type); -static void ctx_sched_in(struct perf_event_context *ctx, enum event_type_t event_type); +static void ctx_sched_out(struct perf_event_context *ctx, struct pmu *pmu, enum event_type_t event_type); +static void ctx_sched_in(struct perf_event_context *ctx, struct pmu *pmu, enum event_type_t event_type); #ifdef CONFIG_CGROUP_PERF @@ -865,7 +867,7 @@ static void perf_cgroup_switch(struct task_struct *task) perf_ctx_lock(cpuctx, cpuctx->task_ctx); perf_ctx_disable(&cpuctx->ctx, true); - ctx_sched_out(&cpuctx->ctx, EVENT_ALL|EVENT_CGROUP); + ctx_sched_out(&cpuctx->ctx, NULL, EVENT_ALL|EVENT_CGROUP); /* * must not be done before ctxswout due * to update_cgrp_time_from_cpuctx() in @@ -877,7 +879,7 @@ static void perf_cgroup_switch(struct task_struct *task) * perf_cgroup_set_timestamp() in ctx_sched_in() * to not have to pass task around */ - ctx_sched_in(&cpuctx->ctx, EVENT_ALL|EVENT_CGROUP); + ctx_sched_in(&cpuctx->ctx, NULL, EVENT_ALL|EVENT_CGROUP); perf_ctx_enable(&cpuctx->ctx, true); perf_ctx_unlock(cpuctx, cpuctx->task_ctx); @@ -2656,7 +2658,8 @@ static void add_event_to_ctx(struct perf_event *event, } static void task_ctx_sched_out(struct perf_event_context *ctx, - enum event_type_t event_type) + struct pmu *pmu, + enum event_type_t event_type) { struct perf_cpu_context *cpuctx = this_cpu_ptr(&perf_cpu_context); @@ -2666,18 +2669,19 @@ static void task_ctx_sched_out(struct perf_event_context *ctx, if (WARN_ON_ONCE(ctx != cpuctx->task_ctx)) return; - ctx_sched_out(ctx, event_type); + ctx_sched_out(ctx, pmu, event_type); } static void perf_event_sched_in(struct perf_cpu_context *cpuctx, - struct perf_event_context *ctx) + struct perf_event_context *ctx, + struct pmu *pmu) { - ctx_sched_in(&cpuctx->ctx, EVENT_PINNED); + ctx_sched_in(&cpuctx->ctx, pmu, EVENT_PINNED); if (ctx) - ctx_sched_in(ctx, EVENT_PINNED); - ctx_sched_in(&cpuctx->ctx, EVENT_FLEXIBLE); + ctx_sched_in(ctx, pmu, EVENT_PINNED); + ctx_sched_in(&cpuctx->ctx, pmu, EVENT_FLEXIBLE); if (ctx) - ctx_sched_in(ctx, EVENT_FLEXIBLE); + ctx_sched_in(ctx, pmu, EVENT_FLEXIBLE); } /* @@ -2695,16 +2699,12 @@ static void perf_event_sched_in(struct perf_cpu_context *cpuctx, * event_type is a bit mask of the types of events involved. For CPU events, * event_type is only either EVENT_PINNED or EVENT_FLEXIBLE. */ -/* - * XXX: ctx_resched() reschedule entire perf_event_context while adding new - * event to the context or enabling existing event in the context. We can - * probably optimize it by rescheduling only affected pmu_ctx. - */ static void ctx_resched(struct perf_cpu_context *cpuctx, struct perf_event_context *task_ctx, - enum event_type_t event_type) + struct pmu *pmu, enum event_type_t event_type) { bool cpu_event = !!(event_type & EVENT_CPU); + struct perf_event_pmu_context *epc; /* * If pinned groups are involved, flexible groups also need to be @@ -2715,10 +2715,14 @@ static void ctx_resched(struct perf_cpu_context *cpuctx, event_type &= EVENT_ALL; - perf_ctx_disable(&cpuctx->ctx, false); + for_each_epc(epc, &cpuctx->ctx, pmu, false) + perf_pmu_disable(epc->pmu); + if (task_ctx) { - perf_ctx_disable(task_ctx, false); - task_ctx_sched_out(task_ctx, event_type); + for_each_epc(epc, task_ctx, pmu, false) + perf_pmu_disable(epc->pmu); + + task_ctx_sched_out(task_ctx, pmu, event_type); } /* @@ -2729,15 +2733,19 @@ static void ctx_resched(struct perf_cpu_context *cpuctx, * - otherwise, do nothing more. */ if (cpu_event) - ctx_sched_out(&cpuctx->ctx, event_type); + ctx_sched_out(&cpuctx->ctx, pmu, event_type); else if (event_type & EVENT_PINNED) - ctx_sched_out(&cpuctx->ctx, EVENT_FLEXIBLE); + ctx_sched_out(&cpuctx->ctx, pmu, EVENT_FLEXIBLE); - perf_event_sched_in(cpuctx, task_ctx); + perf_event_sched_in(cpuctx, task_ctx, pmu); - perf_ctx_enable(&cpuctx->ctx, false); - if (task_ctx) - perf_ctx_enable(task_ctx, false); + for_each_epc(epc, &cpuctx->ctx, pmu, false) + perf_pmu_enable(epc->pmu); + + if (task_ctx) { + for_each_epc(epc, task_ctx, pmu, false) + perf_pmu_enable(epc->pmu); + } } void perf_pmu_resched(struct pmu *pmu) @@ -2746,7 +2754,7 @@ void perf_pmu_resched(struct pmu *pmu) struct perf_event_context *task_ctx = cpuctx->task_ctx; perf_ctx_lock(cpuctx, task_ctx); - ctx_resched(cpuctx, task_ctx, EVENT_ALL|EVENT_CPU); + ctx_resched(cpuctx, task_ctx, pmu, EVENT_ALL|EVENT_CPU); perf_ctx_unlock(cpuctx, task_ctx); } @@ -2802,9 +2810,10 @@ static int __perf_install_in_context(void *info) #endif if (reprogram) { - ctx_sched_out(ctx, EVENT_TIME); + ctx_sched_out(ctx, NULL, EVENT_TIME); add_event_to_ctx(event, ctx); - ctx_resched(cpuctx, task_ctx, get_event_type(event)); + ctx_resched(cpuctx, task_ctx, event->pmu_ctx->pmu, + get_event_type(event)); } else { add_event_to_ctx(event, ctx); } @@ -2948,7 +2957,7 @@ static void __perf_event_enable(struct perf_event *event, return; if (ctx->is_active) - ctx_sched_out(ctx, EVENT_TIME); + ctx_sched_out(ctx, NULL, EVENT_TIME); perf_event_set_state(event, PERF_EVENT_STATE_INACTIVE); perf_cgroup_event_enable(event, ctx); @@ -2957,7 +2966,7 @@ static void __perf_event_enable(struct perf_event *event, return; if (!event_filter_match(event)) { - ctx_sched_in(ctx, EVENT_TIME); + ctx_sched_in(ctx, NULL, EVENT_TIME); return; } @@ -2966,7 +2975,7 @@ static void __perf_event_enable(struct perf_event *event, * then don't put it on unless the group is on. */ if (leader != event && leader->state != PERF_EVENT_STATE_ACTIVE) { - ctx_sched_in(ctx, EVENT_TIME); + ctx_sched_in(ctx, NULL, EVENT_TIME); return; } @@ -2974,7 +2983,7 @@ static void __perf_event_enable(struct perf_event *event, if (ctx->task) WARN_ON_ONCE(task_ctx != ctx); - ctx_resched(cpuctx, task_ctx, get_event_type(event)); + ctx_resched(cpuctx, task_ctx, event->pmu_ctx->pmu, get_event_type(event)); } /* @@ -3276,8 +3285,17 @@ static void __pmu_ctx_sched_out(struct perf_event_pmu_context *pmu_ctx, perf_pmu_enable(pmu); } +/* + * Be very careful with the @pmu argument since this will change ctx state. + * The @pmu argument works for ctx_resched(), because that is symmetric in + * ctx_sched_out() / ctx_sched_in() usage and the ctx state ends up invariant. + * + * However, if you were to be asymmetrical, you could end up with messed up + * state, eg. ctx->is_active cleared even though most EPCs would still actually + * be active. + */ static void -ctx_sched_out(struct perf_event_context *ctx, enum event_type_t event_type) +ctx_sched_out(struct perf_event_context *ctx, struct pmu *pmu, enum event_type_t event_type) { struct perf_cpu_context *cpuctx = this_cpu_ptr(&perf_cpu_context); struct perf_event_pmu_context *pmu_ctx; @@ -3331,11 +3349,8 @@ ctx_sched_out(struct perf_event_context *ctx, enum event_type_t event_type) is_active ^= ctx->is_active; /* changed bits */ - list_for_each_entry(pmu_ctx, &ctx->pmu_ctx_list, pmu_ctx_entry) { - if (cgroup && !pmu_ctx->nr_cgroups) - continue; + for_each_epc(pmu_ctx, ctx, pmu, cgroup) __pmu_ctx_sched_out(pmu_ctx, is_active); - } } /* @@ -3579,7 +3594,7 @@ unlock: inside_switch: perf_ctx_sched_task_cb(ctx, false); - task_ctx_sched_out(ctx, EVENT_ALL); + task_ctx_sched_out(ctx, NULL, EVENT_ALL); perf_ctx_enable(ctx, false); raw_spin_unlock(&ctx->lock); @@ -3877,29 +3892,22 @@ static void pmu_groups_sched_in(struct perf_event_context *ctx, merge_sched_in, &can_add_hw); } -static void ctx_groups_sched_in(struct perf_event_context *ctx, - struct perf_event_groups *groups, - bool cgroup) +static void __pmu_ctx_sched_in(struct perf_event_pmu_context *pmu_ctx, + enum event_type_t event_type) { - struct perf_event_pmu_context *pmu_ctx; + struct perf_event_context *ctx = pmu_ctx->ctx; - list_for_each_entry(pmu_ctx, &ctx->pmu_ctx_list, pmu_ctx_entry) { - if (cgroup && !pmu_ctx->nr_cgroups) - continue; - pmu_groups_sched_in(ctx, groups, pmu_ctx->pmu); - } -} - -static void __pmu_ctx_sched_in(struct perf_event_context *ctx, - struct pmu *pmu) -{ - pmu_groups_sched_in(ctx, &ctx->flexible_groups, pmu); + if (event_type & EVENT_PINNED) + pmu_groups_sched_in(ctx, &ctx->pinned_groups, pmu_ctx->pmu); + if (event_type & EVENT_FLEXIBLE) + pmu_groups_sched_in(ctx, &ctx->flexible_groups, pmu_ctx->pmu); } static void -ctx_sched_in(struct perf_event_context *ctx, enum event_type_t event_type) +ctx_sched_in(struct perf_event_context *ctx, struct pmu *pmu, enum event_type_t event_type) { struct perf_cpu_context *cpuctx = this_cpu_ptr(&perf_cpu_context); + struct perf_event_pmu_context *pmu_ctx; int is_active = ctx->is_active; bool cgroup = event_type & EVENT_CGROUP; @@ -3935,12 +3943,16 @@ ctx_sched_in(struct perf_event_context *ctx, enum event_type_t event_type) * First go through the list and put on any pinned groups * in order to give them the best chance of going on. */ - if (is_active & EVENT_PINNED) - ctx_groups_sched_in(ctx, &ctx->pinned_groups, cgroup); + if (is_active & EVENT_PINNED) { + for_each_epc(pmu_ctx, ctx, pmu, cgroup) + __pmu_ctx_sched_in(pmu_ctx, EVENT_PINNED); + } /* Then walk through the lower prio flexible groups */ - if (is_active & EVENT_FLEXIBLE) - ctx_groups_sched_in(ctx, &ctx->flexible_groups, cgroup); + if (is_active & EVENT_FLEXIBLE) { + for_each_epc(pmu_ctx, ctx, pmu, cgroup) + __pmu_ctx_sched_in(pmu_ctx, EVENT_FLEXIBLE); + } } static void perf_event_context_sched_in(struct task_struct *task) @@ -3983,10 +3995,10 @@ static void perf_event_context_sched_in(struct task_struct *task) */ if (!RB_EMPTY_ROOT(&ctx->pinned_groups.tree)) { perf_ctx_disable(&cpuctx->ctx, false); - ctx_sched_out(&cpuctx->ctx, EVENT_FLEXIBLE); + ctx_sched_out(&cpuctx->ctx, NULL, EVENT_FLEXIBLE); } - perf_event_sched_in(cpuctx, ctx); + perf_event_sched_in(cpuctx, ctx, NULL); perf_ctx_sched_task_cb(cpuctx->task_ctx, true); @@ -4327,14 +4339,14 @@ static bool perf_rotate_context(struct perf_cpu_pmu_context *cpc) update_context_time(&cpuctx->ctx); __pmu_ctx_sched_out(cpu_epc, EVENT_FLEXIBLE); rotate_ctx(&cpuctx->ctx, cpu_event); - __pmu_ctx_sched_in(&cpuctx->ctx, pmu); + __pmu_ctx_sched_in(cpu_epc, EVENT_FLEXIBLE); } if (task_event) rotate_ctx(task_epc->ctx, task_event); if (task_event || (task_epc && cpu_event)) - __pmu_ctx_sched_in(task_epc->ctx, pmu); + __pmu_ctx_sched_in(task_epc, EVENT_FLEXIBLE); perf_pmu_enable(pmu); perf_ctx_unlock(cpuctx, cpuctx->task_ctx); @@ -4400,7 +4412,7 @@ static void perf_event_enable_on_exec(struct perf_event_context *ctx) cpuctx = this_cpu_ptr(&perf_cpu_context); perf_ctx_lock(cpuctx, ctx); - ctx_sched_out(ctx, EVENT_TIME); + ctx_sched_out(ctx, NULL, EVENT_TIME); list_for_each_entry(event, &ctx->event_list, event_entry) { enabled |= event_enable_on_exec(event, ctx); @@ -4412,9 +4424,9 @@ static void perf_event_enable_on_exec(struct perf_event_context *ctx) */ if (enabled) { clone_ctx = unclone_ctx(ctx); - ctx_resched(cpuctx, ctx, event_type); + ctx_resched(cpuctx, ctx, NULL, event_type); } else { - ctx_sched_in(ctx, EVENT_TIME); + ctx_sched_in(ctx, NULL, EVENT_TIME); } perf_ctx_unlock(cpuctx, ctx); @@ -13202,7 +13214,7 @@ static void perf_event_exit_task_context(struct task_struct *child) * in. */ raw_spin_lock_irq(&child_ctx->lock); - task_ctx_sched_out(child_ctx, EVENT_ALL); + task_ctx_sched_out(child_ctx, NULL, EVENT_ALL); /* * Now that the context is inactive, destroy the task <-> ctx relation @@ -13751,7 +13763,7 @@ static void __perf_event_exit_context(void *__info) struct perf_event *event; raw_spin_lock(&ctx->lock); - ctx_sched_out(ctx, EVENT_TIME); + ctx_sched_out(ctx, NULL, EVENT_TIME); list_for_each_entry(event, &ctx->event_list, event_entry) __perf_remove_from_context(event, cpuctx, ctx, (void *)DETACH_GROUP); raw_spin_unlock(&ctx->lock); From 9a32bd9901fe5b1dcf544389dbf04f3b0a2fbab4 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Wed, 7 Aug 2024 13:29:26 +0200 Subject: [PATCH 086/573] perf: Extract a few helpers The context time update code is repeated verbatim a few times. Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Kan Liang Reviewed-by: Namhyung Kim Link: https://lore.kernel.org/r/20240807115550.031212518@infradead.org --- kernel/events/core.c | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index dad2b9ac42c0..eb03c9ab1670 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -2330,6 +2330,24 @@ group_sched_out(struct perf_event *group_event, struct perf_event_context *ctx) event_sched_out(event, ctx); } +static inline void +ctx_time_update(struct perf_cpu_context *cpuctx, struct perf_event_context *ctx) +{ + if (ctx->is_active & EVENT_TIME) { + update_context_time(ctx); + update_cgrp_time_from_cpuctx(cpuctx, false); + } +} + +static inline void +ctx_time_update_event(struct perf_event_context *ctx, struct perf_event *event) +{ + if (ctx->is_active & EVENT_TIME) { + update_context_time(ctx); + update_cgrp_time_from_event(event); + } +} + #define DETACH_GROUP 0x01UL #define DETACH_CHILD 0x02UL #define DETACH_DEAD 0x04UL @@ -2349,10 +2367,7 @@ __perf_remove_from_context(struct perf_event *event, struct perf_event_pmu_context *pmu_ctx = event->pmu_ctx; unsigned long flags = (unsigned long)info; - if (ctx->is_active & EVENT_TIME) { - update_context_time(ctx); - update_cgrp_time_from_cpuctx(cpuctx, false); - } + ctx_time_update(cpuctx, ctx); /* * Ensure event_sched_out() switches to OFF, at the very least @@ -2437,12 +2452,8 @@ static void __perf_event_disable(struct perf_event *event, if (event->state < PERF_EVENT_STATE_INACTIVE) return; - if (ctx->is_active & EVENT_TIME) { - update_context_time(ctx); - update_cgrp_time_from_event(event); - } - perf_pmu_disable(event->pmu_ctx->pmu); + ctx_time_update_event(ctx, event); if (event == event->group_leader) group_sched_out(event, ctx); @@ -4529,10 +4540,7 @@ static void __perf_event_read(void *info) return; raw_spin_lock(&ctx->lock); - if (ctx->is_active & EVENT_TIME) { - update_context_time(ctx); - update_cgrp_time_from_event(event); - } + ctx_time_update_event(ctx, event); perf_event_update_time(event); if (data->group) @@ -4732,10 +4740,7 @@ again: * May read while context is not active (e.g., thread is * blocked), in that case we cannot update context time */ - if (ctx->is_active & EVENT_TIME) { - update_context_time(ctx); - update_cgrp_time_from_event(event); - } + ctx_time_update_event(ctx, event); perf_event_update_time(event); if (group) From 558abc7e3f895049faa46b08656be4c60dc6e9fd Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Wed, 7 Aug 2024 13:29:27 +0200 Subject: [PATCH 087/573] perf: Fix event_function_call() locking All the event_function/@func call context already uses perf_ctx_lock() except for the !ctx->is_active case. Make it all consistent. Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Kan Liang Reviewed-by: Namhyung Kim Link: https://lore.kernel.org/r/20240807115550.138301094@infradead.org --- kernel/events/core.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index eb03c9ab1670..ab49deae6e6f 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -263,6 +263,7 @@ unlock: static void event_function_call(struct perf_event *event, event_f func, void *data) { struct perf_event_context *ctx = event->ctx; + struct perf_cpu_context *cpuctx = this_cpu_ptr(&perf_cpu_context); struct task_struct *task = READ_ONCE(ctx->task); /* verified in event_function */ struct event_function_struct efs = { .event = event, @@ -291,22 +292,22 @@ again: if (!task_function_call(task, event_function, &efs)) return; - raw_spin_lock_irq(&ctx->lock); + perf_ctx_lock(cpuctx, ctx); /* * Reload the task pointer, it might have been changed by * a concurrent perf_event_context_sched_out(). */ task = ctx->task; if (task == TASK_TOMBSTONE) { - raw_spin_unlock_irq(&ctx->lock); + perf_ctx_unlock(cpuctx, ctx); return; } if (ctx->is_active) { - raw_spin_unlock_irq(&ctx->lock); + perf_ctx_unlock(cpuctx, ctx); goto again; } func(event, NULL, ctx, data); - raw_spin_unlock_irq(&ctx->lock); + perf_ctx_unlock(cpuctx, ctx); } /* From 5d95a2af973d47260b1e1828953fc860c0094052 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Wed, 7 Aug 2024 13:29:28 +0200 Subject: [PATCH 088/573] perf: Add context time freeze Many of the the context reschedule users are of the form: ctx_sched_out(.type = EVENT_TIME); ... modify context ctx_resched(); With the idea that the whole reschedule happens with a single time-stamp, rather than with each ctx_sched_out() advancing time and ctx_sched_in() re-starting time, creating a non-atomic experience. However, Kan noticed that since this completely stops time, it actually looses a bit of time between the stop and start. Worse, now that we can do partial (per PMU) reschedules, the PMUs that are not scheduled out still observe the time glitch. Replace this with: ctx_time_freeze(); ... modify context ctx_resched(); With the assumption that this happens in a perf_ctx_lock() / perf_ctx_unlock() pair. The new ctx_time_freeze() will update time and sets EVENT_FROZEN, and ensures EVENT_TIME and EVENT_FROZEN remain set, this avoids perf_event_time_now() from observing a time wobble from not seeing EVENT_TIME for a little while. Additionally, this avoids loosing time between ctx_sched_out(EVENT_TIME) and ctx_sched_in(), which would re-set the timestamp. Reported-by: Kan Liang Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Kan Liang Reviewed-by: Namhyung Kim Link: https://lore.kernel.org/r/20240807115550.250637571@infradead.org --- kernel/events/core.c | 130 +++++++++++++++++++++++++++++-------------- 1 file changed, 87 insertions(+), 43 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index ab49deae6e6f..197d3be443bb 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -155,20 +155,55 @@ static int cpu_function_call(int cpu, remote_function_f func, void *info) return data.ret; } +enum event_type_t { + EVENT_FLEXIBLE = 0x01, + EVENT_PINNED = 0x02, + EVENT_TIME = 0x04, + EVENT_FROZEN = 0x08, + /* see ctx_resched() for details */ + EVENT_CPU = 0x10, + EVENT_CGROUP = 0x20, + + /* compound helpers */ + EVENT_ALL = EVENT_FLEXIBLE | EVENT_PINNED, + EVENT_TIME_FROZEN = EVENT_TIME | EVENT_FROZEN, +}; + +static inline void __perf_ctx_lock(struct perf_event_context *ctx) +{ + raw_spin_lock(&ctx->lock); + WARN_ON_ONCE(ctx->is_active & EVENT_FROZEN); +} + static void perf_ctx_lock(struct perf_cpu_context *cpuctx, struct perf_event_context *ctx) { - raw_spin_lock(&cpuctx->ctx.lock); + __perf_ctx_lock(&cpuctx->ctx); if (ctx) - raw_spin_lock(&ctx->lock); + __perf_ctx_lock(ctx); +} + +static inline void __perf_ctx_unlock(struct perf_event_context *ctx) +{ + /* + * If ctx_sched_in() didn't again set any ALL flags, clean up + * after ctx_sched_out() by clearing is_active. + */ + if (ctx->is_active & EVENT_FROZEN) { + if (!(ctx->is_active & EVENT_ALL)) + ctx->is_active = 0; + else + ctx->is_active &= ~EVENT_FROZEN; + } + raw_spin_unlock(&ctx->lock); } static void perf_ctx_unlock(struct perf_cpu_context *cpuctx, struct perf_event_context *ctx) { if (ctx) - raw_spin_unlock(&ctx->lock); - raw_spin_unlock(&cpuctx->ctx.lock); + __perf_ctx_unlock(ctx); + __perf_ctx_unlock(&cpuctx->ctx); } #define TASK_TOMBSTONE ((void *)-1L) @@ -370,16 +405,6 @@ unlock: (PERF_SAMPLE_BRANCH_KERNEL |\ PERF_SAMPLE_BRANCH_HV) -enum event_type_t { - EVENT_FLEXIBLE = 0x1, - EVENT_PINNED = 0x2, - EVENT_TIME = 0x4, - /* see ctx_resched() for details */ - EVENT_CPU = 0x8, - EVENT_CGROUP = 0x10, - EVENT_ALL = EVENT_FLEXIBLE | EVENT_PINNED, -}; - /* * perf_sched_events : >0 events exist */ @@ -2332,18 +2357,39 @@ group_sched_out(struct perf_event *group_event, struct perf_event_context *ctx) } static inline void -ctx_time_update(struct perf_cpu_context *cpuctx, struct perf_event_context *ctx) +__ctx_time_update(struct perf_cpu_context *cpuctx, struct perf_event_context *ctx, bool final) { if (ctx->is_active & EVENT_TIME) { + if (ctx->is_active & EVENT_FROZEN) + return; update_context_time(ctx); - update_cgrp_time_from_cpuctx(cpuctx, false); + update_cgrp_time_from_cpuctx(cpuctx, final); } } +static inline void +ctx_time_update(struct perf_cpu_context *cpuctx, struct perf_event_context *ctx) +{ + __ctx_time_update(cpuctx, ctx, false); +} + +/* + * To be used inside perf_ctx_lock() / perf_ctx_unlock(). Lasts until perf_ctx_unlock(). + */ +static inline void +ctx_time_freeze(struct perf_cpu_context *cpuctx, struct perf_event_context *ctx) +{ + ctx_time_update(cpuctx, ctx); + if (ctx->is_active & EVENT_TIME) + ctx->is_active |= EVENT_FROZEN; +} + static inline void ctx_time_update_event(struct perf_event_context *ctx, struct perf_event *event) { if (ctx->is_active & EVENT_TIME) { + if (ctx->is_active & EVENT_FROZEN) + return; update_context_time(ctx); update_cgrp_time_from_event(event); } @@ -2822,7 +2868,7 @@ static int __perf_install_in_context(void *info) #endif if (reprogram) { - ctx_sched_out(ctx, NULL, EVENT_TIME); + ctx_time_freeze(cpuctx, ctx); add_event_to_ctx(event, ctx); ctx_resched(cpuctx, task_ctx, event->pmu_ctx->pmu, get_event_type(event)); @@ -2968,8 +3014,7 @@ static void __perf_event_enable(struct perf_event *event, event->state <= PERF_EVENT_STATE_ERROR) return; - if (ctx->is_active) - ctx_sched_out(ctx, NULL, EVENT_TIME); + ctx_time_freeze(cpuctx, ctx); perf_event_set_state(event, PERF_EVENT_STATE_INACTIVE); perf_cgroup_event_enable(event, ctx); @@ -2977,19 +3022,15 @@ static void __perf_event_enable(struct perf_event *event, if (!ctx->is_active) return; - if (!event_filter_match(event)) { - ctx_sched_in(ctx, NULL, EVENT_TIME); + if (!event_filter_match(event)) return; - } /* * If the event is in a group and isn't the group leader, * then don't put it on unless the group is on. */ - if (leader != event && leader->state != PERF_EVENT_STATE_ACTIVE) { - ctx_sched_in(ctx, NULL, EVENT_TIME); + if (leader != event && leader->state != PERF_EVENT_STATE_ACTIVE) return; - } task_ctx = cpuctx->task_ctx; if (ctx->task) @@ -3263,7 +3304,7 @@ static void __pmu_ctx_sched_out(struct perf_event_pmu_context *pmu_ctx, struct perf_event *event, *tmp; struct pmu *pmu = pmu_ctx->pmu; - if (ctx->task && !ctx->is_active) { + if (ctx->task && !(ctx->is_active & EVENT_ALL)) { struct perf_cpu_pmu_context *cpc; cpc = this_cpu_ptr(pmu->cpu_pmu_context); @@ -3338,24 +3379,29 @@ ctx_sched_out(struct perf_event_context *ctx, struct pmu *pmu, enum event_type_t * * would only update time for the pinned events. */ - if (is_active & EVENT_TIME) { - /* update (and stop) ctx time */ - update_context_time(ctx); - update_cgrp_time_from_cpuctx(cpuctx, ctx == &cpuctx->ctx); - /* - * CPU-release for the below ->is_active store, - * see __load_acquire() in perf_event_time_now() - */ - barrier(); - } + __ctx_time_update(cpuctx, ctx, ctx == &cpuctx->ctx); + /* + * CPU-release for the below ->is_active store, + * see __load_acquire() in perf_event_time_now() + */ + barrier(); ctx->is_active &= ~event_type; - if (!(ctx->is_active & EVENT_ALL)) - ctx->is_active = 0; + + if (!(ctx->is_active & EVENT_ALL)) { + /* + * For FROZEN, preserve TIME|FROZEN such that perf_event_time_now() + * does not observe a hole. perf_ctx_unlock() will clean up. + */ + if (ctx->is_active & EVENT_FROZEN) + ctx->is_active &= EVENT_TIME_FROZEN; + else + ctx->is_active = 0; + } if (ctx->task) { WARN_ON_ONCE(cpuctx->task_ctx != ctx); - if (!ctx->is_active) + if (!(ctx->is_active & EVENT_ALL)) cpuctx->task_ctx = NULL; } @@ -3943,7 +3989,7 @@ ctx_sched_in(struct perf_event_context *ctx, struct pmu *pmu, enum event_type_t ctx->is_active |= (event_type | EVENT_TIME); if (ctx->task) { - if (!is_active) + if (!(is_active & EVENT_ALL)) cpuctx->task_ctx = ctx; else WARN_ON_ONCE(cpuctx->task_ctx != ctx); @@ -4424,7 +4470,7 @@ static void perf_event_enable_on_exec(struct perf_event_context *ctx) cpuctx = this_cpu_ptr(&perf_cpu_context); perf_ctx_lock(cpuctx, ctx); - ctx_sched_out(ctx, NULL, EVENT_TIME); + ctx_time_freeze(cpuctx, ctx); list_for_each_entry(event, &ctx->event_list, event_entry) { enabled |= event_enable_on_exec(event, ctx); @@ -4437,8 +4483,6 @@ static void perf_event_enable_on_exec(struct perf_event_context *ctx) if (enabled) { clone_ctx = unclone_ctx(ctx); ctx_resched(cpuctx, ctx, NULL, event_type); - } else { - ctx_sched_in(ctx, NULL, EVENT_TIME); } perf_ctx_unlock(cpuctx, ctx); From 3e15a3fe3a2a170c5be52783667706875c088f96 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Wed, 7 Aug 2024 13:29:29 +0200 Subject: [PATCH 089/573] perf: Optimize __pmu_ctx_sched_out() There is is no point in doing the perf_pmu_disable() dance just to do nothing. This happens for ctx_sched_out(.type = EVENT_TIME) for instance. Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Kan Liang Reviewed-by: Namhyung Kim Link: https://lore.kernel.org/r/20240807115550.392851915@infradead.org --- kernel/events/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index 197d3be443bb..9893ba5e98aa 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -3312,7 +3312,7 @@ static void __pmu_ctx_sched_out(struct perf_event_pmu_context *pmu_ctx, cpc->task_epc = NULL; } - if (!event_type) + if (!(event_type & EVENT_ALL)) return; perf_pmu_disable(pmu); From a7050ca724807a60e639da953a092cf8dc6d1bf7 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 7 Aug 2024 17:00:29 -0400 Subject: [PATCH 090/573] pstore/ramoops: Fix typo as there is no "reserver" For some reason my finger always hits the 'r' after typing "reserve". Fix the typo in the Documentation example. Fixes: d9d814eebb1ae ("pstore/ramoops: Add ramoops.mem_name= command line option") Signed-off-by: Steven Rostedt (Google) Acked-by: Mike Rapoport (Microsoft) Link: https://lore.kernel.org/r/20240807170029.3c1ff651@gandalf.local.home Signed-off-by: Kees Cook --- Documentation/admin-guide/ramoops.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/admin-guide/ramoops.rst b/Documentation/admin-guide/ramoops.rst index 6f534a707b2a..2eabef31220d 100644 --- a/Documentation/admin-guide/ramoops.rst +++ b/Documentation/admin-guide/ramoops.rst @@ -129,7 +129,7 @@ Setting the ramoops parameters can be done in several different manners: takes a size, alignment and name as arguments. The name is used to map the memory to a label that can be retrieved by ramoops. - reserver_mem=2M:4096:oops ramoops.mem_name=oops + reserve_mem=2M:4096:oops ramoops.mem_name=oops You can specify either RAM memory or peripheral devices' memory. However, when specifying RAM, be sure to reserve the memory by issuing memblock_reserve() From a1927fbbf74f9f61eb5c6d1414037c97a8d942ab Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Fri, 26 Jul 2024 15:12:35 -0300 Subject: [PATCH 091/573] platform/chrome: cros_ec_typec: add remove driver hook This allows the driver to be unbound and bound again. Otherwise, when unbinding the driver, there will be leftover sysfs entries. When rebinding the driver, it also ends up touching freed memory when adding to the notifier chain as the old one was not removed and ends up being traversed. Add a remove_new driver hook, which removes the notifier from the chain and unregisters the typec ports. Signed-off-by: Thadeu Lima de Souza Cascardo Link: https://lore.kernel.org/r/20240726181235.920335-1-cascardo@igalia.com Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/cros_ec_typec.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 4d305876ec08..c7781aea0b88 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -1285,6 +1285,15 @@ unregister_ports: return ret; } +static void cros_typec_remove(struct platform_device *pdev) +{ + struct cros_typec_data *typec = platform_get_drvdata(pdev); + + cros_usbpd_unregister_notify(&typec->nb); + cancel_work_sync(&typec->port_work); + cros_unregister_ports(typec); +} + static int __maybe_unused cros_typec_suspend(struct device *dev) { struct cros_typec_data *typec = dev_get_drvdata(dev); @@ -1316,6 +1325,7 @@ static struct platform_driver cros_typec_driver = { .pm = &cros_typec_pm_ops, }, .probe = cros_typec_probe, + .remove_new = cros_typec_remove, }; module_platform_driver(cros_typec_driver); From 789ce0f6028f9e68fc27f6748acefbd2e23f4716 Mon Sep 17 00:00:00 2001 From: Jared McArthur Date: Fri, 9 Aug 2024 10:46:38 -0500 Subject: [PATCH 092/573] dt-bindings: gpio: gpio-davinci: Add the gpio-reserved-ranges property Current definition of the davinci gpio controller doesn't include the gpio-reserved-ranges property. Add the gpio-reserved-ranges property so it can be used within device tree files. Will prevent users from trying to access gpios that don't exist. Signed-off-by: Jared McArthur Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240809154638.394091-2-j-mcarthur@ti.com Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/gpio-davinci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/gpio-davinci.yaml b/Documentation/devicetree/bindings/gpio/gpio-davinci.yaml index 10e56cf306db..1434d08f8b74 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-davinci.yaml +++ b/Documentation/devicetree/bindings/gpio/gpio-davinci.yaml @@ -32,6 +32,8 @@ properties: gpio-ranges: true + gpio-reserved-ranges: true + gpio-line-names: description: strings describing the names of each gpio line. minItems: 1 From 7d442a33bfe817ab2a735f3d2e430e36305354ea Mon Sep 17 00:00:00 2001 From: Brian Mak Date: Tue, 6 Aug 2024 18:16:02 +0000 Subject: [PATCH 093/573] binfmt_elf: Dump smaller VMAs first in ELF cores Large cores may be truncated in some scenarios, such as with daemons with stop timeouts that are not large enough or lack of disk space. This impacts debuggability with large core dumps since critical information necessary to form a usable backtrace, such as stacks and shared library information, are omitted. We attempted to figure out which VMAs are needed to create a useful backtrace, and it turned out to be a non-trivial problem. Instead, we try simply sorting the VMAs by size, which has the intended effect. By sorting VMAs by dump size and dumping in that order, we have a simple, yet effective heuristic. Signed-off-by: Brian Mak Link: https://lore.kernel.org/r/036CD6AE-C560-4FC7-9B02-ADD08E380DC9@juniper.net Acked-by: "Eric W. Biederman" Signed-off-by: Kees Cook --- fs/coredump.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/fs/coredump.c b/fs/coredump.c index 5814a6d781ce..53a78b6bbb5b 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -1249,6 +1250,18 @@ static void free_vma_snapshot(struct coredump_params *cprm) } } +static int cmp_vma_size(const void *vma_meta_lhs_ptr, const void *vma_meta_rhs_ptr) +{ + const struct core_vma_metadata *vma_meta_lhs = vma_meta_lhs_ptr; + const struct core_vma_metadata *vma_meta_rhs = vma_meta_rhs_ptr; + + if (vma_meta_lhs->dump_size < vma_meta_rhs->dump_size) + return -1; + if (vma_meta_lhs->dump_size > vma_meta_rhs->dump_size) + return 1; + return 0; +} + /* * Under the mmap_lock, take a snapshot of relevant information about the task's * VMAs. @@ -1311,5 +1324,8 @@ static bool dump_vma_snapshot(struct coredump_params *cprm) cprm->vma_data_size += m->dump_size; } + sort(cprm->vma_meta, cprm->vma_count, sizeof(*cprm->vma_meta), + cmp_vma_size, NULL); + return true; } From e5e5cc8f73fa677b5b96404b9595d653a9ee0805 Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Mon, 22 Jul 2024 11:54:52 +0000 Subject: [PATCH 094/573] iommu/amd: Add blocked domain support Create global blocked domain with attach device ops. It will clear the DTE so that all DMA from device will be aborted. Signed-off-by: Vasant Hegde Reviewed-by: Suravee Suthikulpanit Reviewed-by: Jason Gunthorpe Link: https://lore.kernel.org/r/20240722115452.5976-1-vasant.hegde@amd.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/iommu.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index b19e8c0f48fa..87c5385ce3f2 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -2462,6 +2462,29 @@ void amd_iommu_domain_free(struct iommu_domain *dom) protection_domain_free(domain); } +static int blocked_domain_attach_device(struct iommu_domain *domain, + struct device *dev) +{ + struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev); + + if (dev_data->domain) + detach_device(dev); + + /* Clear DTE and flush the entry */ + spin_lock(&dev_data->lock); + amd_iommu_dev_update_dte(dev_data, false); + spin_unlock(&dev_data->lock); + + return 0; +} + +static struct iommu_domain blocked_domain = { + .type = IOMMU_DOMAIN_BLOCKED, + .ops = &(const struct iommu_domain_ops) { + .attach_dev = blocked_domain_attach_device, + } +}; + static int amd_iommu_attach_device(struct iommu_domain *dom, struct device *dev) { @@ -2859,6 +2882,7 @@ static int amd_iommu_dev_disable_feature(struct device *dev, const struct iommu_ops amd_iommu_ops = { .capable = amd_iommu_capable, + .blocked_domain = &blocked_domain, .domain_alloc = amd_iommu_domain_alloc, .domain_alloc_user = amd_iommu_domain_alloc_user, .domain_alloc_sva = amd_iommu_domain_alloc_sva, From fe826cc2654e8561b64246325e6a51b62bf2488c Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Tue, 13 Aug 2024 22:55:11 +0200 Subject: [PATCH 095/573] perf: Really fix event_function_call() locking Commit 558abc7e3f89 ("perf: Fix event_function_call() locking") lost IRQ disabling by mistake. Fixes: 558abc7e3f89 ("perf: Fix event_function_call() locking") Reported-by: Pengfei Xu Reported-by: Naresh Kamboju Tested-by: Pengfei Xu Signed-off-by: Namhyung Kim Signed-off-by: Peter Zijlstra (Intel) --- kernel/events/core.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index 9893ba5e98aa..c6a720f41225 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -298,8 +298,8 @@ unlock: static void event_function_call(struct perf_event *event, event_f func, void *data) { struct perf_event_context *ctx = event->ctx; - struct perf_cpu_context *cpuctx = this_cpu_ptr(&perf_cpu_context); struct task_struct *task = READ_ONCE(ctx->task); /* verified in event_function */ + struct perf_cpu_context *cpuctx; struct event_function_struct efs = { .event = event, .func = func, @@ -327,22 +327,25 @@ again: if (!task_function_call(task, event_function, &efs)) return; + local_irq_disable(); + cpuctx = this_cpu_ptr(&perf_cpu_context); perf_ctx_lock(cpuctx, ctx); /* * Reload the task pointer, it might have been changed by * a concurrent perf_event_context_sched_out(). */ task = ctx->task; - if (task == TASK_TOMBSTONE) { - perf_ctx_unlock(cpuctx, ctx); - return; - } + if (task == TASK_TOMBSTONE) + goto unlock; if (ctx->is_active) { perf_ctx_unlock(cpuctx, ctx); + local_irq_enable(); goto again; } func(event, NULL, ctx, data); +unlock: perf_ctx_unlock(cpuctx, ctx); + local_irq_enable(); } /* From 49d2a1ec68eebc436851ccc64135661bad5fc27d Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Sun, 28 Jul 2024 13:41:47 +0200 Subject: [PATCH 096/573] pmdomain: raspberrypi-power: Adjust packet definition According to the official Mailbox property interface the second part of RPI_FIRMWARE_SET_POWER_STATE ( and so on ...) is named state because it represent u32 flags and just the lowest bit is for on/off. So rename it to align with documentation and prepare the driver for further changes. Link: https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface Signed-off-by: Stefan Wahren Reviewed-by: Florian Fainelli Link: https://lore.kernel.org/r/20240728114200.75559-4-wahrenst@gmx.net Signed-off-by: Ulf Hansson --- drivers/pmdomain/bcm/raspberrypi-power.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/pmdomain/bcm/raspberrypi-power.c b/drivers/pmdomain/bcm/raspberrypi-power.c index 06196ebfe03b..39d9a52200c3 100644 --- a/drivers/pmdomain/bcm/raspberrypi-power.c +++ b/drivers/pmdomain/bcm/raspberrypi-power.c @@ -41,7 +41,7 @@ struct rpi_power_domains { */ struct rpi_power_domain_packet { u32 domain; - u32 on; + u32 state; }; /* @@ -53,7 +53,7 @@ static int rpi_firmware_set_power(struct rpi_power_domain *rpi_domain, bool on) struct rpi_power_domain_packet packet; packet.domain = rpi_domain->domain; - packet.on = on; + packet.state = on; return rpi_firmware_property(rpi_domain->fw, rpi_domain->old_interface ? RPI_FIRMWARE_SET_POWER_STATE : @@ -142,13 +142,13 @@ rpi_has_new_domain_support(struct rpi_power_domains *rpi_domains) int ret; packet.domain = RPI_POWER_DOMAIN_ARM; - packet.on = ~0; + packet.state = ~0; ret = rpi_firmware_property(rpi_domains->fw, RPI_FIRMWARE_GET_DOMAIN_STATE, &packet, sizeof(packet)); - return ret == 0 && packet.on != ~0; + return ret == 0 && packet.state != ~0; } static int rpi_power_probe(struct platform_device *pdev) From eb3896ea9e2829c8c3aa2e86d3f90765b06a41c6 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Sun, 28 Jul 2024 13:41:48 +0200 Subject: [PATCH 097/573] pmdomain: raspberrypi-power: Add logging to rpi_firmware_set_power The Raspberry Pi power driver heavily relies on the logging of the underlying firmware driver. This results in disadvantages like unspecific error messages or limited debugging options. So implement the logging for the most important function rpi_firmware_set_power. Signed-off-by: Stefan Wahren Link: https://lore.kernel.org/r/20240728114200.75559-5-wahrenst@gmx.net Signed-off-by: Ulf Hansson --- drivers/pmdomain/bcm/raspberrypi-power.c | 34 ++++++++++++++---------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/drivers/pmdomain/bcm/raspberrypi-power.c b/drivers/pmdomain/bcm/raspberrypi-power.c index 39d9a52200c3..fadedfc9c645 100644 --- a/drivers/pmdomain/bcm/raspberrypi-power.c +++ b/drivers/pmdomain/bcm/raspberrypi-power.c @@ -48,33 +48,39 @@ struct rpi_power_domain_packet { * Asks the firmware to enable or disable power on a specific power * domain. */ -static int rpi_firmware_set_power(struct rpi_power_domain *rpi_domain, bool on) +static int rpi_firmware_set_power(struct generic_pm_domain *domain, bool on) { + struct rpi_power_domain *rpi_domain = + container_of(domain, struct rpi_power_domain, base); + bool old_interface = rpi_domain->old_interface; struct rpi_power_domain_packet packet; + int ret; packet.domain = rpi_domain->domain; packet.state = on; - return rpi_firmware_property(rpi_domain->fw, - rpi_domain->old_interface ? - RPI_FIRMWARE_SET_POWER_STATE : - RPI_FIRMWARE_SET_DOMAIN_STATE, - &packet, sizeof(packet)); + + ret = rpi_firmware_property(rpi_domain->fw, old_interface ? + RPI_FIRMWARE_SET_POWER_STATE : + RPI_FIRMWARE_SET_DOMAIN_STATE, + &packet, sizeof(packet)); + if (ret) + dev_err(&domain->dev, "Failed to set %s to %u (%d)\n", + old_interface ? "power" : "domain", on, ret); + else + dev_dbg(&domain->dev, "Set %s to %u\n", + old_interface ? "power" : "domain", on); + + return ret; } static int rpi_domain_off(struct generic_pm_domain *domain) { - struct rpi_power_domain *rpi_domain = - container_of(domain, struct rpi_power_domain, base); - - return rpi_firmware_set_power(rpi_domain, false); + return rpi_firmware_set_power(domain, false); } static int rpi_domain_on(struct generic_pm_domain *domain) { - struct rpi_power_domain *rpi_domain = - container_of(domain, struct rpi_power_domain, base); - - return rpi_firmware_set_power(rpi_domain, true); + return rpi_firmware_set_power(domain, true); } static void rpi_common_init_power_domain(struct rpi_power_domains *rpi_domains, From 562cdeadac06176b3a99bd457d9463a3b58df60f Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Sun, 28 Jul 2024 13:41:49 +0200 Subject: [PATCH 098/573] pmdomain: raspberrypi-power: set flag GENPD_FLAG_ACTIVE_WAKEUP Set flag GENPD_FLAG_ACTIVE_WAKEUP to rpi_power genpd, then when a device is set as wakeup source using device_set_wakeup_enable, the power domain could be kept on to make sure the device could wakeup the system. Signed-off-by: Stefan Wahren Link: https://lore.kernel.org/r/20240728114200.75559-6-wahrenst@gmx.net Signed-off-by: Ulf Hansson --- drivers/pmdomain/bcm/raspberrypi-power.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pmdomain/bcm/raspberrypi-power.c b/drivers/pmdomain/bcm/raspberrypi-power.c index fadedfc9c645..b87ea7adb7be 100644 --- a/drivers/pmdomain/bcm/raspberrypi-power.c +++ b/drivers/pmdomain/bcm/raspberrypi-power.c @@ -91,6 +91,7 @@ static void rpi_common_init_power_domain(struct rpi_power_domains *rpi_domains, dom->fw = rpi_domains->fw; dom->base.name = name; + dom->base.flags = GENPD_FLAG_ACTIVE_WAKEUP; dom->base.power_on = rpi_domain_on; dom->base.power_off = rpi_domain_off; From 44f65d900698278a8451988abe0d5ca37fd46882 Mon Sep 17 00:00:00 2001 From: Jeff Xu Date: Tue, 6 Aug 2024 21:49:27 +0000 Subject: [PATCH 099/573] binfmt_elf: mseal address zero MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In load_elf_binary as part of the execve(), when the current task’s personality has MMAP_PAGE_ZERO set, the kernel allocates one page at address 0. According to the comment: /* Why this, you ask??? Well SVr4 maps page 0 as read-only, and some applications "depend" upon this behavior. Since we do not have the power to recompile these, we emulate the SVr4 behavior. Sigh. */ At one point, Linus suggested removing this [1]. Code search in debian didn't see much use of MMAP_PAGE_ZERO [2], it exists in util and test (rr). Sealing this is probably safe, the comment doesn't say the app ever wanting to change the mapping to rwx. Sealing also ensures that never happens. If there is a complaint, we can make this configurable. Link: https://lore.kernel.org/lkml/CAHk-=whVa=nm_GW=NVfPHqcxDbWt4JjjK1YWb0cLjO4ZSGyiDA@mail.gmail.com/ [1] Link: https://codesearch.debian.net/search?q=MMAP_PAGE_ZERO&literal=1&perpkg=1&page=1 [2] Signed-off-by: Jeff Xu Link: https://lore.kernel.org/r/20240806214931.2198172-2-jeffxu@google.com Signed-off-by: Kees Cook --- fs/binfmt_elf.c | 5 +++++ include/linux/mm.h | 10 ++++++++++ mm/mseal.c | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index bf9bfd1a0007..c3aa700aeb3c 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -1314,6 +1314,11 @@ out_free_interp: emulate the SVr4 behavior. Sigh. */ error = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC, MAP_FIXED | MAP_PRIVATE, 0); + + retval = do_mseal(0, PAGE_SIZE, 0); + if (retval) + pr_warn_ratelimited("pid=%d, couldn't seal address 0, ret=%d.\n", + task_pid_nr(current), retval); } regs = current_pt_regs(); diff --git a/include/linux/mm.h b/include/linux/mm.h index c4b238a20b76..a178c15812eb 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -4201,4 +4201,14 @@ void vma_pgtable_walk_end(struct vm_area_struct *vma); int reserve_mem_find_by_name(const char *name, phys_addr_t *start, phys_addr_t *size); +#ifdef CONFIG_64BIT +int do_mseal(unsigned long start, size_t len_in, unsigned long flags); +#else +static inline int do_mseal(unsigned long start, size_t len_in, unsigned long flags) +{ + /* noop on 32 bit */ + return 0; +} +#endif + #endif /* _LINUX_MM_H */ diff --git a/mm/mseal.c b/mm/mseal.c index bf783bba8ed0..7a40a84569c8 100644 --- a/mm/mseal.c +++ b/mm/mseal.c @@ -248,7 +248,7 @@ static int apply_mm_seal(unsigned long start, unsigned long end) * * unseal() is not supported. */ -static int do_mseal(unsigned long start, size_t len_in, unsigned long flags) +int do_mseal(unsigned long start, size_t len_in, unsigned long flags) { size_t len; int ret = 0; From 4e9903b0861c9df3464b82db4a7025863bac1897 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Sun, 28 Jul 2024 00:02:36 +0900 Subject: [PATCH 100/573] fortify: refactor test_fortify Makefile to fix some build problems There are some issues in the test_fortify Makefile code. Problem 1: cc-disable-warning invokes compiler dozens of times To see how many times the cc-disable-warning is evaluated, change this code: $(call cc-disable-warning,fortify-source) to: $(call cc-disable-warning,$(shell touch /tmp/fortify-$$$$)fortify-source) Then, build the kernel with CONFIG_FORTIFY_SOURCE=y. You will see a large number of '/tmp/fortify-' files created: $ ls -1 /tmp/fortify-* | wc 80 80 1600 This means the compiler was invoked 80 times just for checking the -Wno-fortify-source flag support. $(call cc-disable-warning,fortify-source) should be added to a simple variable instead of a recursive variable. Problem 2: do not recompile string.o when the test code is updated The test cases are independent of the kernel. However, when the test code is updated, $(obj)/string.o is rebuilt and vmlinux is relinked due to this dependency: $(obj)/string.o: $(obj)/$(TEST_FORTIFY_LOG) always-y is suitable for building the log files. Problem 3: redundant code clean-files += $(addsuffix .o, $(TEST_FORTIFY_LOGS)) ... is unneeded because the top Makefile globally cleans *.o files. This commit fixes these issues and makes the code readable. Signed-off-by: Masahiro Yamada Link: https://lore.kernel.org/r/20240727150302.1823750-2-masahiroy@kernel.org Signed-off-by: Kees Cook --- lib/.gitignore | 2 -- lib/Makefile | 38 +------------------------------------ lib/test_fortify/.gitignore | 2 ++ lib/test_fortify/Makefile | 28 +++++++++++++++++++++++++++ scripts/remove-stale-files | 2 ++ 5 files changed, 33 insertions(+), 39 deletions(-) create mode 100644 lib/test_fortify/.gitignore create mode 100644 lib/test_fortify/Makefile diff --git a/lib/.gitignore b/lib/.gitignore index 54596b634ecb..101a4aa92fb5 100644 --- a/lib/.gitignore +++ b/lib/.gitignore @@ -5,5 +5,3 @@ /gen_crc32table /gen_crc64table /oid_registry_data.c -/test_fortify.log -/test_fortify/*.log diff --git a/lib/Makefile b/lib/Makefile index 322bb127b4dc..4df3c28b23b4 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -393,40 +393,4 @@ obj-$(CONFIG_GENERIC_LIB_DEVMEM_IS_ALLOWED) += devmem_is_allowed.o obj-$(CONFIG_FIRMWARE_TABLE) += fw_table.o -# FORTIFY_SOURCE compile-time behavior tests -TEST_FORTIFY_SRCS = $(wildcard $(src)/test_fortify/*-*.c) -TEST_FORTIFY_LOGS = $(patsubst $(src)/%.c, %.log, $(TEST_FORTIFY_SRCS)) -TEST_FORTIFY_LOG = test_fortify.log - -quiet_cmd_test_fortify = TEST $@ - cmd_test_fortify = $(CONFIG_SHELL) $(srctree)/scripts/test_fortify.sh \ - $< $@ "$(NM)" $(CC) $(c_flags) \ - $(call cc-disable-warning,fortify-source) \ - -DKBUILD_EXTRA_WARN1 - -targets += $(TEST_FORTIFY_LOGS) -clean-files += $(TEST_FORTIFY_LOGS) -clean-files += $(addsuffix .o, $(TEST_FORTIFY_LOGS)) -$(obj)/test_fortify/%.log: $(src)/test_fortify/%.c \ - $(src)/test_fortify/test_fortify.h \ - $(srctree)/include/linux/fortify-string.h \ - $(srctree)/scripts/test_fortify.sh \ - FORCE - $(call if_changed,test_fortify) - -quiet_cmd_gen_fortify_log = GEN $@ - cmd_gen_fortify_log = cat /dev/null > $@ || true - -targets += $(TEST_FORTIFY_LOG) -clean-files += $(TEST_FORTIFY_LOG) -$(obj)/$(TEST_FORTIFY_LOG): $(addprefix $(obj)/, $(TEST_FORTIFY_LOGS)) FORCE - $(call if_changed,gen_fortify_log) - -# Fake dependency to trigger the fortify tests. -ifeq ($(CONFIG_FORTIFY_SOURCE),y) -$(obj)/string.o: $(obj)/$(TEST_FORTIFY_LOG) -endif - -# Some architectures define __NO_FORTIFY if __SANITIZE_ADDRESS__ is undefined. -# Pass CFLAGS_KASAN to avoid warnings. -$(foreach x, $(patsubst %.log,%.o,$(TEST_FORTIFY_LOGS)), $(eval KASAN_SANITIZE_$(x) := y)) +subdir-$(CONFIG_FORTIFY_SOURCE) += test_fortify diff --git a/lib/test_fortify/.gitignore b/lib/test_fortify/.gitignore new file mode 100644 index 000000000000..c1ba37d14b50 --- /dev/null +++ b/lib/test_fortify/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +/*.log diff --git a/lib/test_fortify/Makefile b/lib/test_fortify/Makefile new file mode 100644 index 000000000000..3907a2242ef9 --- /dev/null +++ b/lib/test_fortify/Makefile @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 + +ccflags-y := $(call cc-disable-warning,fortify-source) + +quiet_cmd_test_fortify = TEST $@ + cmd_test_fortify = $(CONFIG_SHELL) $(srctree)/scripts/test_fortify.sh \ + $< $@ "$(NM)" $(CC) $(c_flags) -DKBUILD_EXTRA_WARN1 + +$(obj)/%.log: $(src)/%.c $(srctree)/scripts/test_fortify.sh \ + $(src)/test_fortify.h \ + $(srctree)/include/linux/fortify-string.h \ + FORCE + $(call if_changed,test_fortify) + +logs = $(patsubst $(src)/%.c, %.log, $(wildcard $(src)/*-*.c)) +targets += $(logs) + +quiet_cmd_gen_fortify_log = CAT $@ + cmd_gen_fortify_log = cat $(or $(real-prereqs),/dev/null) > $@ + +$(obj)/test_fortify.log: $(addprefix $(obj)/, $(logs)) FORCE + $(call if_changed,gen_fortify_log) + +always-y += test_fortify.log + +# Some architectures define __NO_FORTIFY if __SANITIZE_ADDRESS__ is undefined. +# Pass CFLAGS_KASAN to avoid warnings. +KASAN_SANITIZE := y diff --git a/scripts/remove-stale-files b/scripts/remove-stale-files index f38d26b78c2a..8fc55a749ccc 100755 --- a/scripts/remove-stale-files +++ b/scripts/remove-stale-files @@ -21,3 +21,5 @@ set -e # then will be really dead and removed from the code base entirely. rm -f *.spec + +rm -f lib/test_fortify.log From 5a8d0c46c9e024bed4805a9335fe6124d8a78d3a Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Sun, 28 Jul 2024 00:02:37 +0900 Subject: [PATCH 101/573] fortify: move test_fortify.sh to lib/test_fortify/ This script is only used in lib/test_fortify/. There is no reason to keep it in scripts/. Signed-off-by: Masahiro Yamada Link: https://lore.kernel.org/r/20240727150302.1823750-3-masahiroy@kernel.org Signed-off-by: Kees Cook --- MAINTAINERS | 1 - lib/test_fortify/Makefile | 4 ++-- {scripts => lib/test_fortify}/test_fortify.sh | 0 3 files changed, 2 insertions(+), 3 deletions(-) rename {scripts => lib/test_fortify}/test_fortify.sh (100%) diff --git a/MAINTAINERS b/MAINTAINERS index 8766f3e5e87e..36c0af94cf08 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8772,7 +8772,6 @@ F: include/linux/fortify-string.h F: lib/fortify_kunit.c F: lib/memcpy_kunit.c F: lib/test_fortify/* -F: scripts/test_fortify.sh K: \b__NO_FORTIFY\b FPGA DFL DRIVERS diff --git a/lib/test_fortify/Makefile b/lib/test_fortify/Makefile index 3907a2242ef9..1826172c32d4 100644 --- a/lib/test_fortify/Makefile +++ b/lib/test_fortify/Makefile @@ -3,10 +3,10 @@ ccflags-y := $(call cc-disable-warning,fortify-source) quiet_cmd_test_fortify = TEST $@ - cmd_test_fortify = $(CONFIG_SHELL) $(srctree)/scripts/test_fortify.sh \ + cmd_test_fortify = $(CONFIG_SHELL) $(src)/test_fortify.sh \ $< $@ "$(NM)" $(CC) $(c_flags) -DKBUILD_EXTRA_WARN1 -$(obj)/%.log: $(src)/%.c $(srctree)/scripts/test_fortify.sh \ +$(obj)/%.log: $(src)/%.c $(src)/test_fortify.sh \ $(src)/test_fortify.h \ $(srctree)/include/linux/fortify-string.h \ FORCE diff --git a/scripts/test_fortify.sh b/lib/test_fortify/test_fortify.sh similarity index 100% rename from scripts/test_fortify.sh rename to lib/test_fortify/test_fortify.sh From 9c6b7fbbd7a2c2772a592adf9b2835371927a1d3 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Sun, 28 Jul 2024 00:02:38 +0900 Subject: [PATCH 102/573] fortify: use if_changed_dep to record header dependency in *.cmd files After building with CONFIG_FORTIFY_SOURCE=y, many .*.d files are left in lib/test_fortify/ because the compiler outputs header dependencies into *.d without fixdep being invoked. When compiling C files, if_changed_dep should be used so that the auto-generated header dependencies are recorded in .*.cmd files. Currently, if_changed is incorrectly used, and only two headers are hard-coded in lib/Makefile. In the previous patch version, the kbuild test robot detected new errors on GCC 7. GCC 7 or older does not produce test.d with the following test code: $ echo 'void b(void) __attribute__((__error__(""))); void a(void) { b(); }' | gcc -Wp,-MMD,test.d -c -o /dev/null -x c - Perhaps, this was a bug that existed in older GCC versions. Skip the tests for GCC<=7 for now, as this will be eventually solved when we bump the minimal supported GCC version. Link: https://lore.kernel.org/oe-kbuild-all/CAK7LNARmJcyyzL-jVJfBPi3W684LTDmuhMf1koF0TXoCpKTmcw@mail.gmail.com/T/#m13771bf78ae21adff22efc4d310c973fb4bcaf67 Signed-off-by: Masahiro Yamada Link: https://lore.kernel.org/r/20240727150302.1823750-4-masahiroy@kernel.org Signed-off-by: Kees Cook --- lib/test_fortify/Makefile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/test_fortify/Makefile b/lib/test_fortify/Makefile index 1826172c32d4..1c3f82ad8bb2 100644 --- a/lib/test_fortify/Makefile +++ b/lib/test_fortify/Makefile @@ -6,11 +6,8 @@ quiet_cmd_test_fortify = TEST $@ cmd_test_fortify = $(CONFIG_SHELL) $(src)/test_fortify.sh \ $< $@ "$(NM)" $(CC) $(c_flags) -DKBUILD_EXTRA_WARN1 -$(obj)/%.log: $(src)/%.c $(src)/test_fortify.sh \ - $(src)/test_fortify.h \ - $(srctree)/include/linux/fortify-string.h \ - FORCE - $(call if_changed,test_fortify) +$(obj)/%.log: $(src)/%.c $(src)/test_fortify.sh FORCE + $(call if_changed_dep,test_fortify) logs = $(patsubst $(src)/%.c, %.log, $(wildcard $(src)/*-*.c)) targets += $(logs) @@ -21,7 +18,10 @@ quiet_cmd_gen_fortify_log = CAT $@ $(obj)/test_fortify.log: $(addprefix $(obj)/, $(logs)) FORCE $(call if_changed,gen_fortify_log) -always-y += test_fortify.log +# GCC<=7 does not always produce *.d files. +# Run the tests only for GCC>=8 or Clang. +always-$(call gcc-min-version, 80000) += test_fortify.log +always-$(CONFIG_CC_IS_CLANG) += test_fortify.log # Some architectures define __NO_FORTIFY if __SANITIZE_ADDRESS__ is undefined. # Pass CFLAGS_KASAN to avoid warnings. From a98ae7f045b29de4f48b191d5aeb4e803183d759 Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Thu, 25 Jul 2024 12:18:40 +0200 Subject: [PATCH 103/573] lib/string_choices: Add str_up_down() helper Add str_up_down() helper to return "up" or "down" string literal. Signed-off-by: Michal Wajdeczko Link: https://lore.kernel.org/r/20240725101841.574-1-michal.wajdeczko@intel.com Signed-off-by: Kees Cook --- include/linux/string_choices.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/linux/string_choices.h b/include/linux/string_choices.h index d9ebe20229f8..bcde3c9cff81 100644 --- a/include/linux/string_choices.h +++ b/include/linux/string_choices.h @@ -42,6 +42,11 @@ static inline const char *str_yes_no(bool v) return v ? "yes" : "no"; } +static inline const char *str_up_down(bool v) +{ + return v ? "up" : "down"; +} + /** * str_plural - Return the simple pluralization based on English counts * @num: Number used for deciding pluralization From 9b97452bcce77f8ef29b20c9662d95988b5990e4 Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Thu, 25 Jul 2024 12:18:41 +0200 Subject: [PATCH 104/573] coccinelle: Add rules to find str_up_down() replacements Add rules for finding places where str_up_down() can be used. This currently finds over 20 locations. Signed-off-by: Michal Wajdeczko Link: https://lore.kernel.org/r/20240725101841.574-2-michal.wajdeczko@intel.com Signed-off-by: Kees Cook --- scripts/coccinelle/api/string_choices.cocci | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/scripts/coccinelle/api/string_choices.cocci b/scripts/coccinelle/api/string_choices.cocci index a71966c0494e..d517f6bc850b 100644 --- a/scripts/coccinelle/api/string_choices.cocci +++ b/scripts/coccinelle/api/string_choices.cocci @@ -39,3 +39,26 @@ e << str_plural_r.E; @@ coccilib.report.print_report(p[0], "opportunity for str_plural(%s)" % e) + +@str_up_down depends on patch@ +expression E; +@@ +( +- ((E) ? "up" : "down") ++ str_up_down(E) +) + +@str_up_down_r depends on !patch exists@ +expression E; +position P; +@@ +( +* ((E@P) ? "up" : "down") +) + +@script:python depends on report@ +p << str_up_down_r.P; +e << str_up_down_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_up_down(%s)" % e) From f5c1ca3a15fdb867d2b535003f74e0b975eff116 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Mon, 12 Aug 2024 11:29:40 -0700 Subject: [PATCH 105/573] string_choices: Add wrapper for str_down_up() The string choice functions which are not clearly true/false synonyms also have inverted wrappers. Add this for str_down_up() as well. Suggested-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240812182939.work.424-kees@kernel.org Reviewed-by: Andy Shevchenko Signed-off-by: Kees Cook --- include/linux/string_choices.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/string_choices.h b/include/linux/string_choices.h index bcde3c9cff81..1320bcdcb89c 100644 --- a/include/linux/string_choices.h +++ b/include/linux/string_choices.h @@ -46,6 +46,7 @@ static inline const char *str_up_down(bool v) { return v ? "up" : "down"; } +#define str_down_up(v) str_up_down(!(v)) /** * str_plural - Return the simple pluralization based on English counts From 0336f898881ae13b92dfd8b72e69ed1246eac762 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Mon, 12 Aug 2024 11:36:38 -0700 Subject: [PATCH 106/573] coccinelle: Add rules to find str_down_up() replacements As done with str_up_down(), add checks for str_down_up() opportunities. 5 cases currently exist in the tree. Suggested-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240812183637.work.999-kees@kernel.org Reviewed-by: Andy Shevchenko Signed-off-by: Kees Cook --- scripts/coccinelle/api/string_choices.cocci | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/scripts/coccinelle/api/string_choices.cocci b/scripts/coccinelle/api/string_choices.cocci index d517f6bc850b..5e729f187f22 100644 --- a/scripts/coccinelle/api/string_choices.cocci +++ b/scripts/coccinelle/api/string_choices.cocci @@ -62,3 +62,26 @@ e << str_up_down_r.E; @@ coccilib.report.print_report(p[0], "opportunity for str_up_down(%s)" % e) + +@str_down_up depends on patch@ +expression E; +@@ +( +- ((E) ? "down" : "up") ++ str_down_up(E) +) + +@str_down_up_r depends on !patch exists@ +expression E; +position P; +@@ +( +* ((E@P) ? "down" : "up") +) + +@script:python depends on report@ +p << str_down_up_r.P; +e << str_down_up_r.E; +@@ + +coccilib.report.print_report(p[0], "opportunity for str_down_up(%s)" % e) From bbf3c7ff9dfa45be51500d23a1276991a7cd8c6e Mon Sep 17 00:00:00 2001 From: Justin Stitt Date: Thu, 8 Aug 2024 14:43:56 -0700 Subject: [PATCH 107/573] lib/string_helpers: rework overflow-dependent code When @size is 0, the desired behavior is to allow unlimited bytes to be parsed. Currently, this relies on some intentional arithmetic overflow where --size gives us SIZE_MAX when size is 0. Explicitly spell out the desired behavior without relying on intentional overflow/underflow. Signed-off-by: Justin Stitt Link: https://lore.kernel.org/r/20240808-b4-string_helpers_caa133-v1-1-686a455167c4@google.com Signed-off-by: Kees Cook --- lib/string_helpers.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/string_helpers.c b/lib/string_helpers.c index 69ba49b853c7..4f887aa62fa0 100644 --- a/lib/string_helpers.c +++ b/lib/string_helpers.c @@ -321,6 +321,9 @@ int string_unescape(char *src, char *dst, size_t size, unsigned int flags) { char *out = dst; + if (!size) + size = SIZE_MAX; + while (*src && --size) { if (src[0] == '\\' && src[1] != '\0' && size > 1) { src++; From 98db56e4900837e4d5d3892b332dca76c8c9f68a Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Fri, 9 Aug 2024 10:27:14 -0700 Subject: [PATCH 108/573] iommu/arm-smmu: Un-demote unhandled-fault msg Previously this was dev_err_ratelimited() but it got changed to a ratelimited dev_dbg(). Change it back to dev_err(). Fixes: d525b0af0c3b ("iommu/arm-smmu: Pretty-print context fault related regs") Signed-off-by: Rob Clark Reviewed-by: Pranjal Shrivastava Link: https://lore.kernel.org/r/20240809172716.10275-1-robdclark@gmail.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu/arm-smmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu.c b/drivers/iommu/arm/arm-smmu/arm-smmu.c index 723273440c21..8321962b3714 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu.c @@ -417,7 +417,7 @@ void arm_smmu_read_context_fault_info(struct arm_smmu_device *smmu, int idx, void arm_smmu_print_context_fault_info(struct arm_smmu_device *smmu, int idx, const struct arm_smmu_context_fault_info *cfi) { - dev_dbg(smmu->dev, + dev_err(smmu->dev, "Unhandled context fault: fsr=0x%x, iova=0x%08lx, fsynr=0x%x, cbfrsynra=0x%x, cb=%d\n", cfi->fsr, cfi->iova, cfi->fsynr, cfi->cbfrsynra, idx); From df49881956bab88298e754c73010196b49af6733 Mon Sep 17 00:00:00 2001 From: Zhang Zekun Date: Thu, 15 Aug 2024 19:15:04 +0800 Subject: [PATCH 109/573] iommu/arm-smmu-v3: Remove the unused empty definition arm_smmu_sva_remove_dev_pasid() has been removed since commit d38c28dbefee ("iommu/arm-smmu-v3: Put the SVA mmu notifier in the smmu_domain"), remain the empty definition untouched in header file, which is used when CONFIG_ARM_SMMU_V3_SVA is not set. So, let's remove the unused definition. Signed-off-by: Zhang Zekun Reviewed-by: Jason Gunthorpe Link: https://lore.kernel.org/r/20240815111504.48810-1-zhangzekun11@huawei.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 14bca41a981b..c425c878bbd4 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -860,10 +860,5 @@ static inline void arm_smmu_sva_notifier_synchronize(void) {} #define arm_smmu_sva_domain_alloc NULL -static inline void arm_smmu_sva_remove_dev_pasid(struct iommu_domain *domain, - struct device *dev, - ioasid_t id) -{ -} #endif /* CONFIG_ARM_SMMU_V3_SVA */ #endif /* _ARM_SMMU_V3_H */ From af048ec9c05178206e845a88bfd3cb2884a43da7 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 15 Aug 2024 14:25:00 +0300 Subject: [PATCH 110/573] iommu/arm-smmu-v3: Fix a NULL vs IS_ERR() check The arm_smmu_domain_alloc() function returns error pointers on error. It doesn't return NULL. Update the error checking to match. Fixes: 52acd7d8a413 ("iommu/arm-smmu-v3: Add support for domain_alloc_user fn") Signed-off-by: Dan Carpenter Reviewed-by: Shameer Kolothum Reviewed-by: Jason Gunthorpe Link: https://lore.kernel.org/r/9208cd0d-8105-40df-93e9-bdcdf0d55eec@stanley.mountain Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index a31460f9f3d4..ac6c1597fb2d 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -3062,8 +3062,8 @@ arm_smmu_domain_alloc_user(struct device *dev, u32 flags, return ERR_PTR(-EOPNOTSUPP); smmu_domain = arm_smmu_domain_alloc(); - if (!smmu_domain) - return ERR_PTR(-ENOMEM); + if (IS_ERR(smmu_domain)) + return ERR_CAST(smmu_domain); smmu_domain->domain.type = IOMMU_DOMAIN_UNMANAGED; smmu_domain->domain.ops = arm_smmu_ops.default_domain_ops; From b8e4b0529d59a3ccd0b25a31d3cfc8b0f3b34068 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Tue, 13 Aug 2024 21:08:41 +0200 Subject: [PATCH 111/573] power: sequencing: qcom-wcn: add support for the WCN6855 PMU Enable support for controlling the power-up sequence of the PMU inside the WCN6855 model. Signed-off-by: Konrad Dybcio [Bartosz: split Konrad's bigger patch, write the commit message] Co-developed-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20240813190841.155067-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/pwrseq-qcom-wcn.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c index 42dacfda745e..a958d29eb18b 100644 --- a/drivers/power/sequencing/pwrseq-qcom-wcn.c +++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c @@ -198,6 +198,13 @@ static const struct pwrseq_qcom_wcn_pdata pwrseq_qca6390_of_data = { .gpio_enable_delay_ms = 100, }; +static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn6855_of_data = { + .vregs = pwrseq_qca6390_vregs, + .num_vregs = ARRAY_SIZE(pwrseq_qca6390_vregs), + .pwup_delay_ms = 50, + .gpio_enable_delay_ms = 5, +}; + static const char *const pwrseq_wcn7850_vregs[] = { "vdd", "vddio", @@ -314,6 +321,10 @@ static const struct of_device_id pwrseq_qcom_wcn_of_match[] = { .compatible = "qcom,qca6390-pmu", .data = &pwrseq_qca6390_of_data, }, + { + .compatible = "qcom,wcn6855-pmu", + .data = &pwrseq_wcn6855_of_data, + }, { .compatible = "qcom,wcn7850-pmu", .data = &pwrseq_wcn7850_of_data, From f2c38c96d51093a38af90e46bca813c7dff671d7 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 16 Aug 2024 17:13:56 +0200 Subject: [PATCH 112/573] gpio: of: simplify with scoped for each OF child loop Use scoped for_each_xxx loop when iterating over device nodes to make code a bit simpler. Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240816151356.154991-1-krzysztof.kozlowski@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-of.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index 6683e531df52..3bd3283b349c 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -338,11 +338,10 @@ static void of_gpio_flags_quirks(const struct device_node *np, */ if (IS_ENABLED(CONFIG_SPI_MASTER) && !strcmp(propname, "cs-gpios") && of_property_read_bool(np, "cs-gpios")) { - struct device_node *child; u32 cs; int ret; - for_each_child_of_node(np, child) { + for_each_child_of_node_scoped(np, child) { ret = of_property_read_u32(child, "reg", &cs); if (ret) continue; @@ -363,7 +362,6 @@ static void of_gpio_flags_quirks(const struct device_node *np, "spi-cs-high"); of_gpio_quirk_polarity(child, active_high, flags); - of_node_put(child); break; } } @@ -836,18 +834,15 @@ static int of_gpiochip_add_hog(struct gpio_chip *chip, struct device_node *hog) */ static int of_gpiochip_scan_gpios(struct gpio_chip *chip) { - struct device_node *np; int ret; - for_each_available_child_of_node(dev_of_node(&chip->gpiodev->dev), np) { + for_each_available_child_of_node_scoped(dev_of_node(&chip->gpiodev->dev), np) { if (!of_property_read_bool(np, "gpio-hog")) continue; ret = of_gpiochip_add_hog(chip, np); - if (ret < 0) { - of_node_put(np); + if (ret < 0) return ret; - } of_node_set_flag(np, OF_POPULATED); } From ef3d4b94d2d88b160887ff9ca737a5f8ec101579 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 Aug 2024 17:28:56 +0300 Subject: [PATCH 113/573] gpiolib: Introduce for_each_gpio_property_name() helper Introduce a helper macro for_each_gpio_property_name(). With that in place, update users. This, in particular, will help making the following simplifications easier. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240819142945.327808-2-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-acpi.c | 21 ++------------------- drivers/gpio/gpiolib-of.c | 25 ++++--------------------- drivers/gpio/gpiolib.h | 13 +++++++++++++ 3 files changed, 19 insertions(+), 40 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index 69cd2be9c7f3..cf4b1f068bac 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -973,18 +973,9 @@ __acpi_find_gpio(struct fwnode_handle *fwnode, const char *con_id, unsigned int struct acpi_device *adev = to_acpi_device_node(fwnode); struct gpio_desc *desc; char propname[32]; - int i; /* Try first from _DSD */ - for (i = 0; i < gpio_suffix_count; i++) { - if (con_id) { - snprintf(propname, sizeof(propname), "%s-%s", - con_id, gpio_suffixes[i]); - } else { - snprintf(propname, sizeof(propname), "%s", - gpio_suffixes[i]); - } - + for_each_gpio_property_name(propname, con_id) { if (adev) desc = acpi_get_gpiod_by_index(adev, propname, idx, info); @@ -1450,17 +1441,9 @@ int acpi_gpio_count(const struct fwnode_handle *fwnode, const char *con_id) int count = -ENOENT; int ret; char propname[32]; - unsigned int i; /* Try first from _DSD */ - for (i = 0; i < gpio_suffix_count; i++) { - if (con_id) - snprintf(propname, sizeof(propname), "%s-%s", - con_id, gpio_suffixes[i]); - else - snprintf(propname, sizeof(propname), "%s", - gpio_suffixes[i]); - + for_each_gpio_property_name(propname, con_id) { ret = acpi_dev_get_property(adev, propname, ACPI_TYPE_ANY, &obj); if (ret == 0) { if (obj->type == ACPI_TYPE_LOCAL_REFERENCE) diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index 3bd3283b349c..d0d78e0fa28c 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -97,20 +97,12 @@ int of_gpio_count(const struct fwnode_handle *fwnode, const char *con_id) const struct device_node *np = to_of_node(fwnode); int ret; char propname[32]; - unsigned int i; ret = of_gpio_spi_cs_get_count(np, con_id); if (ret > 0) return ret; - for (i = 0; i < gpio_suffix_count; i++) { - if (con_id) - snprintf(propname, sizeof(propname), "%s-%s", - con_id, gpio_suffixes[i]); - else - snprintf(propname, sizeof(propname), "%s", - gpio_suffixes[i]); - + for_each_gpio_property_name(propname, con_id) { ret = of_gpio_named_count(np, propname); if (ret > 0) break; @@ -685,23 +677,14 @@ static const of_find_gpio_quirk of_find_gpio_quirks[] = { struct gpio_desc *of_find_gpio(struct device_node *np, const char *con_id, unsigned int idx, unsigned long *flags) { - char prop_name[32]; /* 32 is max size of property name */ + char propname[32]; /* 32 is max size of property name */ enum of_gpio_flags of_flags; const of_find_gpio_quirk *q; struct gpio_desc *desc; - unsigned int i; /* Try GPIO property "foo-gpios" and "foo-gpio" */ - for (i = 0; i < gpio_suffix_count; i++) { - if (con_id) - snprintf(prop_name, sizeof(prop_name), "%s-%s", con_id, - gpio_suffixes[i]); - else - snprintf(prop_name, sizeof(prop_name), "%s", - gpio_suffixes[i]); - - desc = of_get_named_gpiod_flags(np, prop_name, idx, &of_flags); - + for_each_gpio_property_name(propname, con_id) { + desc = of_get_named_gpiod_flags(np, propname, idx, &of_flags); if (!gpiod_not_found(desc)) break; } diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 4de0bf1a62d3..0271e747fb6e 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -93,6 +93,19 @@ static inline struct gpio_device *to_gpio_device(struct device *dev) extern const char *const gpio_suffixes[]; extern const size_t gpio_suffix_count; +#define for_each_gpio_property_name(propname, con_id) \ + for (unsigned int __i = 0; \ + __i < gpio_suffix_count && ({ \ + const char *__gs = gpio_suffixes[__i]; \ + \ + if (con_id) \ + snprintf(propname, sizeof(propname), "%s-%s", con_id, __gs); \ + else \ + snprintf(propname, sizeof(propname), "%s", __gs); \ + 1; \ + }); \ + __i++) + /** * struct gpio_array - Opaque descriptor for a structure of GPIO array attributes * From e42fce0ff99658b5b43e8dae4f7acc43d38a00ef Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 Aug 2024 17:28:57 +0300 Subject: [PATCH 114/573] gpiolib: swnode: Unify return code variable name In one case 'ret' is used in the other 'error'. Make the latter use the former, i.e. 'ret'. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240819142945.327808-3-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-swnode.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpiolib-swnode.c b/drivers/gpio/gpiolib-swnode.c index cec1ab878af8..e7ba6cc73966 100644 --- a/drivers/gpio/gpiolib-swnode.c +++ b/drivers/gpio/gpiolib-swnode.c @@ -67,7 +67,7 @@ struct gpio_desc *swnode_find_gpio(struct fwnode_handle *fwnode, struct fwnode_reference_args args; struct gpio_desc *desc; char propname[32]; /* 32 is max size of property name */ - int error; + int ret; swnode = to_software_node(fwnode); if (!swnode) @@ -79,11 +79,11 @@ struct gpio_desc *swnode_find_gpio(struct fwnode_handle *fwnode, * We expect all swnode-described GPIOs have GPIO number and * polarity arguments, hence nargs is set to 2. */ - error = fwnode_property_get_reference_args(fwnode, propname, NULL, 2, idx, &args); - if (error) { + ret = fwnode_property_get_reference_args(fwnode, propname, NULL, 2, idx, &args); + if (ret) { pr_debug("%s: can't parse '%s' property of node '%pfwP[%d]'\n", __func__, propname, fwnode, idx); - return ERR_PTR(error); + return ERR_PTR(ret); } struct gpio_device *gdev __free(gpio_device_put) = From 7fd6809888a82055fcca9d14417d5e2675f0acc5 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 Aug 2024 17:28:58 +0300 Subject: [PATCH 115/573] gpiolib: swnode: Introduce swnode_gpio_get_reference() helper Instead of the spreading simlar code over the file, introduce a helper. It also enforces the nargs validation for all GPIO software node APIs. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240819142945.327808-4-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-swnode.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/gpio/gpiolib-swnode.c b/drivers/gpio/gpiolib-swnode.c index e7ba6cc73966..d5e58a9673b5 100644 --- a/drivers/gpio/gpiolib-swnode.c +++ b/drivers/gpio/gpiolib-swnode.c @@ -59,6 +59,17 @@ static struct gpio_device *swnode_get_gpio_device(struct fwnode_handle *fwnode) return gdev ?: ERR_PTR(-EPROBE_DEFER); } +static int swnode_gpio_get_reference(const struct fwnode_handle *fwnode, + const char *propname, unsigned int idx, + struct fwnode_reference_args *args) +{ + /* + * We expect all swnode-described GPIOs have GPIO number and + * polarity arguments, hence nargs is set to 2. + */ + return fwnode_property_get_reference_args(fwnode, propname, NULL, 2, idx, args); +} + struct gpio_desc *swnode_find_gpio(struct fwnode_handle *fwnode, const char *con_id, unsigned int idx, unsigned long *flags) @@ -75,11 +86,7 @@ struct gpio_desc *swnode_find_gpio(struct fwnode_handle *fwnode, swnode_format_propname(con_id, propname, sizeof(propname)); - /* - * We expect all swnode-described GPIOs have GPIO number and - * polarity arguments, hence nargs is set to 2. - */ - ret = fwnode_property_get_reference_args(fwnode, propname, NULL, 2, idx, &args); + ret = swnode_gpio_get_reference(fwnode, propname, idx, &args); if (ret) { pr_debug("%s: can't parse '%s' property of node '%pfwP[%d]'\n", __func__, propname, fwnode, idx); @@ -128,8 +135,7 @@ int swnode_gpio_count(const struct fwnode_handle *fwnode, const char *con_id) * 1 or 2 entries. */ count = 0; - while (fwnode_property_get_reference_args(fwnode, propname, NULL, 0, - count, &args) == 0) { + while (swnode_gpio_get_reference(fwnode, propname, count, &args) == 0) { fwnode_handle_put(args.fwnode); count++; } From a975a64692c39991fdde2f1d990b7bdd48d183fc Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 Aug 2024 17:28:59 +0300 Subject: [PATCH 116/573] gpiolib: swnode: Make use of for_each_gpio_property_name() For the sake of unification and easier maintenance replace swnode_format_propname() call with for_each_gpio_property_name() for-loop. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240819142945.327808-5-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-swnode.c | 38 +++++++++++++---------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/drivers/gpio/gpiolib-swnode.c b/drivers/gpio/gpiolib-swnode.c index d5e58a9673b5..1a6f70671816 100644 --- a/drivers/gpio/gpiolib-swnode.c +++ b/drivers/gpio/gpiolib-swnode.c @@ -24,20 +24,6 @@ #define GPIOLIB_SWNODE_UNDEFINED_NAME "swnode-gpio-undefined" -static void swnode_format_propname(const char *con_id, char *propname, - size_t max_size) -{ - /* - * Note we do not need to try both -gpios and -gpio suffixes, - * as, unlike OF and ACPI, we can fix software nodes to conform - * to the proper binding. - */ - if (con_id) - snprintf(propname, max_size, "%s-gpios", con_id); - else - strscpy(propname, "gpios", max_size); -} - static struct gpio_device *swnode_get_gpio_device(struct fwnode_handle *fwnode) { const struct software_node *gdev_node; @@ -84,9 +70,11 @@ struct gpio_desc *swnode_find_gpio(struct fwnode_handle *fwnode, if (!swnode) return ERR_PTR(-EINVAL); - swnode_format_propname(con_id, propname, sizeof(propname)); - - ret = swnode_gpio_get_reference(fwnode, propname, idx, &args); + for_each_gpio_property_name(propname, con_id) { + ret = swnode_gpio_get_reference(fwnode, propname, idx, &args); + if (ret == 0) + break; + } if (ret) { pr_debug("%s: can't parse '%s' property of node '%pfwP[%d]'\n", __func__, propname, fwnode, idx); @@ -128,19 +116,21 @@ int swnode_gpio_count(const struct fwnode_handle *fwnode, const char *con_id) char propname[32]; int count; - swnode_format_propname(con_id, propname, sizeof(propname)); - /* * This is not very efficient, but GPIO lists usually have only * 1 or 2 entries. */ - count = 0; - while (swnode_gpio_get_reference(fwnode, propname, count, &args) == 0) { - fwnode_handle_put(args.fwnode); - count++; + for_each_gpio_property_name(propname, con_id) { + count = 0; + while (swnode_gpio_get_reference(fwnode, propname, count, &args) == 0) { + fwnode_handle_put(args.fwnode); + count++; + } + if (count) + return count; } - return count ?: -ENOENT; + return -ENOENT; } #if IS_ENABLED(CONFIG_GPIO_SWNODE_UNDEFINED) From 4b91188dced811e2d867574b672888406cb7114c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 Aug 2024 17:29:00 +0300 Subject: [PATCH 117/573] gpiolib: Replace gpio_suffix_count with NULL-terminated array There is no need to have and export the count variable for the array in question. Instead, make it NULL-terminated. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240819142945.327808-6-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 3 +-- drivers/gpio/gpiolib.h | 11 +++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 3a9668cc100d..3903d0a75304 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -90,8 +90,7 @@ DEFINE_STATIC_SRCU(gpio_devices_srcu); static DEFINE_MUTEX(gpio_machine_hogs_mutex); static LIST_HEAD(gpio_machine_hogs); -const char *const gpio_suffixes[] = { "gpios", "gpio" }; -const size_t gpio_suffix_count = ARRAY_SIZE(gpio_suffixes); +const char *const gpio_suffixes[] = { "gpios", "gpio", NULL }; static void gpiochip_free_hogs(struct gpio_chip *gc); static int gpiochip_add_irqchip(struct gpio_chip *gc, diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 0271e747fb6e..067197d61d57 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -89,14 +89,13 @@ static inline struct gpio_device *to_gpio_device(struct device *dev) return container_of(dev, struct gpio_device, dev); } -/* gpio suffixes used for ACPI and device tree lookup */ +/* GPIO suffixes used for ACPI and device tree lookup */ extern const char *const gpio_suffixes[]; -extern const size_t gpio_suffix_count; #define for_each_gpio_property_name(propname, con_id) \ - for (unsigned int __i = 0; \ - __i < gpio_suffix_count && ({ \ - const char *__gs = gpio_suffixes[__i]; \ + for (const char * const *__suffixes = gpio_suffixes; \ + *__suffixes && ({ \ + const char *__gs = *__suffixes; \ \ if (con_id) \ snprintf(propname, sizeof(propname), "%s-%s", con_id, __gs); \ @@ -104,7 +103,7 @@ extern const size_t gpio_suffix_count; snprintf(propname, sizeof(propname), "%s", __gs); \ 1; \ }); \ - __i++) + __suffixes++) /** * struct gpio_array - Opaque descriptor for a structure of GPIO array attributes From 77c5e7b623032502ee49fe7e7868eaca6786d7ed Mon Sep 17 00:00:00 2001 From: Finley Xiao Date: Wed, 14 Aug 2024 18:26:41 -0400 Subject: [PATCH 118/573] dt-bindings: power: Add support for RK3576 SoC Define power domain IDs as described in the TRM and add compatible for rockchip,rk3576-power-controller Signed-off-by: Finley Xiao Co-Developed-by: Detlev Casanova Signed-off-by: Detlev Casanova Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20240814222824.3170-2-detlev.casanova@collabora.com Signed-off-by: Ulf Hansson --- .../power/rockchip,power-controller.yaml | 1 + .../dt-bindings/power/rockchip,rk3576-power.h | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 include/dt-bindings/power/rockchip,rk3576-power.h diff --git a/Documentation/devicetree/bindings/power/rockchip,power-controller.yaml b/Documentation/devicetree/bindings/power/rockchip,power-controller.yaml index 0d5e999a58f1..650dc0aae6f5 100644 --- a/Documentation/devicetree/bindings/power/rockchip,power-controller.yaml +++ b/Documentation/devicetree/bindings/power/rockchip,power-controller.yaml @@ -41,6 +41,7 @@ properties: - rockchip,rk3368-power-controller - rockchip,rk3399-power-controller - rockchip,rk3568-power-controller + - rockchip,rk3576-power-controller - rockchip,rk3588-power-controller - rockchip,rv1126-power-controller diff --git a/include/dt-bindings/power/rockchip,rk3576-power.h b/include/dt-bindings/power/rockchip,rk3576-power.h new file mode 100644 index 000000000000..324a056aa851 --- /dev/null +++ b/include/dt-bindings/power/rockchip,rk3576-power.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ +#ifndef __DT_BINDINGS_POWER_RK3576_POWER_H__ +#define __DT_BINDINGS_POWER_RK3576_POWER_H__ + +/* VD_NPU */ +#define RK3576_PD_NPU 0 +#define RK3576_PD_NPUTOP 1 +#define RK3576_PD_NPU0 2 +#define RK3576_PD_NPU1 3 + +/* VD_GPU */ +#define RK3576_PD_GPU 4 + +/* VD_LOGIC */ +#define RK3576_PD_NVM 5 +#define RK3576_PD_SDGMAC 6 +#define RK3576_PD_USB 7 +#define RK3576_PD_PHP 8 +#define RK3576_PD_SUBPHP 9 +#define RK3576_PD_AUDIO 10 +#define RK3576_PD_VEPU0 11 +#define RK3576_PD_VEPU1 12 +#define RK3576_PD_VPU 13 +#define RK3576_PD_VDEC 14 +#define RK3576_PD_VI 15 +#define RK3576_PD_VO0 16 +#define RK3576_PD_VO1 17 +#define RK3576_PD_VOP 18 + +#endif From cfee1b50775869de9076d021ea11a8438854dcba Mon Sep 17 00:00:00 2001 From: Finley Xiao Date: Wed, 14 Aug 2024 18:26:42 -0400 Subject: [PATCH 119/573] pmdomain: rockchip: Add support for RK3576 SoC Add configuration for RK3576 SoC and list the power domains. Signed-off-by: Finley Xiao Signed-off-by: Detlev Casanova Reviewed-by: Elaine Zhang Link: https://lore.kernel.org/r/20240814222824.3170-3-detlev.casanova@collabora.com Signed-off-by: Ulf Hansson --- drivers/pmdomain/rockchip/pm-domains.c | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index 9b76b62869d0..64b4d7120d83 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -33,6 +33,7 @@ #include #include #include +#include #include struct rockchip_domain_info { @@ -175,6 +176,9 @@ struct rockchip_pmu { #define DOMAIN_RK3568(name, pwr, req, wakeup) \ DOMAIN_M(name, pwr, pwr, req, req, req, wakeup) +#define DOMAIN_RK3576(name, p_offset, pwr, status, r_status, r_offset, req, idle, wakeup) \ + DOMAIN_M_O_R(name, p_offset, pwr, status, 0, r_status, r_status, r_offset, req, idle, idle, wakeup) + /* * Dynamic Memory Controller may need to coordinate with us -- see * rockchip_pmu_block(). @@ -1106,6 +1110,28 @@ static const struct rockchip_domain_info rk3568_pm_domains[] = { [RK3568_PD_PIPE] = DOMAIN_RK3568("pipe", BIT(8), BIT(11), false), }; +static const struct rockchip_domain_info rk3576_pm_domains[] = { + [RK3576_PD_NPU] = DOMAIN_RK3576("npu", 0x0, BIT(0), BIT(0), 0, 0x0, 0, 0, false), + [RK3576_PD_NVM] = DOMAIN_RK3576("nvm", 0x0, BIT(6), 0, BIT(6), 0x4, BIT(2), BIT(18), false), + [RK3576_PD_SDGMAC] = DOMAIN_RK3576("sdgmac", 0x0, BIT(7), 0, BIT(7), 0x4, BIT(1), BIT(17), false), + [RK3576_PD_AUDIO] = DOMAIN_RK3576("audio", 0x0, BIT(8), 0, BIT(8), 0x4, BIT(0), BIT(16), false), + [RK3576_PD_PHP] = DOMAIN_RK3576("php", 0x0, BIT(9), 0, BIT(9), 0x0, BIT(15), BIT(15), false), + [RK3576_PD_SUBPHP] = DOMAIN_RK3576("subphp", 0x0, BIT(10), 0, BIT(10), 0x0, 0, 0, false), + [RK3576_PD_VOP] = DOMAIN_RK3576("vop", 0x0, BIT(11), 0, BIT(11), 0x0, 0x6000, 0x6000, false), + [RK3576_PD_VO1] = DOMAIN_RK3576("vo1", 0x0, BIT(14), 0, BIT(14), 0x0, BIT(12), BIT(12), false), + [RK3576_PD_VO0] = DOMAIN_RK3576("vo0", 0x0, BIT(15), 0, BIT(15), 0x0, BIT(11), BIT(11), false), + [RK3576_PD_USB] = DOMAIN_RK3576("usb", 0x4, BIT(0), 0, BIT(16), 0x0, BIT(10), BIT(10), true), + [RK3576_PD_VI] = DOMAIN_RK3576("vi", 0x4, BIT(1), 0, BIT(17), 0x0, BIT(9), BIT(9), false), + [RK3576_PD_VEPU0] = DOMAIN_RK3576("vepu0", 0x4, BIT(2), 0, BIT(18), 0x0, BIT(7), BIT(7), false), + [RK3576_PD_VEPU1] = DOMAIN_RK3576("vepu1", 0x4, BIT(3), 0, BIT(19), 0x0, BIT(8), BIT(8), false), + [RK3576_PD_VDEC] = DOMAIN_RK3576("vdec", 0x4, BIT(4), 0, BIT(20), 0x0, BIT(6), BIT(6), false), + [RK3576_PD_VPU] = DOMAIN_RK3576("vpu", 0x4, BIT(5), 0, BIT(21), 0x0, BIT(5), BIT(5), false), + [RK3576_PD_NPUTOP] = DOMAIN_RK3576("nputop", 0x4, BIT(6), 0, BIT(22), 0x0, 0x18, 0x18, false), + [RK3576_PD_NPU0] = DOMAIN_RK3576("npu0", 0x4, BIT(7), 0, BIT(23), 0x0, BIT(1), BIT(1), false), + [RK3576_PD_NPU1] = DOMAIN_RK3576("npu1", 0x4, BIT(8), 0, BIT(24), 0x0, BIT(2), BIT(2), false), + [RK3576_PD_GPU] = DOMAIN_RK3576("gpu", 0x4, BIT(9), 0, BIT(25), 0x0, BIT(0), BIT(0), false), +}; + static const struct rockchip_domain_info rk3588_pm_domains[] = { [RK3588_PD_GPU] = DOMAIN_RK3588("gpu", 0x0, BIT(0), 0, 0x0, 0, BIT(1), 0x0, BIT(0), BIT(0), false), [RK3588_PD_NPU] = DOMAIN_RK3588("npu", 0x0, BIT(1), BIT(1), 0x0, 0, 0, 0x0, 0, 0, false), @@ -1284,6 +1310,21 @@ static const struct rockchip_pmu_info rk3568_pmu = { .domain_info = rk3568_pm_domains, }; +static const struct rockchip_pmu_info rk3576_pmu = { + .pwr_offset = 0x210, + .status_offset = 0x230, + .chain_status_offset = 0x248, + .mem_status_offset = 0x250, + .mem_pwr_offset = 0x300, + .req_offset = 0x110, + .idle_offset = 0x128, + .ack_offset = 0x120, + .repair_status_offset = 0x570, + + .num_domains = ARRAY_SIZE(rk3576_pm_domains), + .domain_info = rk3576_pm_domains, +}; + static const struct rockchip_pmu_info rk3588_pmu = { .pwr_offset = 0x14c, .status_offset = 0x180, @@ -1359,6 +1400,10 @@ static const struct of_device_id rockchip_pm_domain_dt_match[] = { .compatible = "rockchip,rk3568-power-controller", .data = (void *)&rk3568_pmu, }, + { + .compatible = "rockchip,rk3576-power-controller", + .data = (void *)&rk3576_pmu, + }, { .compatible = "rockchip,rk3588-power-controller", .data = (void *)&rk3588_pmu, From b6cee6544d01b8ac01232d343b1b2b594d94d61c Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Mon, 19 Aug 2024 15:59:09 +0530 Subject: [PATCH 120/573] PM: domains: add device managed version of dev_pm_domain_attach|detach_list() Add the devres-enabled version of dev_pm_domain_attach|detach_list. If client drivers use devm_pm_domain_attach_list() to attach the PM domains, devm_pm_domain_detach_list() will be invoked implicitly during remove phase. Signed-off-by: Dikshita Agarwal Link: https://lore.kernel.org/r/1724063350-11993-2-git-send-email-quic_dikshita@quicinc.com Signed-off-by: Ulf Hansson --- drivers/base/power/common.c | 45 +++++++++++++++++++++++++++++++++++++ include/linux/pm_domain.h | 11 +++++++++ 2 files changed, 56 insertions(+) diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c index 327d168dd37a..8c34ae1cd8d5 100644 --- a/drivers/base/power/common.c +++ b/drivers/base/power/common.c @@ -276,6 +276,51 @@ err_attach: } EXPORT_SYMBOL_GPL(dev_pm_domain_attach_list); +/** + * devm_pm_domain_detach_list - devres-enabled version of dev_pm_domain_detach_list. + * @_list: The list of PM domains to detach. + * + * This function reverse the actions from devm_pm_domain_attach_list(). + * it will be invoked during the remove phase from drivers implicitly if driver + * uses devm_pm_domain_attach_list() to attach the PM domains. + */ +static void devm_pm_domain_detach_list(void *_list) +{ + struct dev_pm_domain_list *list = _list; + + dev_pm_domain_detach_list(list); +} + +/** + * devm_pm_domain_attach_list - devres-enabled version of dev_pm_domain_attach_list + * @dev: The device used to lookup the PM domains for. + * @data: The data used for attaching to the PM domains. + * @list: An out-parameter with an allocated list of attached PM domains. + * + * NOTE: this will also handle calling devm_pm_domain_detach_list() for + * you during remove phase. + * + * Returns the number of attached PM domains or a negative error code in case of + * a failure. + */ +int devm_pm_domain_attach_list(struct device *dev, + const struct dev_pm_domain_attach_data *data, + struct dev_pm_domain_list **list) +{ + int ret, num_pds; + + num_pds = dev_pm_domain_attach_list(dev, data, list); + if (num_pds <= 0) + return num_pds; + + ret = devm_add_action_or_reset(dev, devm_pm_domain_detach_list, *list); + if (ret) + return ret; + + return num_pds; +} +EXPORT_SYMBOL_GPL(devm_pm_domain_attach_list); + /** * dev_pm_domain_detach - Detach a device from its PM domain. * @dev: Device to detach. diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index b86bb52858ac..b637ec14025f 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -476,6 +476,9 @@ struct device *dev_pm_domain_attach_by_name(struct device *dev, int dev_pm_domain_attach_list(struct device *dev, const struct dev_pm_domain_attach_data *data, struct dev_pm_domain_list **list); +int devm_pm_domain_attach_list(struct device *dev, + const struct dev_pm_domain_attach_data *data, + struct dev_pm_domain_list **list); void dev_pm_domain_detach(struct device *dev, bool power_off); void dev_pm_domain_detach_list(struct dev_pm_domain_list *list); int dev_pm_domain_start(struct device *dev); @@ -502,6 +505,14 @@ static inline int dev_pm_domain_attach_list(struct device *dev, { return 0; } + +static inline int devm_pm_domain_attach_list(struct device *dev, + const struct dev_pm_domain_attach_data *data, + struct dev_pm_domain_list **list) +{ + return 0; +} + static inline void dev_pm_domain_detach(struct device *dev, bool power_off) {} static inline void dev_pm_domain_detach_list(struct dev_pm_domain_list *list) {} static inline int dev_pm_domain_start(struct device *dev) From 3b019409ce9a280db53dbf6beb7ee42c17951782 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Mon, 19 Aug 2024 15:59:10 +0530 Subject: [PATCH 121/573] media: venus: use device managed APIs for power domains Use devres-enabled version of power domain attach APIs. Signed-off-by: Dikshita Agarwal Reviewed-by: Bryan O'Donoghue Link: https://lore.kernel.org/r/1724063350-11993-3-git-send-email-quic_dikshita@quicinc.com Signed-off-by: Ulf Hansson --- drivers/media/platform/qcom/venus/pm_helpers.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/media/platform/qcom/venus/pm_helpers.c b/drivers/media/platform/qcom/venus/pm_helpers.c index 4ce76ce6dd4d..ea8a2bd9419e 100644 --- a/drivers/media/platform/qcom/venus/pm_helpers.c +++ b/drivers/media/platform/qcom/venus/pm_helpers.c @@ -876,7 +876,7 @@ static int vcodec_domains_get(struct venus_core *core) if (!res->vcodec_pmdomains_num) goto skip_pmdomains; - ret = dev_pm_domain_attach_list(dev, &vcodec_data, &core->pmdomains); + ret = devm_pm_domain_attach_list(dev, &vcodec_data, &core->pmdomains); if (ret < 0) return ret; @@ -902,14 +902,11 @@ skip_pmdomains: return 0; opp_attach_err: - dev_pm_domain_detach_list(core->pmdomains); return ret; } static void vcodec_domains_put(struct venus_core *core) { - dev_pm_domain_detach_list(core->pmdomains); - if (!core->has_opp_table) return; From 9b37f971f31354fcfd84b7c2f219138644c7edd3 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 19 Aug 2024 19:59:56 +0800 Subject: [PATCH 122/573] pmdomain: apple: Make apple_pmgr_reset_ops static The sparse tool complains as follows: drivers/pmdomain/apple/pmgr-pwrstate.c:180:32: warning: symbol 'apple_pmgr_reset_ops' was not declared. Should it be static: This symbol is not used outside of pmgr-pwrstate.c, so marks it static. Signed-off-by: Jinjie Ruan Link: https://lore.kernel.org/r/20240819115956.3884847-1-ruanjinjie@huawei.com Signed-off-by: Ulf Hansson --- drivers/pmdomain/apple/pmgr-pwrstate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c index d62a776c89a1..9467235110f4 100644 --- a/drivers/pmdomain/apple/pmgr-pwrstate.c +++ b/drivers/pmdomain/apple/pmgr-pwrstate.c @@ -177,7 +177,7 @@ static int apple_pmgr_reset_status(struct reset_controller_dev *rcdev, unsigned return !!(reg & APPLE_PMGR_RESET); } -const struct reset_control_ops apple_pmgr_reset_ops = { +static const struct reset_control_ops apple_pmgr_reset_ops = { .assert = apple_pmgr_reset_assert, .deassert = apple_pmgr_reset_deassert, .reset = apple_pmgr_reset_reset, From 7aa1204d086e1eb8fc09203d2cdc3f798683d6d4 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 16 Aug 2024 17:09:28 +0200 Subject: [PATCH 123/573] cpuidle: psci: Simplify with scoped for each OF child loop Use scoped for_each_child_of_node_scoped() when iterating over device nodes to make code a bit simpler. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Jonathan Cameron Link: https://lore.kernel.org/r/20240816150931.142208-1-krzysztof.kozlowski@linaro.org Signed-off-by: Ulf Hansson --- drivers/cpuidle/cpuidle-psci-domain.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/cpuidle/cpuidle-psci-domain.c b/drivers/cpuidle/cpuidle-psci-domain.c index ea28b73ef3fb..146f97068022 100644 --- a/drivers/cpuidle/cpuidle-psci-domain.c +++ b/drivers/cpuidle/cpuidle-psci-domain.c @@ -142,7 +142,6 @@ static const struct of_device_id psci_of_match[] = { static int psci_cpuidle_domain_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; - struct device_node *node; bool use_osi = psci_has_osi_support(); int ret = 0, pd_count = 0; @@ -153,15 +152,13 @@ static int psci_cpuidle_domain_probe(struct platform_device *pdev) * Parse child nodes for the "#power-domain-cells" property and * initialize a genpd/genpd-of-provider pair when it's found. */ - for_each_child_of_node(np, node) { + for_each_child_of_node_scoped(np, node) { if (!of_property_present(node, "#power-domain-cells")) continue; ret = psci_pd_init(node, use_osi); - if (ret) { - of_node_put(node); + if (ret) goto exit; - } pd_count++; } From 157519c026ec0773401111e33aed20a7a6800d5f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 16 Aug 2024 17:09:31 +0200 Subject: [PATCH 124/573] cpuidle: dt_idle_genpd: Simplify with scoped for each OF child loop Use scoped for_each_child_of_node_scoped() when iterating over device nodes to make code a bit simpler. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Jonathan Cameron Link: https://lore.kernel.org/r/20240816150931.142208-4-krzysztof.kozlowski@linaro.org Signed-off-by: Ulf Hansson --- drivers/cpuidle/dt_idle_genpd.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/drivers/cpuidle/dt_idle_genpd.c b/drivers/cpuidle/dt_idle_genpd.c index 1af63c189039..203e9b754aea 100644 --- a/drivers/cpuidle/dt_idle_genpd.c +++ b/drivers/cpuidle/dt_idle_genpd.c @@ -130,11 +130,10 @@ out: int dt_idle_pd_init_topology(struct device_node *np) { - struct device_node *node; struct of_phandle_args child, parent; int ret; - for_each_child_of_node(np, node) { + for_each_child_of_node_scoped(np, node) { if (of_parse_phandle_with_args(node, "power-domains", "#power-domain-cells", 0, &parent)) continue; @@ -143,10 +142,8 @@ int dt_idle_pd_init_topology(struct device_node *np) child.args_count = 0; ret = of_genpd_add_subdomain(&parent, &child); of_node_put(parent.np); - if (ret) { - of_node_put(node); + if (ret) return ret; - } } return 0; @@ -154,11 +151,10 @@ int dt_idle_pd_init_topology(struct device_node *np) int dt_idle_pd_remove_topology(struct device_node *np) { - struct device_node *node; struct of_phandle_args child, parent; int ret; - for_each_child_of_node(np, node) { + for_each_child_of_node_scoped(np, node) { if (of_parse_phandle_with_args(node, "power-domains", "#power-domain-cells", 0, &parent)) continue; @@ -167,10 +163,8 @@ int dt_idle_pd_remove_topology(struct device_node *np) child.args_count = 0; ret = of_genpd_remove_subdomain(&parent, &child); of_node_put(parent.np); - if (ret) { - of_node_put(node); + if (ret) return ret; - } } return 0; From 2a4727e6a8bd1d2b8ae7abf95061eda0457c4d79 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 20 Aug 2024 23:08:58 +0300 Subject: [PATCH 125/573] gpio: virtuser: Use GPIO_LOOKUP_IDX() macro Use GPIO_LOOKUP_IDX() macro which provides a compound literal and can be used with dynamic data. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240820200858.3659995-1-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-virtuser.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/gpio/gpio-virtuser.c b/drivers/gpio/gpio-virtuser.c index ccc47ea0b3e1..91b6352c957c 100644 --- a/drivers/gpio/gpio-virtuser.c +++ b/drivers/gpio/gpio-virtuser.c @@ -1410,7 +1410,6 @@ gpio_virtuser_make_lookup_table(struct gpio_virtuser_device *dev) size_t num_entries = gpio_virtuser_get_lookup_count(dev); struct gpio_virtuser_lookup_entry *entry; struct gpio_virtuser_lookup *lookup; - struct gpiod_lookup *curr; unsigned int i = 0; lockdep_assert_held(&dev->lock); @@ -1426,14 +1425,10 @@ gpio_virtuser_make_lookup_table(struct gpio_virtuser_device *dev) list_for_each_entry(lookup, &dev->lookup_list, siblings) { list_for_each_entry(entry, &lookup->entry_list, siblings) { - curr = &table->table[i]; - - curr->con_id = lookup->con_id; - curr->idx = i; - curr->key = entry->key; - curr->chip_hwnum = entry->offset < 0 ? - U16_MAX : entry->offset; - curr->flags = entry->flags; + table->table[i] = + GPIO_LOOKUP_IDX(entry->key, + entry->offset < 0 ? U16_MAX : entry->offset, + lookup->con_id, i, entry->flags); i++; } } From b41a9bf2c64eea119bb6cbef420345f547b9a677 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Wed, 21 Aug 2024 08:42:02 -0300 Subject: [PATCH 126/573] gpio: pca953x: Print the error code on read/write failures Print the error code in the pca953x_write_regs() and pca953x_read_regs() functions to help debugging. Suggested-by: Russell King (Oracle) Signed-off-by: Fabio Estevam Link: https://lore.kernel.org/r/20240821114202.2072220-1-festevam@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pca953x.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index 8baf3edd5274..3f2d33ee20cc 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -498,7 +498,7 @@ static int pca953x_write_regs(struct pca953x_chip *chip, int reg, unsigned long ret = regmap_bulk_write(chip->regmap, regaddr, value, NBANK(chip)); if (ret < 0) { - dev_err(&chip->client->dev, "failed writing register\n"); + dev_err(&chip->client->dev, "failed writing register: %d\n", ret); return ret; } @@ -513,7 +513,7 @@ static int pca953x_read_regs(struct pca953x_chip *chip, int reg, unsigned long * ret = regmap_bulk_read(chip->regmap, regaddr, value, NBANK(chip)); if (ret < 0) { - dev_err(&chip->client->dev, "failed reading register\n"); + dev_err(&chip->client->dev, "failed reading register: %d\n", ret); return ret; } From 8b7e0a6c443e855374a426dcdfd0a19912d70df3 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 21 Aug 2024 12:08:18 +0200 Subject: [PATCH 127/573] Documentation: add a driver API doc for the power sequencing subsystem Describe what the subsystem does, how the consumers and providers work and add API reference generated from kerneldocs. Acked-by: Jonathan Corbet Link: https://lore.kernel.org/r/20240821100818.13763-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- Documentation/driver-api/index.rst | 1 + Documentation/driver-api/pwrseq.rst | 95 +++++++++++++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 97 insertions(+) create mode 100644 Documentation/driver-api/pwrseq.rst diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst index f10decc2c14b..7f83e05769b4 100644 --- a/Documentation/driver-api/index.rst +++ b/Documentation/driver-api/index.rst @@ -124,6 +124,7 @@ Subsystem-specific APIs pps ptp pwm + pwrseq regulator reset rfkill diff --git a/Documentation/driver-api/pwrseq.rst b/Documentation/driver-api/pwrseq.rst new file mode 100644 index 000000000000..a644084ded17 --- /dev/null +++ b/Documentation/driver-api/pwrseq.rst @@ -0,0 +1,95 @@ +.. SPDX-License-Identifier: GPL-2.0-only +.. Copyright 2024 Linaro Ltd. + +==================== +Power Sequencing API +==================== + +:Author: Bartosz Golaszewski + +Introduction +============ + +This framework is designed to abstract complex power-up sequences that are +shared between multiple logical devices in the linux kernel. + +The intention is to allow consumers to obtain a power sequencing handle +exposed by the power sequence provider and delegate the actual requesting and +control of the underlying resources as well as to allow the provider to +mitigate any potential conflicts between multiple users behind the scenes. + +Glossary +-------- + +The power sequencing API uses a number of terms specific to the subsystem: + +Unit + + A unit is a discreet chunk of a power sequence. For instance one unit may + enable a set of regulators, another may enable a specific GPIO. Units can + define dependencies in the form of other units that must be enabled before + it itself can be. + +Target + + A target is a set of units (composed of the "final" unit and its + dependencies) that a consumer selects by its name when requesting a handle + to the power sequencer. Via the dependency system, multiple targets may + share the same parts of a power sequence but ignore parts that are + irrelevant. + +Descriptor + + A handle passed by the pwrseq core to every consumer that serves as the + entry point to the provider layer. It ensures coherence between different + users and keeps reference counting consistent. + +Consumer interface +================== + +The consumer API is aimed to be as simple as possible. The driver interested in +getting a descriptor from the power sequencer should call pwrseq_get() and +specify the name of the target it wants to reach in the sequence after calling +pwrseq_power_up(). The descriptor can be released by calling pwrseq_put() and +the consumer can request the powering down of its target with +pwrseq_power_off(). Note that there is no guarantee that pwrseq_power_off() +will have any effect as there may be multiple users of the underlying resources +who may keep them active. + +Provider interface +================== + +The provider API is admittedly not nearly as straightforward as the one for +consumers but it makes up for it in flexibility. + +Each provider can logically split the power-up sequence into descrete chunks +(units) and define their dependencies. They can then expose named targets that +consumers may use as the final point in the sequence that they wish to reach. + +To that end the providers fill out a set of configuration structures and +register with the pwrseq subsystem by calling pwrseq_device_register(). + +Dynamic consumer matching +------------------------- + +The main difference between pwrseq and other linux kernel providers is the +mechanism for dynamic matching of consumers and providers. Every power sequence +provider driver must implement the `match()` callback and pass it to the pwrseq +core when registering with the subsystems. + +When a client requests a sequencer handle, the core will call this callback for +every registered provider and let it flexibly figure out whether the proposed +client device is indeed its consumer. For example: if the provider binds to the +device-tree node representing a power management unit of a chipset and the +consumer driver controls one of its modules, the provider driver may parse the +relevant regulator supply properties in device tree and see if they lead from +the PMU to the consumer. + +API reference +============= + +.. kernel-doc:: include/linux/pwrseq/provider.h + :internal: + +.. kernel-doc:: drivers/power/sequencing/core.c + :export: diff --git a/MAINTAINERS b/MAINTAINERS index 42decde38320..e3d3a1de01c9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18201,6 +18201,7 @@ M: Bartosz Golaszewski L: linux-pm@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git +F: Documentation/driver-api/pwrseq.rst F: drivers/power/sequencing/ F: include/linux/pwrseq/ From 5ac86f0ed04bce41242167ffa12ad92038788a95 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 10 Jul 2024 16:15:55 -0700 Subject: [PATCH 128/573] virt: vbox: struct vmmdev_hgcm_pagelist: Replace 1-element array with flexible array Replace the deprecated[1] use of a 1-element array in struct vmmdev_hgcm_pagelist with a modern flexible array. As this is UAPI, we cannot trivially change the size of the struct, so use a union to retain the old first element's size, but switch "pages" to a flexible array. No binary differences are present after this conversion. Link: https://github.com/KSPP/linux/issues/79 [1] Reviewed-by: Gustavo A. R. Silva Link: https://lore.kernel.org/r/20240710231555.work.406-kees@kernel.org Signed-off-by: Kees Cook --- include/uapi/linux/vbox_vmmdev_types.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/uapi/linux/vbox_vmmdev_types.h b/include/uapi/linux/vbox_vmmdev_types.h index f8a8d6b3c521..6073858d52a2 100644 --- a/include/uapi/linux/vbox_vmmdev_types.h +++ b/include/uapi/linux/vbox_vmmdev_types.h @@ -282,7 +282,10 @@ struct vmmdev_hgcm_pagelist { __u32 flags; /** VMMDEV_HGCM_F_PARM_*. */ __u16 offset_first_page; /** Data offset in the first page. */ __u16 page_count; /** Number of pages. */ - __u64 pages[1]; /** Page addresses. */ + union { + __u64 unused; /** Deprecated place-holder for first "pages" entry. */ + __DECLARE_FLEX_ARRAY(__u64, pages); /** Page addresses. */ + }; }; VMMDEV_ASSERT_SIZE(vmmdev_hgcm_pagelist, 4 + 2 + 2 + 8); From c93452777f537b2f6c3c601dc484821142b07dc7 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 10 Jul 2024 16:09:12 -0700 Subject: [PATCH 129/573] media: venus: hfi_cmds: struct hfi_session_release_buffer_pkt: Replace 1-element array with flexible array Replace the deprecated[1] use of a 1-element array in struct hfi_session_release_buffer_pkt with a modern flexible array. No binary differences are present after this conversion. Link: https://github.com/KSPP/linux/issues/79 [1] Reviewed-by: Vikash Garodia Reviewed-by: Gustavo A. R. Silva Reviewed-by: Dikshita Agarwal Reviewed-by: Bryan O'Donoghue Acked-by: Vikash Garodia Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20240710230914.3156277-1-kees@kernel.org Signed-off-by: Kees Cook --- drivers/media/platform/qcom/venus/hfi_cmds.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/venus/hfi_cmds.h b/drivers/media/platform/qcom/venus/hfi_cmds.h index 20acd412ee7b..42825f07939d 100644 --- a/drivers/media/platform/qcom/venus/hfi_cmds.h +++ b/drivers/media/platform/qcom/venus/hfi_cmds.h @@ -227,7 +227,7 @@ struct hfi_session_release_buffer_pkt { u32 extradata_size; u32 response_req; u32 num_buffers; - u32 buffer_info[1]; + u32 buffer_info[]; }; struct hfi_session_release_resources_pkt { From 32ef4b710cbe1a8901534033872a5bc6b1618bbc Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 10 Jul 2024 16:09:13 -0700 Subject: [PATCH 130/573] media: venus: hfi_cmds: struct hfi_session_release_buffer_pkt: Add __counted_by annotation The only direct user of struct hfi_session_release_buffer_pkt is pkt_session_unset_buffers() which sets "num_buffers" before using it as a loop counter for accessing "buffer_info". Add the __counted_by annotation to reflect the relationship. Reviewed-by: Vikash Garodia Reviewed-by: Bryan O'Donoghue Reviewed-by: Dikshita Agarwal Reviewed-by: Gustavo A. R. Silva Link: https://lore.kernel.org/r/20240710230914.3156277-2-kees@kernel.org Signed-off-by: Kees Cook --- drivers/media/platform/qcom/venus/hfi_cmds.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/venus/hfi_cmds.h b/drivers/media/platform/qcom/venus/hfi_cmds.h index 42825f07939d..1adf2d2ae5f2 100644 --- a/drivers/media/platform/qcom/venus/hfi_cmds.h +++ b/drivers/media/platform/qcom/venus/hfi_cmds.h @@ -227,7 +227,7 @@ struct hfi_session_release_buffer_pkt { u32 extradata_size; u32 response_req; u32 num_buffers; - u32 buffer_info[]; + u32 buffer_info[] __counted_by(num_buffers); }; struct hfi_session_release_resources_pkt { From 559048d156ff3391c4b793779a824c9193e20442 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Mon, 5 Aug 2024 14:43:44 -0700 Subject: [PATCH 131/573] string: Check for "nonstring" attribute on strscpy() arguments GCC already checks for arguments that are marked with the "nonstring"[1] attribute when used on standard C String API functions (e.g. strcpy). Gain this compile-time checking also for the kernel's primary string copying function, strscpy(). Note that Clang has neither "nonstring" nor __builtin_has_attribute(). Link: https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-nonstring-variable-attribute [1] Reviewed-by: Miguel Ojeda Tested-by: Miguel Ojeda Link: https://lore.kernel.org/r/20240805214340.work.339-kees@kernel.org Signed-off-by: Kees Cook --- include/linux/compiler.h | 3 +++ include/linux/compiler_types.h | 7 +++++++ include/linux/string.h | 12 ++++++++---- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/include/linux/compiler.h b/include/linux/compiler.h index 2df665fa2964..ec55bcce4146 100644 --- a/include/linux/compiler.h +++ b/include/linux/compiler.h @@ -242,6 +242,9 @@ static inline void *offset_to_ptr(const int *off) /* &a[0] degrades to a pointer: a different type from an array */ #define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0])) +/* Require C Strings (i.e. NUL-terminated) lack the "nonstring" attribute. */ +#define __must_be_cstr(p) BUILD_BUG_ON_ZERO(__annotated(p, nonstring)) + /* * This returns a constant expression while determining if an argument is * a constant expression, most importantly without evaluating the argument. diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h index f14c275950b5..1a957ea2f4fe 100644 --- a/include/linux/compiler_types.h +++ b/include/linux/compiler_types.h @@ -421,6 +421,13 @@ struct ftrace_likely_data { #define __member_size(p) __builtin_object_size(p, 1) #endif +/* Determine if an attribute has been applied to a variable. */ +#if __has_builtin(__builtin_has_attribute) +#define __annotated(var, attr) __builtin_has_attribute(var, attr) +#else +#define __annotated(var, attr) (false) +#endif + /* * Some versions of gcc do not mark 'asm goto' volatile: * diff --git a/include/linux/string.h b/include/linux/string.h index 9edace076ddb..95b3fc308f4f 100644 --- a/include/linux/string.h +++ b/include/linux/string.h @@ -76,12 +76,16 @@ ssize_t sized_strscpy(char *, const char *, size_t); * known size. */ #define __strscpy0(dst, src, ...) \ - sized_strscpy(dst, src, sizeof(dst) + __must_be_array(dst)) -#define __strscpy1(dst, src, size) sized_strscpy(dst, src, size) + sized_strscpy(dst, src, sizeof(dst) + __must_be_array(dst) + \ + __must_be_cstr(dst) + __must_be_cstr(src)) +#define __strscpy1(dst, src, size) \ + sized_strscpy(dst, src, size + __must_be_cstr(dst) + __must_be_cstr(src)) #define __strscpy_pad0(dst, src, ...) \ - sized_strscpy_pad(dst, src, sizeof(dst) + __must_be_array(dst)) -#define __strscpy_pad1(dst, src, size) sized_strscpy_pad(dst, src, size) + sized_strscpy_pad(dst, src, sizeof(dst) + __must_be_array(dst) + \ + __must_be_cstr(dst) + __must_be_cstr(src)) +#define __strscpy_pad1(dst, src, size) \ + sized_strscpy_pad(dst, src, size + __must_be_cstr(dst) + __must_be_cstr(src)) /** * strscpy - Copy a C-string into a sized buffer From 014e756247e847cde8a06fc27ee3a72a5140b972 Mon Sep 17 00:00:00 2001 From: Suravee Suthikulpanit Date: Fri, 16 Aug 2024 22:16:50 +0000 Subject: [PATCH 132/573] iommu/amd: Update PASID, GATS, GLX, SNPAVICSUP feature related macros Clean up and reorder them according to the bit index. There is no functional change. Suggested-by: Jason Gunthorpe Signed-off-by: Suravee Suthikulpanit Reviewed-by: Vasant Hegde Reviewed-by: Jason Gunthorpe Link: https://lore.kernel.org/r/20240816221650.62295-1-suravee.suthikulpanit@amd.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu.h | 5 ----- drivers/iommu/amd/amd_iommu_types.h | 16 ++++++---------- drivers/iommu/amd/init.c | 8 +++----- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h index 2d5945c982bd..52e18b5f99fd 100644 --- a/drivers/iommu/amd/amd_iommu.h +++ b/drivers/iommu/amd/amd_iommu.h @@ -121,11 +121,6 @@ static inline bool check_feature2(u64 mask) return (amd_iommu_efr2 & mask); } -static inline int check_feature_gpt_level(void) -{ - return ((amd_iommu_efr >> FEATURE_GATS_SHIFT) & FEATURE_GATS_MASK); -} - static inline bool amd_iommu_gt_ppr_supported(void) { return (check_feature(FEATURE_GT) && diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h index 2b76b5dedc1d..c9f9a598eb82 100644 --- a/drivers/iommu/amd/amd_iommu_types.h +++ b/drivers/iommu/amd/amd_iommu_types.h @@ -8,6 +8,7 @@ #ifndef _ASM_X86_AMD_IOMMU_TYPES_H #define _ASM_X86_AMD_IOMMU_TYPES_H +#include #include #include #include @@ -95,26 +96,21 @@ #define FEATURE_GA BIT_ULL(7) #define FEATURE_HE BIT_ULL(8) #define FEATURE_PC BIT_ULL(9) -#define FEATURE_GATS_SHIFT (12) -#define FEATURE_GATS_MASK (3ULL) +#define FEATURE_GATS GENMASK_ULL(13, 12) +#define FEATURE_GLX GENMASK_ULL(15, 14) #define FEATURE_GAM_VAPIC BIT_ULL(21) +#define FEATURE_PASMAX GENMASK_ULL(36, 32) #define FEATURE_GIOSUP BIT_ULL(48) #define FEATURE_HASUP BIT_ULL(49) #define FEATURE_EPHSUP BIT_ULL(50) #define FEATURE_HDSUP BIT_ULL(52) #define FEATURE_SNP BIT_ULL(63) -#define FEATURE_PASID_SHIFT 32 -#define FEATURE_PASID_MASK (0x1fULL << FEATURE_PASID_SHIFT) - -#define FEATURE_GLXVAL_SHIFT 14 -#define FEATURE_GLXVAL_MASK (0x03ULL << FEATURE_GLXVAL_SHIFT) /* Extended Feature 2 Bits */ -#define FEATURE_SNPAVICSUP_SHIFT 5 -#define FEATURE_SNPAVICSUP_MASK (0x07ULL << FEATURE_SNPAVICSUP_SHIFT) +#define FEATURE_SNPAVICSUP GENMASK_ULL(7, 5) #define FEATURE_SNPAVICSUP_GAM(x) \ - ((x & FEATURE_SNPAVICSUP_MASK) >> FEATURE_SNPAVICSUP_SHIFT == 0x1) + (FIELD_GET(FEATURE_SNPAVICSUP, x) == 0x1) /* Note: * The current driver only support 16-bit PASID. diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c index c89d85b54a1a..6b15ce09e78d 100644 --- a/drivers/iommu/amd/init.c +++ b/drivers/iommu/amd/init.c @@ -2042,14 +2042,12 @@ static int __init iommu_init_pci(struct amd_iommu *iommu) int glxval; u64 pasmax; - pasmax = amd_iommu_efr & FEATURE_PASID_MASK; - pasmax >>= FEATURE_PASID_SHIFT; + pasmax = FIELD_GET(FEATURE_PASMAX, amd_iommu_efr); iommu->iommu.max_pasids = (1 << (pasmax + 1)) - 1; BUG_ON(iommu->iommu.max_pasids & ~PASID_MASK); - glxval = amd_iommu_efr & FEATURE_GLXVAL_MASK; - glxval >>= FEATURE_GLXVAL_SHIFT; + glxval = FIELD_GET(FEATURE_GLX, amd_iommu_efr); if (amd_iommu_max_glx_val == -1) amd_iommu_max_glx_val = glxval; @@ -3088,7 +3086,7 @@ static int __init early_amd_iommu_init(void) /* 5 level guest page table */ if (cpu_feature_enabled(X86_FEATURE_LA57) && - check_feature_gpt_level() == GUEST_PGTABLE_5_LEVEL) + FIELD_GET(FEATURE_GATS, amd_iommu_efr) == GUEST_PGTABLE_5_LEVEL) amd_iommu_gpt_level = PAGE_MODE_5_LEVEL; /* Disable any previously enabled IOMMUs */ From d1b35e6d34e9b46fbf98444dd7aa114c032e9ac0 Mon Sep 17 00:00:00 2001 From: Yu Jiaoliang Date: Fri, 23 Aug 2024 10:40:56 +0800 Subject: [PATCH 133/573] platform/chrome: chromeos_laptop: Use kmemdup_array Let the kememdup_array() take care about multiplication and possible overflows. Signed-off-by: Yu Jiaoliang Link: https://lore.kernel.org/r/20240823024056.3031644-1-yujiaoliang@vivo.com Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/chromeos_laptop.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/platform/chrome/chromeos_laptop.c b/drivers/platform/chrome/chromeos_laptop.c index a2cdbfbaeae6..3ab668764383 100644 --- a/drivers/platform/chrome/chromeos_laptop.c +++ b/drivers/platform/chrome/chromeos_laptop.c @@ -749,10 +749,9 @@ chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop, if (!src->num_i2c_peripherals) return 0; - i2c_peripherals = kmemdup(src->i2c_peripherals, - src->num_i2c_peripherals * - sizeof(*src->i2c_peripherals), - GFP_KERNEL); + i2c_peripherals = kmemdup_array(src->i2c_peripherals, + src->num_i2c_peripherals, + sizeof(*i2c_peripherals), GFP_KERNEL); if (!i2c_peripherals) return -ENOMEM; From 3a8990b8a778219327c5f8ecf10b5d81377b925a Mon Sep 17 00:00:00 2001 From: Marc Gonzalez Date: Tue, 20 Aug 2024 15:27:19 +0200 Subject: [PATCH 134/573] iommu/arm-smmu-qcom: hide last LPASS SMMU context bank from linux On qcom msm8998, writing to the last context bank of lpass_q6_smmu (base address 0x05100000) produces a system freeze & reboot. The hardware/hypervisor reports 13 context banks for the LPASS SMMU on msm8998, but only the first 12 are accessible... Override the number of context banks [ 2.546101] arm-smmu 5100000.iommu: probing hardware configuration... [ 2.552439] arm-smmu 5100000.iommu: SMMUv2 with: [ 2.558945] arm-smmu 5100000.iommu: stage 1 translation [ 2.563627] arm-smmu 5100000.iommu: address translation ops [ 2.568923] arm-smmu 5100000.iommu: non-coherent table walk [ 2.574566] arm-smmu 5100000.iommu: (IDR0.CTTW overridden by FW configuration) [ 2.580220] arm-smmu 5100000.iommu: stream matching with 12 register groups [ 2.587263] arm-smmu 5100000.iommu: 13 context banks (0 stage-2 only) [ 2.614447] arm-smmu 5100000.iommu: Supported page sizes: 0x63315000 [ 2.621358] arm-smmu 5100000.iommu: Stage-1: 36-bit VA -> 36-bit IPA [ 2.627772] arm-smmu 5100000.iommu: preserved 0 boot mappings Specifically, the crashes occur here: qsmmu->bypass_cbndx = smmu->num_context_banks - 1; arm_smmu_cb_write(smmu, qsmmu->bypass_cbndx, ARM_SMMU_CB_SCTLR, 0); and here: arm_smmu_write_context_bank(smmu, i); arm_smmu_cb_write(smmu, i, ARM_SMMU_CB_FSR, ARM_SMMU_CB_FSR_FAULT); It is likely that FW reserves the last context bank for its own use, thus a simple work-around is: DON'T USE IT in Linux. If we decrease the number of context banks, last one will be "hidden". Signed-off-by: Marc Gonzalez Reviewed-by: Caleb Connolly Reviewed-by: Bjorn Andersson Link: https://lore.kernel.org/r/20240820-smmu-v3-1-2f71483b00ec@freebox.fr Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c index 36c6b36ad4ff..b981ff25a983 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c @@ -282,6 +282,13 @@ static int qcom_smmu_cfg_probe(struct arm_smmu_device *smmu) u32 smr; int i; + /* + * MSM8998 LPASS SMMU reports 13 context banks, but accessing + * the last context bank crashes the system. + */ + if (of_device_is_compatible(smmu->dev->of_node, "qcom,msm8998-smmu-v2") && smmu->num_context_banks == 13) + smmu->num_context_banks = 12; + /* * Some platforms support more than the Arm SMMU architected maximum of * 128 stream matching groups. For unknown reasons, the additional From 336c218dd7f0588ed8a7345f367975a00a4f003f Mon Sep 17 00:00:00 2001 From: Mirsad Todorovac Date: Fri, 12 Jul 2024 01:43:20 +0200 Subject: [PATCH 135/573] mtd: slram: insert break after errors in parsing the map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GCC 12.3.0 compiler on linux-next next-20240709 tree found the execution path in which, due to lazy evaluation, devlength isn't initialised with the parsed string: 289 while (map) { 290 devname = devstart = devlength = NULL; 291 292 if (!(devname = strsep(&map, ","))) { 293 E("slram: No devicename specified.\n"); 294 break; 295 } 296 T("slram: devname = %s\n", devname); 297 if ((!map) || (!(devstart = strsep(&map, ",")))) { 298 E("slram: No devicestart specified.\n"); 299 } 300 T("slram: devstart = %s\n", devstart); → 301 if ((!map) || (!(devlength = strsep(&map, ",")))) { 302 E("slram: No devicelength / -end specified.\n"); 303 } → 304 T("slram: devlength = %s\n", devlength); 305 if (parse_cmdline(devname, devstart, devlength) != 0) { 306 return(-EINVAL); 307 } Parsing should be finished after map == NULL, so a break is best inserted after each E("slram: ... \n") error message. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: Miquel Raynal Cc: Richard Weinberger Cc: Vignesh Raghavendra Cc: linux-mtd@lists.infradead.org Signed-off-by: Mirsad Todorovac Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240711234319.637824-1-mtodorovac69@gmail.com --- drivers/mtd/devices/slram.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mtd/devices/slram.c b/drivers/mtd/devices/slram.c index 28131a127d06..8297b366a066 100644 --- a/drivers/mtd/devices/slram.c +++ b/drivers/mtd/devices/slram.c @@ -296,10 +296,12 @@ static int __init init_slram(void) T("slram: devname = %s\n", devname); if ((!map) || (!(devstart = strsep(&map, ",")))) { E("slram: No devicestart specified.\n"); + break; } T("slram: devstart = %s\n", devstart); if ((!map) || (!(devlength = strsep(&map, ",")))) { E("slram: No devicelength / -end specified.\n"); + break; } T("slram: devlength = %s\n", devlength); if (parse_cmdline(devname, devstart, devlength) != 0) { From ea265e483eb3d9750c7cfc642dedd5be31dc22c2 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Wed, 31 Jul 2024 13:13:00 -0600 Subject: [PATCH 136/573] mtd: Use of_property_read_bool() Use of_property_read_bool() to read boolean properties rather than of_get_property(). This is part of a larger effort to remove callers of of_get_property() and similar functions. of_get_property() leaks the DT property data pointer which is a problem for dynamically allocated nodes which may be freed. Signed-off-by: Rob Herring (Arm) Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240731191312.1710417-22-robh@kernel.org --- drivers/mtd/parsers/ofpart_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/parsers/ofpart_core.c b/drivers/mtd/parsers/ofpart_core.c index e7b8e9d0a910..abfa68798918 100644 --- a/drivers/mtd/parsers/ofpart_core.c +++ b/drivers/mtd/parsers/ofpart_core.c @@ -157,10 +157,10 @@ static int parse_fixed_partitions(struct mtd_info *master, partname = of_get_property(pp, "name", &len); parts[i].name = partname; - if (of_get_property(pp, "read-only", &len)) + if (of_property_read_bool(pp, "read-only")) parts[i].mask_flags |= MTD_WRITEABLE; - if (of_get_property(pp, "lock", &len)) + if (of_property_read_bool(pp, "lock")) parts[i].mask_flags |= MTD_POWERUP_LOCK; if (of_property_read_bool(pp, "slc-mode")) From e334c01df28225139cc831e4871c590a7851b4fc Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 22 Aug 2024 17:46:32 +0100 Subject: [PATCH 137/573] mtd: parsers: bcm47xxpart: make read-only array possible_nvram_sizes static const Don't populate the read-only array possible_nvram_sizes on the stack at run time, instead make it static const. Signed-off-by: Colin Ian King Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240822164632.638171-1-colin.i.king@gmail.com --- drivers/mtd/parsers/bcm47xxpart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/parsers/bcm47xxpart.c b/drivers/mtd/parsers/bcm47xxpart.c index 13daf9bffd08..49c8e7f27f21 100644 --- a/drivers/mtd/parsers/bcm47xxpart.c +++ b/drivers/mtd/parsers/bcm47xxpart.c @@ -95,7 +95,7 @@ static int bcm47xxpart_parse(struct mtd_info *master, uint32_t blocksize = master->erasesize; int trx_parts[2]; /* Array with indexes of TRX partitions */ int trx_num = 0; /* Number of found TRX partitions */ - int possible_nvram_sizes[] = { 0x8000, 0xF000, 0x10000, }; + static const int possible_nvram_sizes[] = { 0x8000, 0xF000, 0x10000, }; int err; /* From 175086cf4acdf4ccd3d7a6bb5c5231ececd6656b Mon Sep 17 00:00:00 2001 From: Yan Zhen Date: Fri, 23 Aug 2024 19:08:24 +0800 Subject: [PATCH 138/573] mtd: concat: Use kmemdup_array instead of kmemdup for multiple allocation When we are allocating an array, using kmemdup_array() to take care about multiplication and possible overflows. Also it makes auditing the code easier. Signed-off-by: Yan Zhen Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240823110824.3895787-1-yanzhen@vivo.com --- drivers/mtd/mtdconcat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index 193428de6a4b..f56f44aa8625 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c @@ -204,7 +204,7 @@ concat_writev(struct mtd_info *mtd, const struct kvec *vecs, } /* make a copy of vecs */ - vecs_copy = kmemdup(vecs, sizeof(struct kvec) * count, GFP_KERNEL); + vecs_copy = kmemdup_array(vecs, count, sizeof(struct kvec), GFP_KERNEL); if (!vecs_copy) return -ENOMEM; From e2a9fcb36e851adb5b25c4acea53a290fd48a636 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 5 Aug 2024 19:51:02 +0200 Subject: [PATCH 139/573] mtd: spinand: winbond: add support for W25N01KV Add support for Winbond W25N01KV 1Gbit SPI-NAND. It has 4-bit on-die ECC. Signed-off-by: Robert Marko Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240805175125.6658-1-robimarko@gmail.com --- drivers/mtd/nand/spi/winbond.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c index ba7c813b9542..f3bb81d7e460 100644 --- a/drivers/mtd/nand/spi/winbond.c +++ b/drivers/mtd/nand/spi/winbond.c @@ -76,6 +76,18 @@ static int w25m02gv_select_target(struct spinand_device *spinand, return spi_mem_exec_op(spinand->spimem, &op); } +static int w25n01kv_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = 64 + (8 * section); + region->length = 7; + + return 0; +} + static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section, struct mtd_oob_region *region) { @@ -100,6 +112,11 @@ static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section, return 0; } +static const struct mtd_ooblayout_ops w25n01kv_ooblayout = { + .ecc = w25n01kv_ooblayout_ecc, + .free = w25n02kv_ooblayout_free, +}; + static const struct mtd_ooblayout_ops w25n02kv_ooblayout = { .ecc = w25n02kv_ooblayout_ecc, .free = w25n02kv_ooblayout_free, @@ -163,6 +180,15 @@ static const struct spinand_info winbond_spinand_table[] = { &update_cache_variants), 0, SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)), + SPINAND_INFO("W25N01KV", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xae, 0x21), + NAND_MEMORG(1, 2048, 96, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&w25n01kv_ooblayout, w25n02kv_ecc_get_status)), SPINAND_INFO("W25N02KV", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x22), NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), From 1824520e7477bedf76bd08c32261c755e6405cd9 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Mon, 12 Aug 2024 02:56:41 +0100 Subject: [PATCH 140/573] mtd: spinand: set bitflip_threshold to 75% of ECC strength Reporting an unclean read from SPI-NAND only when the maximum number of correctable bitflip errors has been hit seems a bit late. UBI LEB scrubbing, which depends on the lower MTD device reporting correctable bitflips, then only kicks in when it's almost too late. Set bitflip_threshold to 75% of the ECC strength, which is also the default for raw NAND. Signed-off-by: Daniel Golle Reviewed-by: Frieder Schrempf Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/2117e387260b0a96f95b8e1652ff79e0e2d71d53.1723427450.git.daniel@makrotopia.org --- drivers/mtd/nand/spi/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index e0b6715e5dfe..018c854d0619 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -1287,6 +1287,7 @@ static int spinand_init(struct spinand_device *spinand) /* Propagate ECC information to mtd_info */ mtd->ecc_strength = nanddev_get_ecc_conf(nand)->strength; mtd->ecc_step_size = nanddev_get_ecc_conf(nand)->step_size; + mtd->bitflip_threshold = DIV_ROUND_UP(mtd->ecc_strength * 3, 4); ret = spinand_create_dirmaps(spinand); if (ret) { From ccce71013406a0a3c81850dab940f07b112349d3 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 14 Aug 2024 14:21:18 +0200 Subject: [PATCH 141/573] mtd: rawnand: davinci: make platform_data private There are no longer any users of the platform data for davinci rawnand in board files. We can remove the public pdata headers and move the structures that are still used into the driver compilation unit while removing the rest. Signed-off-by: Bartosz Golaszewski Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240814122120.13975-1-brgl@bgdev.pl --- drivers/mtd/nand/raw/davinci_nand.c | 70 +++++++++++++-- .../linux/platform_data/mtd-davinci-aemif.h | 36 -------- include/linux/platform_data/mtd-davinci.h | 88 ------------------- 3 files changed, 65 insertions(+), 129 deletions(-) delete mode 100644 include/linux/platform_data/mtd-davinci-aemif.h delete mode 100644 include/linux/platform_data/mtd-davinci.h diff --git a/drivers/mtd/nand/raw/davinci_nand.c b/drivers/mtd/nand/raw/davinci_nand.c index 051deea768db..392678143a36 100644 --- a/drivers/mtd/nand/raw/davinci_nand.c +++ b/drivers/mtd/nand/raw/davinci_nand.c @@ -20,8 +20,71 @@ #include #include -#include -#include +#define NRCSR_OFFSET 0x00 +#define NANDFCR_OFFSET 0x60 +#define NANDFSR_OFFSET 0x64 +#define NANDF1ECC_OFFSET 0x70 + +/* 4-bit ECC syndrome registers */ +#define NAND_4BIT_ECC_LOAD_OFFSET 0xbc +#define NAND_4BIT_ECC1_OFFSET 0xc0 +#define NAND_4BIT_ECC2_OFFSET 0xc4 +#define NAND_4BIT_ECC3_OFFSET 0xc8 +#define NAND_4BIT_ECC4_OFFSET 0xcc +#define NAND_ERR_ADD1_OFFSET 0xd0 +#define NAND_ERR_ADD2_OFFSET 0xd4 +#define NAND_ERR_ERRVAL1_OFFSET 0xd8 +#define NAND_ERR_ERRVAL2_OFFSET 0xdc + +/* NOTE: boards don't need to use these address bits + * for ALE/CLE unless they support booting from NAND. + * They're used unless platform data overrides them. + */ +#define MASK_ALE 0x08 +#define MASK_CLE 0x10 + +struct davinci_nand_pdata { + uint32_t mask_ale; + uint32_t mask_cle; + + /* + * 0-indexed chip-select number of the asynchronous + * interface to which the NAND device has been connected. + * + * So, if you have NAND connected to CS3 of DA850, you + * will pass '1' here. Since the asynchronous interface + * on DA850 starts from CS2. + */ + uint32_t core_chipsel; + + /* for packages using two chipselects */ + uint32_t mask_chipsel; + + /* board's default static partition info */ + struct mtd_partition *parts; + unsigned int nr_parts; + + /* none == NAND_ECC_ENGINE_TYPE_NONE (strongly *not* advised!!) + * soft == NAND_ECC_ENGINE_TYPE_SOFT + * else == NAND_ECC_ENGINE_TYPE_ON_HOST, according to ecc_bits + * + * All DaVinci-family chips support 1-bit hardware ECC. + * Newer ones also support 4-bit ECC, but are awkward + * using it with large page chips. + */ + enum nand_ecc_engine_type engine_type; + enum nand_ecc_placement ecc_placement; + u8 ecc_bits; + + /* e.g. NAND_BUSWIDTH_16 */ + unsigned int options; + /* e.g. NAND_BBT_USE_FLASH */ + unsigned int bbt_options; + + /* Main and mirror bbt descriptor overrides */ + struct nand_bbt_descr *bbt_td; + struct nand_bbt_descr *bbt_md; +}; /* * This is a device driver for the NAND flash controller found on the @@ -54,8 +117,6 @@ struct davinci_nand_info { uint32_t mask_cle; uint32_t core_chipsel; - - struct davinci_aemif_timing *timing; }; static DEFINE_SPINLOCK(davinci_nand_lock); @@ -775,7 +836,6 @@ static int nand_davinci_probe(struct platform_device *pdev) info->chip.options = pdata->options; info->chip.bbt_td = pdata->bbt_td; info->chip.bbt_md = pdata->bbt_md; - info->timing = pdata->timing; info->current_cs = info->vaddr; info->core_chipsel = pdata->core_chipsel; diff --git a/include/linux/platform_data/mtd-davinci-aemif.h b/include/linux/platform_data/mtd-davinci-aemif.h deleted file mode 100644 index a49826214a39..000000000000 --- a/include/linux/platform_data/mtd-davinci-aemif.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * TI DaVinci AEMIF support - * - * Copyright 2010 (C) Texas Instruments, Inc. https://www.ti.com/ - * - * This file is licensed under the terms of the GNU General Public License - * version 2. This program is licensed "as is" without any warranty of any - * kind, whether express or implied. - */ -#ifndef _MACH_DAVINCI_AEMIF_H -#define _MACH_DAVINCI_AEMIF_H - -#include - -#define NRCSR_OFFSET 0x00 -#define AWCCR_OFFSET 0x04 -#define A1CR_OFFSET 0x10 - -#define ACR_ASIZE_MASK 0x3 -#define ACR_EW_MASK BIT(30) -#define ACR_SS_MASK BIT(31) - -/* All timings in nanoseconds */ -struct davinci_aemif_timing { - u8 wsetup; - u8 wstrobe; - u8 whold; - - u8 rsetup; - u8 rstrobe; - u8 rhold; - - u8 ta; -}; - -#endif diff --git a/include/linux/platform_data/mtd-davinci.h b/include/linux/platform_data/mtd-davinci.h deleted file mode 100644 index dd474dd44848..000000000000 --- a/include/linux/platform_data/mtd-davinci.h +++ /dev/null @@ -1,88 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * mach-davinci/nand.h - * - * Copyright © 2006 Texas Instruments. - * - * Ported to 2.6.23 Copyright © 2008 by - * Sander Huijsen - * Troy Kisky - * Dirk Behme - * - * -------------------------------------------------------------------------- - */ - -#ifndef __ARCH_ARM_DAVINCI_NAND_H -#define __ARCH_ARM_DAVINCI_NAND_H - -#include - -#define NANDFCR_OFFSET 0x60 -#define NANDFSR_OFFSET 0x64 -#define NANDF1ECC_OFFSET 0x70 - -/* 4-bit ECC syndrome registers */ -#define NAND_4BIT_ECC_LOAD_OFFSET 0xbc -#define NAND_4BIT_ECC1_OFFSET 0xc0 -#define NAND_4BIT_ECC2_OFFSET 0xc4 -#define NAND_4BIT_ECC3_OFFSET 0xc8 -#define NAND_4BIT_ECC4_OFFSET 0xcc -#define NAND_ERR_ADD1_OFFSET 0xd0 -#define NAND_ERR_ADD2_OFFSET 0xd4 -#define NAND_ERR_ERRVAL1_OFFSET 0xd8 -#define NAND_ERR_ERRVAL2_OFFSET 0xdc - -/* NOTE: boards don't need to use these address bits - * for ALE/CLE unless they support booting from NAND. - * They're used unless platform data overrides them. - */ -#define MASK_ALE 0x08 -#define MASK_CLE 0x10 - -struct davinci_nand_pdata { /* platform_data */ - uint32_t mask_ale; - uint32_t mask_cle; - - /* - * 0-indexed chip-select number of the asynchronous - * interface to which the NAND device has been connected. - * - * So, if you have NAND connected to CS3 of DA850, you - * will pass '1' here. Since the asynchronous interface - * on DA850 starts from CS2. - */ - uint32_t core_chipsel; - - /* for packages using two chipselects */ - uint32_t mask_chipsel; - - /* board's default static partition info */ - struct mtd_partition *parts; - unsigned nr_parts; - - /* none == NAND_ECC_ENGINE_TYPE_NONE (strongly *not* advised!!) - * soft == NAND_ECC_ENGINE_TYPE_SOFT - * else == NAND_ECC_ENGINE_TYPE_ON_HOST, according to ecc_bits - * - * All DaVinci-family chips support 1-bit hardware ECC. - * Newer ones also support 4-bit ECC, but are awkward - * using it with large page chips. - */ - enum nand_ecc_engine_type engine_type; - enum nand_ecc_placement ecc_placement; - u8 ecc_bits; - - /* e.g. NAND_BUSWIDTH_16 */ - unsigned options; - /* e.g. NAND_BBT_USE_FLASH */ - unsigned bbt_options; - - /* Main and mirror bbt descriptor overrides */ - struct nand_bbt_descr *bbt_td; - struct nand_bbt_descr *bbt_md; - - /* Access timings */ - struct davinci_aemif_timing *timing; -}; - -#endif /* __ARCH_ARM_DAVINCI_NAND_H */ From db93caa6a4cf467c90ec11c438f776fc37050c00 Mon Sep 17 00:00:00 2001 From: Shan-Chun Hung Date: Tue, 16 Jul 2024 08:45:26 +0800 Subject: [PATCH 142/573] dt-bindings: mmc: nuvoton,ma35d1-sdhci: Document MA35D1 SDHCI controller Add binding for Nuvoton MA35D1 SDHCI controller. Signed-off-by: Shan-Chun Hung Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240716004527.20378-2-shanchun1218@gmail.com Signed-off-by: Ulf Hansson --- .../bindings/mmc/nuvoton,ma35d1-sdhci.yaml | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 Documentation/devicetree/bindings/mmc/nuvoton,ma35d1-sdhci.yaml diff --git a/Documentation/devicetree/bindings/mmc/nuvoton,ma35d1-sdhci.yaml b/Documentation/devicetree/bindings/mmc/nuvoton,ma35d1-sdhci.yaml new file mode 100644 index 000000000000..4d787147c300 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/nuvoton,ma35d1-sdhci.yaml @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mmc/nuvoton,ma35d1-sdhci.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton MA35D1 SD/SDIO/MMC Controller + +maintainers: + - Shan-Chun Hung + +allOf: + - $ref: sdhci-common.yaml# + +properties: + compatible: + enum: + - nuvoton,ma35d1-sdhci + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + pinctrl-names: + minItems: 1 + items: + - const: default + - const: state_uhs + + pinctrl-0: + description: + Should contain default/high speed pin ctrl. + maxItems: 1 + + pinctrl-1: + description: + Should contain uhs mode pin ctrl. + maxItems: 1 + + resets: + maxItems: 1 + + nuvoton,sys: + $ref: /schemas/types.yaml#/definitions/phandle + description: phandle to access GCR (Global Control Register) registers. + +required: + - compatible + - reg + - interrupts + - clocks + - pinctrl-names + - pinctrl-0 + - resets + - nuvoton,sys + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + mmc@40190000 { + compatible = "nuvoton,ma35d1-sdhci"; + reg = <0x0 0x40190000 0x0 0x2000>; + interrupts = ; + clocks = <&clk SDH1_GATE>; + pinctrl-names = "default", "state_uhs"; + pinctrl-0 = <&pinctrl_sdhci1>; + pinctrl-1 = <&pinctrl_sdhci1_uhs>; + resets = <&sys MA35D1_RESET_SDH1>; + nuvoton,sys = <&sys>; + vqmmc-supply = <&sdhci1_vqmmc_regulator>; + bus-width = <8>; + max-frequency = <200000000>; + }; + }; From addc9ecb9ddb3fe77be3979cce021ad07442c1fc Mon Sep 17 00:00:00 2001 From: Shan-Chun Hung Date: Tue, 16 Jul 2024 08:45:27 +0800 Subject: [PATCH 143/573] mmc: sdhci-of-ma35d1: Add Nuvoton MA35D1 SDHCI driver Add the SDHCI driver for the MA35D1 platform. It is based upon the SDHCI interface, but requires some extra initialization. Signed-off-by: Shan-Chun Hung Acked-by: Adrian Hunter Link: https://lore.kernel.org/r/20240716004527.20378-3-shanchun1218@gmail.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/Kconfig | 12 ++ drivers/mmc/host/Makefile | 1 + drivers/mmc/host/sdhci-of-ma35d1.c | 314 +++++++++++++++++++++++++++++ 3 files changed, 327 insertions(+) create mode 100644 drivers/mmc/host/sdhci-of-ma35d1.c diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index eb3ecfe05591..7199cb0bd0b9 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -252,6 +252,18 @@ config MMC_SDHCI_OF_SPARX5 If unsure, say N. +config MMC_SDHCI_OF_MA35D1 + tristate "SDHCI OF support for the MA35D1 SDHCI controller" + depends on ARCH_MA35 || COMPILE_TEST + depends on MMC_SDHCI_PLTFM + help + This selects the MA35D1 Secure Digital Host Controller Interface. + The controller supports SD/MMC/SDIO devices. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + config MMC_SDHCI_CADENCE tristate "SDHCI support for the Cadence SD/SDIO/eMMC controller" depends on MMC_SDHCI_PLTFM diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index f53f86d200ac..3ccffebbe59b 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -88,6 +88,7 @@ obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o obj-$(CONFIG_MMC_SDHCI_OF_DWCMSHC) += sdhci-of-dwcmshc.o obj-$(CONFIG_MMC_SDHCI_OF_SPARX5) += sdhci-of-sparx5.o +obj-$(CONFIG_MMC_SDHCI_OF_MA35D1) += sdhci-of-ma35d1.o obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o obj-$(CONFIG_MMC_SDHCI_IPROC) += sdhci-iproc.o obj-$(CONFIG_MMC_SDHCI_NPCM) += sdhci-npcm.o diff --git a/drivers/mmc/host/sdhci-of-ma35d1.c b/drivers/mmc/host/sdhci-of-ma35d1.c new file mode 100644 index 000000000000..b84c2927bd4a --- /dev/null +++ b/drivers/mmc/host/sdhci-of-ma35d1.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2024 Nuvoton Technology Corp. + * + * Author: Shan-Chun Hung + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sdhci-pltfm.h" +#include "sdhci.h" + +#define MA35_SYS_MISCFCR0 0x070 +#define MA35_SDHCI_MSHCCTL 0x508 +#define MA35_SDHCI_MBIUCTL 0x510 + +#define MA35_SDHCI_CMD_CONFLICT_CHK BIT(0) +#define MA35_SDHCI_INCR_MSK GENMASK(3, 0) +#define MA35_SDHCI_INCR16 BIT(3) +#define MA35_SDHCI_INCR8 BIT(2) + +struct ma35_priv { + struct reset_control *rst; + struct pinctrl *pinctrl; + struct pinctrl_state *pins_uhs; + struct pinctrl_state *pins_default; +}; + +struct ma35_restore_data { + u32 reg; + u32 width; +}; + +static const struct ma35_restore_data restore_data[] = { + { SDHCI_CLOCK_CONTROL, sizeof(u32)}, + { SDHCI_BLOCK_SIZE, sizeof(u32)}, + { SDHCI_INT_ENABLE, sizeof(u32)}, + { SDHCI_SIGNAL_ENABLE, sizeof(u32)}, + { SDHCI_AUTO_CMD_STATUS, sizeof(u32)}, + { SDHCI_HOST_CONTROL, sizeof(u32)}, + { SDHCI_TIMEOUT_CONTROL, sizeof(u8) }, + { MA35_SDHCI_MSHCCTL, sizeof(u16)}, + { MA35_SDHCI_MBIUCTL, sizeof(u16)}, +}; + +/* + * If DMA addr spans 128MB boundary, we split the DMA transfer into two + * so that each DMA transfer doesn't exceed the boundary. + */ +static void ma35_adma_write_desc(struct sdhci_host *host, void **desc, dma_addr_t addr, int len, + unsigned int cmd) +{ + int tmplen, offset; + + if (likely(!len || (ALIGN(addr, SZ_128M) == ALIGN(addr + len - 1, SZ_128M)))) { + sdhci_adma_write_desc(host, desc, addr, len, cmd); + return; + } + + offset = addr & (SZ_128M - 1); + tmplen = SZ_128M - offset; + sdhci_adma_write_desc(host, desc, addr, tmplen, cmd); + + addr += tmplen; + len -= tmplen; + sdhci_adma_write_desc(host, desc, addr, len, cmd); +} + +static void ma35_set_clock(struct sdhci_host *host, unsigned int clock) +{ + u32 ctl; + + /* + * If the clock frequency exceeds MMC_HIGH_52_MAX_DTR, + * disable command conflict check. + */ + ctl = sdhci_readw(host, MA35_SDHCI_MSHCCTL); + if (clock > MMC_HIGH_52_MAX_DTR) + ctl &= ~MA35_SDHCI_CMD_CONFLICT_CHK; + else + ctl |= MA35_SDHCI_CMD_CONFLICT_CHK; + sdhci_writew(host, ctl, MA35_SDHCI_MSHCCTL); + + sdhci_set_clock(host, clock); +} + +static int ma35_start_signal_voltage_switch(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct ma35_priv *priv = sdhci_pltfm_priv(pltfm_host); + + switch (ios->signal_voltage) { + case MMC_SIGNAL_VOLTAGE_180: + if (!IS_ERR(priv->pinctrl) && !IS_ERR(priv->pins_uhs)) + pinctrl_select_state(priv->pinctrl, priv->pins_uhs); + break; + case MMC_SIGNAL_VOLTAGE_330: + if (!IS_ERR(priv->pinctrl) && !IS_ERR(priv->pins_default)) + pinctrl_select_state(priv->pinctrl, priv->pins_default); + break; + default: + dev_err(mmc_dev(host->mmc), "Unsupported signal voltage!\n"); + return -EINVAL; + } + + return sdhci_start_signal_voltage_switch(mmc, ios); +} + +static void ma35_voltage_switch(struct sdhci_host *host) +{ + /* Wait for 5ms after set 1.8V signal enable bit */ + fsleep(5000); +} + +static int ma35_execute_tuning(struct mmc_host *mmc, u32 opcode) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct ma35_priv *priv = sdhci_pltfm_priv(pltfm_host); + int idx; + u32 regs[ARRAY_SIZE(restore_data)] = {}; + + /* + * Limitations require a reset of SD/eMMC before tuning and + * saving the registers before resetting, then restoring + * after the reset. + */ + for (idx = 0; idx < ARRAY_SIZE(restore_data); idx++) { + if (restore_data[idx].width == sizeof(u32)) + regs[idx] = sdhci_readl(host, restore_data[idx].reg); + else if (restore_data[idx].width == sizeof(u16)) + regs[idx] = sdhci_readw(host, restore_data[idx].reg); + else if (restore_data[idx].width == sizeof(u8)) + regs[idx] = sdhci_readb(host, restore_data[idx].reg); + } + + reset_control_assert(priv->rst); + reset_control_deassert(priv->rst); + + for (idx = 0; idx < ARRAY_SIZE(restore_data); idx++) { + if (restore_data[idx].width == sizeof(u32)) + sdhci_writel(host, regs[idx], restore_data[idx].reg); + else if (restore_data[idx].width == sizeof(u16)) + sdhci_writew(host, regs[idx], restore_data[idx].reg); + else if (restore_data[idx].width == sizeof(u8)) + sdhci_writeb(host, regs[idx], restore_data[idx].reg); + } + + return sdhci_execute_tuning(mmc, opcode); +} + +static const struct sdhci_ops sdhci_ma35_ops = { + .set_clock = ma35_set_clock, + .set_bus_width = sdhci_set_bus_width, + .set_uhs_signaling = sdhci_set_uhs_signaling, + .get_max_clock = sdhci_pltfm_clk_get_max_clock, + .reset = sdhci_reset, + .adma_write_desc = ma35_adma_write_desc, + .voltage_switch = ma35_voltage_switch, +}; + +static const struct sdhci_pltfm_data sdhci_ma35_pdata = { + .ops = &sdhci_ma35_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | SDHCI_QUIRK2_BROKEN_DDR50 | + SDHCI_QUIRK2_ACMD23_BROKEN, +}; + +static int ma35_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sdhci_pltfm_host *pltfm_host; + struct sdhci_host *host; + struct ma35_priv *priv; + int err; + u32 extra, ctl; + + host = sdhci_pltfm_init(pdev, &sdhci_ma35_pdata, sizeof(struct ma35_priv)); + if (IS_ERR(host)) + return PTR_ERR(host); + + /* Extra adma table cnt for cross 128M boundary handling. */ + extra = DIV_ROUND_UP_ULL(dma_get_required_mask(dev), SZ_128M); + extra = min(extra, SDHCI_MAX_SEGS); + + host->adma_table_cnt += extra; + pltfm_host = sdhci_priv(host); + priv = sdhci_pltfm_priv(pltfm_host); + + pltfm_host->clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(pltfm_host->clk)) { + err = dev_err_probe(dev, PTR_ERR(pltfm_host->clk), "failed to get clk\n"); + goto err_sdhci; + } + + err = mmc_of_parse(host->mmc); + if (err) + goto err_sdhci; + + priv->rst = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(priv->rst)) { + err = dev_err_probe(dev, PTR_ERR(priv->rst), "failed to get reset control\n"); + goto err_sdhci; + } + + sdhci_get_of_property(pdev); + + priv->pinctrl = devm_pinctrl_get(dev); + if (!IS_ERR(priv->pinctrl)) { + priv->pins_default = pinctrl_lookup_state(priv->pinctrl, "default"); + priv->pins_uhs = pinctrl_lookup_state(priv->pinctrl, "state_uhs"); + pinctrl_select_state(priv->pinctrl, priv->pins_default); + } + + if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V)) { + struct regmap *regmap; + u32 reg; + + regmap = syscon_regmap_lookup_by_phandle(dev_of_node(dev), "nuvoton,sys"); + if (!IS_ERR(regmap)) { + /* Enable SDHCI voltage stable for 1.8V */ + regmap_read(regmap, MA35_SYS_MISCFCR0, ®); + reg |= BIT(17); + regmap_write(regmap, MA35_SYS_MISCFCR0, reg); + } + + host->mmc_host_ops.start_signal_voltage_switch = + ma35_start_signal_voltage_switch; + } + + host->mmc_host_ops.execute_tuning = ma35_execute_tuning; + + err = sdhci_add_host(host); + if (err) + goto err_sdhci; + + /* + * Split data into chunks of 16 or 8 bytes for transmission. + * Each chunk transfer is guaranteed to be uninterrupted on the bus. + * This likely corresponds to the AHB bus DMA burst size. + */ + ctl = sdhci_readw(host, MA35_SDHCI_MBIUCTL); + ctl &= ~MA35_SDHCI_INCR_MSK; + ctl |= MA35_SDHCI_INCR16 | MA35_SDHCI_INCR8; + sdhci_writew(host, ctl, MA35_SDHCI_MBIUCTL); + + return 0; + +err_sdhci: + sdhci_pltfm_free(pdev); + return err; +} + +static void ma35_disable_card_clk(struct sdhci_host *host) +{ + u16 ctrl; + + ctrl = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + if (ctrl & SDHCI_CLOCK_CARD_EN) { + ctrl &= ~SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, ctrl, SDHCI_CLOCK_CONTROL); + } +} + +static void ma35_remove(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + + sdhci_remove_host(host, 0); + ma35_disable_card_clk(host); + sdhci_pltfm_free(pdev); +} + +static const struct of_device_id sdhci_ma35_dt_ids[] = { + { .compatible = "nuvoton,ma35d1-sdhci" }, + {} +}; + +static struct platform_driver sdhci_ma35_driver = { + .driver = { + .name = "sdhci-ma35", + .of_match_table = sdhci_ma35_dt_ids, + }, + .probe = ma35_probe, + .remove_new = ma35_remove, +}; +module_platform_driver(sdhci_ma35_driver); + +MODULE_DESCRIPTION("SDHCI platform driver for Nuvoton MA35"); +MODULE_AUTHOR("Shan-Chun Hung "); +MODULE_LICENSE("GPL"); From 3af5a1e7be84e0a5d6635dbedb13bd1ce4161414 Mon Sep 17 00:00:00 2001 From: Doug Brown Date: Sun, 14 Jul 2024 08:55:11 -0700 Subject: [PATCH 144/573] mmc: sdhci-pxav2: Remove unnecessary null pointer check There is no need to check for a null mrq->cmd in pxav1_request_done. mmc_request_done already assumes it's not null, and it's always called in this path by every SDHCI driver. This was caught by Smatch. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/9ddaef2a-05bb-4fe7-98c5-da40a0813027@stanley.mountain/ Signed-off-by: Doug Brown Acked-by: Adrian Hunter Link: https://lore.kernel.org/r/20240714155510.48880-1-doug@schmorgal.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-pxav2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-pxav2.c b/drivers/mmc/host/sdhci-pxav2.c index b75cbea88b40..7b957f6d5588 100644 --- a/drivers/mmc/host/sdhci-pxav2.c +++ b/drivers/mmc/host/sdhci-pxav2.c @@ -126,7 +126,7 @@ static void pxav1_request_done(struct sdhci_host *host, struct mmc_request *mrq) struct sdhci_pxav2_host *pxav2_host; /* If this is an SDIO command, perform errata workaround for silicon bug */ - if (mrq->cmd && !mrq->cmd->error && + if (!mrq->cmd->error && (mrq->cmd->opcode == SD_IO_RW_DIRECT || mrq->cmd->opcode == SD_IO_RW_EXTENDED)) { /* Reset data port */ From 24a9ea1c0fdc2cab2dda08e3370880b7d9a3359e Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Wed, 24 Jul 2024 19:21:17 +0100 Subject: [PATCH 145/573] dt-bindings: mmc: renesas,sdhi: Document RZ/V2H(P) support The SD/MMC block on the RZ/V2H(P) ("R9A09G057") SoC is similar to that of the R-Car Gen3, but it has some differences: - HS400 is not supported. - It has additional SD_STATUS register to control voltage, power enable and reset. - It supports fixed address mode. To accommodate these differences, a SoC-specific 'renesas,sdhi-r9a09g057' compatible string is added. Signed-off-by: Lad Prabhakar Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20240724182119.652080-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Ulf Hansson --- Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml b/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml index 3d0e61e59856..1155b1d79df5 100644 --- a/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml +++ b/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml @@ -18,6 +18,7 @@ properties: - renesas,sdhi-r7s9210 # SH-Mobile AG5 - renesas,sdhi-r8a73a4 # R-Mobile APE6 - renesas,sdhi-r8a7740 # R-Mobile A1 + - renesas,sdhi-r9a09g057 # RZ/V2H(P) - renesas,sdhi-sh73a0 # R-Mobile APE6 - items: - enum: @@ -66,6 +67,7 @@ properties: - renesas,sdhi-r9a07g054 # RZ/V2L - renesas,sdhi-r9a08g045 # RZ/G3S - renesas,sdhi-r9a09g011 # RZ/V2M + - renesas,sdhi-r9a09g057 # RZ/V2H(P) - const: renesas,rzg2l-sdhi reg: From a091f510af0b55617b6abf1989837fb8fe86257a Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Wed, 24 Jul 2024 19:21:18 +0100 Subject: [PATCH 146/573] mmc: tmio: Use MMC core APIs to control the vqmmc regulator Use the mmc_regulator_enable_vqmmc() and mmc_regulator_disable_vqmmc() APIs to enable/disable the vqmmc regulator. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Reviewed-by: Geert Uytterhoeven Tested-by: Claudiu Beznea # on RZ/G3S Link: https://lore.kernel.org/r/20240724182119.652080-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/tmio_mmc_core.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/tmio_mmc_core.c b/drivers/mmc/host/tmio_mmc_core.c index b359876cc33d..45a474ccab1c 100644 --- a/drivers/mmc/host/tmio_mmc_core.c +++ b/drivers/mmc/host/tmio_mmc_core.c @@ -895,8 +895,8 @@ static void tmio_mmc_power_on(struct tmio_mmc_host *host, unsigned short vdd) * It seems, VccQ should be switched on after Vcc, this is also what the * omap_hsmmc.c driver does. */ - if (!IS_ERR(mmc->supply.vqmmc) && !ret) { - ret = regulator_enable(mmc->supply.vqmmc); + if (!ret) { + ret = mmc_regulator_enable_vqmmc(mmc); usleep_range(200, 300); } @@ -909,8 +909,7 @@ static void tmio_mmc_power_off(struct tmio_mmc_host *host) { struct mmc_host *mmc = host->mmc; - if (!IS_ERR(mmc->supply.vqmmc)) - regulator_disable(mmc->supply.vqmmc); + mmc_regulator_disable_vqmmc(mmc); if (!IS_ERR(mmc->supply.vmmc)) mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0); From b6db8f1fb415afd4f7773668c8576ad591ed32eb Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Wed, 24 Jul 2024 19:21:19 +0100 Subject: [PATCH 147/573] mmc: renesas_sdhi: Add RZ/V2H(P) compatible string The SD/MMC block on the RZ/V2H(P) ("R9A09G057") SoC is similar to that of the R-Car Gen3, but it has some differences: - HS400 is not supported. - It has additional SD_STATUS register to control voltage, power enable and reset. - It supports fixed address mode. To accommodate these differences, a SoC-specific 'renesas,sdhi-r9a09g057' compatible string is added. Note for RZ/V2H(P), we are using the `of_rzg2l_compatible` OF data as it already handles no HS400 and fixed address mode support. Since the SDxIOVS and SDxPWEN pins can always be used as GPIO pins on the RZ/V2H(P) SoC, no driver changes are done to control the SD_STATUS register. Signed-off-by: Lad Prabhakar Link: https://lore.kernel.org/r/20240724182119.652080-4-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/renesas_sdhi_internal_dmac.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/renesas_sdhi_internal_dmac.c b/drivers/mmc/host/renesas_sdhi_internal_dmac.c index caf1d2e23343..1dcaa050f264 100644 --- a/drivers/mmc/host/renesas_sdhi_internal_dmac.c +++ b/drivers/mmc/host/renesas_sdhi_internal_dmac.c @@ -285,6 +285,7 @@ static const struct of_device_id renesas_sdhi_internal_dmac_of_match[] = { { .compatible = "renesas,sdhi-r8a77990", .data = &of_r8a77990_compatible, }, { .compatible = "renesas,sdhi-r8a77995", .data = &of_rcar_gen3_nohs400_compatible, }, { .compatible = "renesas,sdhi-r9a09g011", .data = &of_rzg2l_compatible, }, + { .compatible = "renesas,sdhi-r9a09g057", .data = &of_rzg2l_compatible, }, { .compatible = "renesas,rzg2l-sdhi", .data = &of_rzg2l_compatible, }, { .compatible = "renesas,rcar-gen3-sdhi", .data = &of_rcar_gen3_compatible, }, { .compatible = "renesas,rcar-gen4-sdhi", .data = &of_rcar_gen3_compatible, }, From 977849b2d46da3ac3e57e46791f2be3eb48df0b6 Mon Sep 17 00:00:00 2001 From: Chen Wang Date: Mon, 5 Aug 2024 17:17:21 +0800 Subject: [PATCH 148/573] mmc: sdhci-of-dwcmshc: add common bulk optional clocks support In addition to the required core clock and optional bus clock, the soc will expand its own clocks, so the bulk clock mechanism is abstracted. Note, I call the bulk clocks as "other clocks" due to the bus clock has been called as "optional". Signed-off-by: Chen Wang Tested-by: Drew Fustini # TH1520 Tested-by: Inochi Amaoto # Duo and Huashan Pi Acked-by: Adrian Hunter Link: https://lore.kernel.org/r/e57e8c51da81f176b49608269a884f840903e78e.1722847198.git.unicorn_wang@outlook.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-dwcmshc.c | 90 +++++++++++++++-------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index e79aa4b3b6c3..35401616fb2e 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -108,7 +108,6 @@ #define DLL_LOCK_WO_TMOUT(x) \ ((((x) & DWCMSHC_EMMC_DLL_LOCKED) == DWCMSHC_EMMC_DLL_LOCKED) && \ (((x) & DWCMSHC_EMMC_DLL_TIMEOUT) == 0)) -#define RK35xx_MAX_CLKS 3 /* PHY register area pointer */ #define DWC_MSHC_PTR_PHY_R 0x300 @@ -199,23 +198,54 @@ enum dwcmshc_rk_type { }; struct rk35xx_priv { - /* Rockchip specified optional clocks */ - struct clk_bulk_data rockchip_clks[RK35xx_MAX_CLKS]; struct reset_control *reset; enum dwcmshc_rk_type devtype; u8 txclk_tapnum; }; +#define DWCMSHC_MAX_OTHER_CLKS 3 + struct dwcmshc_priv { struct clk *bus_clk; int vendor_specific_area1; /* P_VENDOR_SPECIFIC_AREA1 reg */ int vendor_specific_area2; /* P_VENDOR_SPECIFIC_AREA2 reg */ + int num_other_clks; + struct clk_bulk_data other_clks[DWCMSHC_MAX_OTHER_CLKS]; + void *priv; /* pointer to SoC private stuff */ u16 delay_line; u16 flags; }; +static int dwcmshc_get_enable_other_clks(struct device *dev, + struct dwcmshc_priv *priv, + int num_clks, + const char * const clk_ids[]) +{ + int err; + + if (num_clks > DWCMSHC_MAX_OTHER_CLKS) + return -EINVAL; + + for (int i = 0; i < num_clks; i++) + priv->other_clks[i].id = clk_ids[i]; + + err = devm_clk_bulk_get_optional(dev, num_clks, priv->other_clks); + if (err) { + dev_err(dev, "failed to get clocks %d\n", err); + return err; + } + + err = clk_bulk_prepare_enable(num_clks, priv->other_clks); + if (err) + dev_err(dev, "failed to enable clocks %d\n", err); + + priv->num_other_clks = num_clks; + + return err; +} + /* * If DMA addr spans 128MB boundary, we split the DMA transfer into two * so that each DMA transfer doesn't exceed the boundary. @@ -1036,8 +1066,9 @@ dsbl_cqe_caps: static int dwcmshc_rk35xx_init(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv) { - int err; + static const char * const clk_ids[] = {"axi", "block", "timer"}; struct rk35xx_priv *priv = dwc_priv->priv; + int err; priv->reset = devm_reset_control_array_get_optional_exclusive(mmc_dev(host->mmc)); if (IS_ERR(priv->reset)) { @@ -1046,21 +1077,10 @@ static int dwcmshc_rk35xx_init(struct sdhci_host *host, struct dwcmshc_priv *dwc return err; } - priv->rockchip_clks[0].id = "axi"; - priv->rockchip_clks[1].id = "block"; - priv->rockchip_clks[2].id = "timer"; - err = devm_clk_bulk_get_optional(mmc_dev(host->mmc), RK35xx_MAX_CLKS, - priv->rockchip_clks); - if (err) { - dev_err(mmc_dev(host->mmc), "failed to get clocks %d\n", err); + err = dwcmshc_get_enable_other_clks(mmc_dev(host->mmc), dwc_priv, + ARRAY_SIZE(clk_ids), clk_ids); + if (err) return err; - } - - err = clk_bulk_prepare_enable(RK35xx_MAX_CLKS, priv->rockchip_clks); - if (err) { - dev_err(mmc_dev(host->mmc), "failed to enable clocks %d\n", err); - return err; - } if (of_property_read_u8(mmc_dev(host->mmc)->of_node, "rockchip,txclk-tapnum", &priv->txclk_tapnum)) @@ -1280,9 +1300,7 @@ err_rpm: err_clk: clk_disable_unprepare(pltfm_host->clk); clk_disable_unprepare(priv->bus_clk); - if (rk_priv) - clk_bulk_disable_unprepare(RK35xx_MAX_CLKS, - rk_priv->rockchip_clks); + clk_bulk_disable_unprepare(priv->num_other_clks, priv->other_clks); free_pltfm: sdhci_pltfm_free(pdev); return err; @@ -1304,7 +1322,6 @@ static void dwcmshc_remove(struct platform_device *pdev) struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host); - struct rk35xx_priv *rk_priv = priv->priv; pm_runtime_get_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); @@ -1316,9 +1333,7 @@ static void dwcmshc_remove(struct platform_device *pdev) clk_disable_unprepare(pltfm_host->clk); clk_disable_unprepare(priv->bus_clk); - if (rk_priv) - clk_bulk_disable_unprepare(RK35xx_MAX_CLKS, - rk_priv->rockchip_clks); + clk_bulk_disable_unprepare(priv->num_other_clks, priv->other_clks); sdhci_pltfm_free(pdev); } @@ -1328,7 +1343,6 @@ static int dwcmshc_suspend(struct device *dev) struct sdhci_host *host = dev_get_drvdata(dev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host); - struct rk35xx_priv *rk_priv = priv->priv; int ret; pm_runtime_resume(dev); @@ -1347,9 +1361,7 @@ static int dwcmshc_suspend(struct device *dev) if (!IS_ERR(priv->bus_clk)) clk_disable_unprepare(priv->bus_clk); - if (rk_priv) - clk_bulk_disable_unprepare(RK35xx_MAX_CLKS, - rk_priv->rockchip_clks); + clk_bulk_disable_unprepare(priv->num_other_clks, priv->other_clks); return ret; } @@ -1359,7 +1371,6 @@ static int dwcmshc_resume(struct device *dev) struct sdhci_host *host = dev_get_drvdata(dev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host); - struct rk35xx_priv *rk_priv = priv->priv; int ret; ret = clk_prepare_enable(pltfm_host->clk); @@ -1372,29 +1383,24 @@ static int dwcmshc_resume(struct device *dev) goto disable_clk; } - if (rk_priv) { - ret = clk_bulk_prepare_enable(RK35xx_MAX_CLKS, - rk_priv->rockchip_clks); - if (ret) - goto disable_bus_clk; - } + ret = clk_bulk_prepare_enable(priv->num_other_clks, priv->other_clks); + if (ret) + goto disable_bus_clk; ret = sdhci_resume_host(host); if (ret) - goto disable_rockchip_clks; + goto disable_other_clks; if (host->mmc->caps2 & MMC_CAP2_CQE) { ret = cqhci_resume(host->mmc); if (ret) - goto disable_rockchip_clks; + goto disable_other_clks; } return 0; -disable_rockchip_clks: - if (rk_priv) - clk_bulk_disable_unprepare(RK35xx_MAX_CLKS, - rk_priv->rockchip_clks); +disable_other_clks: + clk_bulk_disable_unprepare(priv->num_other_clks, priv->other_clks); disable_bus_clk: if (!IS_ERR(priv->bus_clk)) clk_disable_unprepare(priv->bus_clk); From 2b857745498fdb341b684aa399b05dfffec35501 Mon Sep 17 00:00:00 2001 From: Chen Wang Date: Mon, 5 Aug 2024 17:17:40 +0800 Subject: [PATCH 149/573] mmc: sdhci-of-dwcmshc: move two rk35xx functions This patch just move dwcmshc_rk35xx_init() and dwcmshc_rk35xx_postinit() to put the functions of rk35xx together as much as possible. This change is an intermediate process before further modification. Signed-off-by: Chen Wang Tested-by: Drew Fustini # TH1520 Tested-by: Inochi Amaoto # Duo and Huashan Pi Acked-by: Adrian Hunter Link: https://lore.kernel.org/r/54204702d5febd3e867eb3544c36919fe4140a88.1722847198.git.unicorn_wang@outlook.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-dwcmshc.c | 90 ++++++++++++++--------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index 35401616fb2e..a002636d51fd 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -711,6 +711,51 @@ static void rk35xx_sdhci_reset(struct sdhci_host *host, u8 mask) sdhci_reset(host, mask); } +static int dwcmshc_rk35xx_init(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv) +{ + static const char * const clk_ids[] = {"axi", "block", "timer"}; + struct rk35xx_priv *priv = dwc_priv->priv; + int err; + + priv->reset = devm_reset_control_array_get_optional_exclusive(mmc_dev(host->mmc)); + if (IS_ERR(priv->reset)) { + err = PTR_ERR(priv->reset); + dev_err(mmc_dev(host->mmc), "failed to get reset control %d\n", err); + return err; + } + + err = dwcmshc_get_enable_other_clks(mmc_dev(host->mmc), dwc_priv, + ARRAY_SIZE(clk_ids), clk_ids); + if (err) + return err; + + if (of_property_read_u8(mmc_dev(host->mmc)->of_node, "rockchip,txclk-tapnum", + &priv->txclk_tapnum)) + priv->txclk_tapnum = DLL_TXCLK_TAPNUM_DEFAULT; + + /* Disable cmd conflict check */ + sdhci_writel(host, 0x0, dwc_priv->vendor_specific_area1 + DWCMSHC_HOST_CTRL3); + /* Reset previous settings */ + sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_TXCLK); + sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_STRBIN); + + return 0; +} + +static void dwcmshc_rk35xx_postinit(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv) +{ + /* + * Don't support highspeed bus mode with low clk speed as we + * cannot use DLL for this condition. + */ + if (host->mmc->f_max <= 52000000) { + dev_info(mmc_dev(host->mmc), "Disabling HS200/HS400, frequency too low (%d)\n", + host->mmc->f_max); + host->mmc->caps2 &= ~(MMC_CAP2_HS200 | MMC_CAP2_HS400); + host->mmc->caps &= ~(MMC_CAP_3_3V_DDR | MMC_CAP_1_8V_DDR); + } +} + static int th1520_execute_tuning(struct sdhci_host *host, u32 opcode) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -1064,51 +1109,6 @@ dsbl_cqe_caps: host->mmc->caps2 &= ~(MMC_CAP2_CQE | MMC_CAP2_CQE_DCMD); } -static int dwcmshc_rk35xx_init(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv) -{ - static const char * const clk_ids[] = {"axi", "block", "timer"}; - struct rk35xx_priv *priv = dwc_priv->priv; - int err; - - priv->reset = devm_reset_control_array_get_optional_exclusive(mmc_dev(host->mmc)); - if (IS_ERR(priv->reset)) { - err = PTR_ERR(priv->reset); - dev_err(mmc_dev(host->mmc), "failed to get reset control %d\n", err); - return err; - } - - err = dwcmshc_get_enable_other_clks(mmc_dev(host->mmc), dwc_priv, - ARRAY_SIZE(clk_ids), clk_ids); - if (err) - return err; - - if (of_property_read_u8(mmc_dev(host->mmc)->of_node, "rockchip,txclk-tapnum", - &priv->txclk_tapnum)) - priv->txclk_tapnum = DLL_TXCLK_TAPNUM_DEFAULT; - - /* Disable cmd conflict check */ - sdhci_writel(host, 0x0, dwc_priv->vendor_specific_area1 + DWCMSHC_HOST_CTRL3); - /* Reset previous settings */ - sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_TXCLK); - sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_STRBIN); - - return 0; -} - -static void dwcmshc_rk35xx_postinit(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv) -{ - /* - * Don't support highspeed bus mode with low clk speed as we - * cannot use DLL for this condition. - */ - if (host->mmc->f_max <= 52000000) { - dev_info(mmc_dev(host->mmc), "Disabling HS200/HS400, frequency too low (%d)\n", - host->mmc->f_max); - host->mmc->caps2 &= ~(MMC_CAP2_HS200 | MMC_CAP2_HS400); - host->mmc->caps &= ~(MMC_CAP_3_3V_DDR | MMC_CAP_1_8V_DDR); - } -} - static const struct of_device_id sdhci_dwcmshc_dt_ids[] = { { .compatible = "rockchip,rk3588-dwcmshc", From 76610189b281cad900dafb9320161168e5761c14 Mon Sep 17 00:00:00 2001 From: Chen Wang Date: Mon, 5 Aug 2024 17:17:59 +0800 Subject: [PATCH 150/573] mmc: sdhci-of-dwcmshc: factor out code for th1520_init() Different socs have initialization operations in the probe process, which are summarized as functions. This patch first factor out init function for th1520. Signed-off-by: Chen Wang Reviewed-by: Drew Fustini Tested-by: Drew Fustini # TH1520 Tested-by: Inochi Amaoto # Duo and Huashan Pi Acked-by: Adrian Hunter Link: https://lore.kernel.org/r/23c6a81052a6dd3660d60348731229d60a209b32.1722847198.git.unicorn_wang@outlook.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-dwcmshc.c | 51 +++++++++++++++++------------ 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index a002636d51fd..b272ec2ab232 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -830,6 +830,35 @@ static void th1520_sdhci_reset(struct sdhci_host *host, u8 mask) } } +static int th1520_init(struct device *dev, + struct sdhci_host *host, + struct dwcmshc_priv *dwc_priv) +{ + dwc_priv->delay_line = PHY_SDCLKDL_DC_DEFAULT; + + if (device_property_read_bool(dev, "mmc-ddr-1_8v") || + device_property_read_bool(dev, "mmc-hs200-1_8v") || + device_property_read_bool(dev, "mmc-hs400-1_8v")) + dwc_priv->flags |= FLAG_IO_FIXED_1V8; + else + dwc_priv->flags &= ~FLAG_IO_FIXED_1V8; + + /* + * start_signal_voltage_switch() will try 3.3V first + * then 1.8V. Use SDHCI_SIGNALING_180 rather than + * SDHCI_SIGNALING_330 to avoid setting voltage to 3.3V + * in sdhci_start_signal_voltage_switch(). + */ + if (dwc_priv->flags & FLAG_IO_FIXED_1V8) { + host->flags &= ~SDHCI_SIGNALING_330; + host->flags |= SDHCI_SIGNALING_180; + } + + sdhci_enable_v4_mode(host); + + return 0; +} + static void cv18xx_sdhci_reset(struct sdhci_host *host, u8 mask) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -1231,27 +1260,7 @@ static int dwcmshc_probe(struct platform_device *pdev) } if (pltfm_data == &sdhci_dwcmshc_th1520_pdata) { - priv->delay_line = PHY_SDCLKDL_DC_DEFAULT; - - if (device_property_read_bool(dev, "mmc-ddr-1_8v") || - device_property_read_bool(dev, "mmc-hs200-1_8v") || - device_property_read_bool(dev, "mmc-hs400-1_8v")) - priv->flags |= FLAG_IO_FIXED_1V8; - else - priv->flags &= ~FLAG_IO_FIXED_1V8; - - /* - * start_signal_voltage_switch() will try 3.3V first - * then 1.8V. Use SDHCI_SIGNALING_180 rather than - * SDHCI_SIGNALING_330 to avoid setting voltage to 3.3V - * in sdhci_start_signal_voltage_switch(). - */ - if (priv->flags & FLAG_IO_FIXED_1V8) { - host->flags &= ~SDHCI_SIGNALING_330; - host->flags |= SDHCI_SIGNALING_180; - } - - sdhci_enable_v4_mode(host); + th1520_init(dev, host, priv); } #ifdef CONFIG_ACPI From 9676a7ef2cf5feb757fb08a712d0f06dcb824d24 Mon Sep 17 00:00:00 2001 From: Chen Wang Date: Mon, 5 Aug 2024 17:18:19 +0800 Subject: [PATCH 151/573] mmc: sdhci-of-dwcmshc: factor out code into dwcmshc_rk35xx_init Continue factor out code fron probe into dwcmshc_rk35xx_init. Signed-off-by: Chen Wang Tested-by: Drew Fustini # TH1520 Tested-by: Inochi Amaoto # Duo and Huashan Pi Acked-by: Adrian Hunter Link: https://lore.kernel.org/r/4f1f2fa403ce7f0b4d79afb7d7e8a1690cde5d6c.1722847198.git.unicorn_wang@outlook.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-dwcmshc.c | 34 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index b272ec2ab232..55fba5ef37ba 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -711,12 +711,22 @@ static void rk35xx_sdhci_reset(struct sdhci_host *host, u8 mask) sdhci_reset(host, mask); } -static int dwcmshc_rk35xx_init(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv) +static int dwcmshc_rk35xx_init(struct device *dev, struct sdhci_host *host, + struct dwcmshc_priv *dwc_priv) { static const char * const clk_ids[] = {"axi", "block", "timer"}; - struct rk35xx_priv *priv = dwc_priv->priv; + struct rk35xx_priv *priv; int err; + priv = devm_kzalloc(dev, sizeof(struct rk35xx_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (of_device_is_compatible(dev->of_node, "rockchip,rk3588-dwcmshc")) + priv->devtype = DWCMSHC_RK3588; + else + priv->devtype = DWCMSHC_RK3568; + priv->reset = devm_reset_control_array_get_optional_exclusive(mmc_dev(host->mmc)); if (IS_ERR(priv->reset)) { err = PTR_ERR(priv->reset); @@ -739,6 +749,8 @@ static int dwcmshc_rk35xx_init(struct sdhci_host *host, struct dwcmshc_priv *dwc sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_TXCLK); sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_STRBIN); + dwc_priv->priv = priv; + return 0; } @@ -1184,7 +1196,6 @@ static int dwcmshc_probe(struct platform_device *pdev) struct sdhci_pltfm_host *pltfm_host; struct sdhci_host *host; struct dwcmshc_priv *priv; - struct rk35xx_priv *rk_priv = NULL; const struct sdhci_pltfm_data *pltfm_data; int err; u32 extra, caps; @@ -1241,20 +1252,7 @@ static int dwcmshc_probe(struct platform_device *pdev) host->mmc_host_ops.execute_tuning = dwcmshc_execute_tuning; if (pltfm_data == &sdhci_dwcmshc_rk35xx_pdata) { - rk_priv = devm_kzalloc(&pdev->dev, sizeof(struct rk35xx_priv), GFP_KERNEL); - if (!rk_priv) { - err = -ENOMEM; - goto err_clk; - } - - if (of_device_is_compatible(pdev->dev.of_node, "rockchip,rk3588-dwcmshc")) - rk_priv->devtype = DWCMSHC_RK3588; - else - rk_priv->devtype = DWCMSHC_RK3568; - - priv->priv = rk_priv; - - err = dwcmshc_rk35xx_init(host, priv); + err = dwcmshc_rk35xx_init(dev, host, priv); if (err) goto err_clk; } @@ -1290,7 +1288,7 @@ static int dwcmshc_probe(struct platform_device *pdev) dwcmshc_cqhci_init(host, pdev); } - if (rk_priv) + if (pltfm_data == &sdhci_dwcmshc_rk35xx_pdata) dwcmshc_rk35xx_postinit(host, priv); err = __sdhci_add_host(host); From a2e34ac156a0ff7bc0b22ea21a92b411636b7b50 Mon Sep 17 00:00:00 2001 From: Chen Wang Date: Mon, 5 Aug 2024 17:18:43 +0800 Subject: [PATCH 152/573] mmc: sdhci-of-dwcmshc: add dwcmshc_pltfm_data Abstract dwcmshc_pltfm_data to hold the sdhci_pltfm_data plus some comoon operations of soc such as init/postinit. Signed-off-by: Chen Wang Tested-by: Drew Fustini # TH1520 Tested-by: Inochi Amaoto # Duo and Huashan Pi Acked-by: Adrian Hunter Link: https://lore.kernel.org/r/cb2c68c594286e9588c53acb76163e60c140c02b.1722847198.git.unicorn_wang@outlook.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-dwcmshc.c | 81 +++++++++++++++++------------ 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index 55fba5ef37ba..16f420994519 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -218,6 +218,12 @@ struct dwcmshc_priv { u16 flags; }; +struct dwcmshc_pltfm_data { + const struct sdhci_pltfm_data pdata; + int (*init)(struct device *dev, struct sdhci_host *host, struct dwcmshc_priv *dwc_priv); + void (*postinit)(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv); +}; + static int dwcmshc_get_enable_other_clks(struct device *dev, struct dwcmshc_priv *priv, int num_clks, @@ -1048,39 +1054,52 @@ static const struct sdhci_ops sdhci_dwcmshc_cv18xx_ops = { .platform_execute_tuning = cv18xx_sdhci_execute_tuning, }; -static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = { - .ops = &sdhci_dwcmshc_ops, - .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, - .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, +static const struct dwcmshc_pltfm_data sdhci_dwcmshc_pdata = { + .pdata = { + .ops = &sdhci_dwcmshc_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, + }, }; #ifdef CONFIG_ACPI -static const struct sdhci_pltfm_data sdhci_dwcmshc_bf3_pdata = { - .ops = &sdhci_dwcmshc_ops, - .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, - .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | - SDHCI_QUIRK2_ACMD23_BROKEN, +static const struct dwcmshc_pltfm_data sdhci_dwcmshc_bf3_pdata = { + .pdata = { + .ops = &sdhci_dwcmshc_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | + SDHCI_QUIRK2_ACMD23_BROKEN, + }, }; #endif -static const struct sdhci_pltfm_data sdhci_dwcmshc_rk35xx_pdata = { - .ops = &sdhci_dwcmshc_rk35xx_ops, - .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | - SDHCI_QUIRK_BROKEN_TIMEOUT_VAL, - .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | - SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN, +static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rk35xx_pdata = { + .pdata = { + .ops = &sdhci_dwcmshc_rk35xx_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | + SDHCI_QUIRK_BROKEN_TIMEOUT_VAL, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | + SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN, + }, + .init = dwcmshc_rk35xx_init, + .postinit = dwcmshc_rk35xx_postinit, }; -static const struct sdhci_pltfm_data sdhci_dwcmshc_th1520_pdata = { - .ops = &sdhci_dwcmshc_th1520_ops, - .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, - .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, +static const struct dwcmshc_pltfm_data sdhci_dwcmshc_th1520_pdata = { + .pdata = { + .ops = &sdhci_dwcmshc_th1520_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, + }, + .init = th1520_init, }; -static const struct sdhci_pltfm_data sdhci_dwcmshc_cv18xx_pdata = { - .ops = &sdhci_dwcmshc_cv18xx_ops, - .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, - .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, +static const struct dwcmshc_pltfm_data sdhci_dwcmshc_cv18xx_pdata = { + .pdata = { + .ops = &sdhci_dwcmshc_cv18xx_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, + }, }; static const struct cqhci_host_ops dwcmshc_cqhci_ops = { @@ -1196,7 +1215,7 @@ static int dwcmshc_probe(struct platform_device *pdev) struct sdhci_pltfm_host *pltfm_host; struct sdhci_host *host; struct dwcmshc_priv *priv; - const struct sdhci_pltfm_data *pltfm_data; + const struct dwcmshc_pltfm_data *pltfm_data; int err; u32 extra, caps; @@ -1206,7 +1225,7 @@ static int dwcmshc_probe(struct platform_device *pdev) return -ENODEV; } - host = sdhci_pltfm_init(pdev, pltfm_data, + host = sdhci_pltfm_init(pdev, &pltfm_data->pdata, sizeof(struct dwcmshc_priv)); if (IS_ERR(host)) return PTR_ERR(host); @@ -1251,16 +1270,12 @@ static int dwcmshc_probe(struct platform_device *pdev) host->mmc_host_ops.hs400_enhanced_strobe = dwcmshc_hs400_enhanced_strobe; host->mmc_host_ops.execute_tuning = dwcmshc_execute_tuning; - if (pltfm_data == &sdhci_dwcmshc_rk35xx_pdata) { - err = dwcmshc_rk35xx_init(dev, host, priv); + if (pltfm_data->init) { + err = pltfm_data->init(&pdev->dev, host, priv); if (err) goto err_clk; } - if (pltfm_data == &sdhci_dwcmshc_th1520_pdata) { - th1520_init(dev, host, priv); - } - #ifdef CONFIG_ACPI if (pltfm_data == &sdhci_dwcmshc_bf3_pdata) sdhci_enable_v4_mode(host); @@ -1288,8 +1303,8 @@ static int dwcmshc_probe(struct platform_device *pdev) dwcmshc_cqhci_init(host, pdev); } - if (pltfm_data == &sdhci_dwcmshc_rk35xx_pdata) - dwcmshc_rk35xx_postinit(host, priv); + if (pltfm_data->postinit) + pltfm_data->postinit(host, priv); err = __sdhci_add_host(host); if (err) From fc7b91683edbba4eab6c454f89e41aa56ea614e2 Mon Sep 17 00:00:00 2001 From: Chen Wang Date: Mon, 5 Aug 2024 17:19:04 +0800 Subject: [PATCH 153/573] dt-bindings: mmc: sdhci-of-dwcmhsc: Add Sophgo SG2042 support SG2042 use Synopsys dwcnshc IP for SD/eMMC controllers. SG2042 defines 3 clocks for SD/eMMC controllers. - EMMC_100M/SD_100M for cclk(Card clocks in DWC_mshc), so reuse existing "core". - AXI_EMMC/AXI_SD for aclk/hclk(Bus interface clocks in DWC_mshc) and blck(Core Base Clock in DWC_mshc), these 3 clocks share one source, so reuse existing "bus". - 100K_EMMC/100K_SD for cqetmclk(Timer clocks in DWC_mshc), so reuse existing "timer" which was added for rockchip specified. Signed-off-by: Chen Wang Reviewed-by: Conor Dooley Link: https://lore.kernel.org/r/9ca450097e5389a38bcd7d8ddf863766df4cea10.1722847198.git.unicorn_wang@outlook.com Signed-off-by: Ulf Hansson --- .../bindings/mmc/snps,dwcmshc-sdhci.yaml | 60 +++++++++++++------ 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml index 4d3031d9965f..80d50178d2e3 100644 --- a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml +++ b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml @@ -10,9 +10,6 @@ maintainers: - Ulf Hansson - Jisheng Zhang -allOf: - - $ref: mmc-controller.yaml# - properties: compatible: enum: @@ -21,6 +18,7 @@ properties: - snps,dwcmshc-sdhci - sophgo,cv1800b-dwcmshc - sophgo,sg2002-dwcmshc + - sophgo,sg2042-dwcmshc - thead,th1520-dwcmshc reg: @@ -31,22 +29,11 @@ properties: clocks: minItems: 1 - items: - - description: core clock - - description: bus clock for optional - - description: axi clock for rockchip specified - - description: block clock for rockchip specified - - description: timer clock for rockchip specified - + maxItems: 5 clock-names: minItems: 1 - items: - - const: core - - const: bus - - const: axi - - const: block - - const: timer + maxItems: 5 resets: maxItems: 5 @@ -63,7 +50,6 @@ properties: description: Specify the number of delay for tx sampling. $ref: /schemas/types.yaml#/definitions/uint8 - required: - compatible - reg @@ -71,6 +57,46 @@ required: - clocks - clock-names +allOf: + - $ref: mmc-controller.yaml# + + - if: + properties: + compatible: + contains: + const: sophgo,sg2042-dwcmshc + + then: + properties: + clocks: + items: + - description: core clock + - description: bus clock + - description: timer clock + clock-names: + items: + - const: core + - const: bus + - const: timer + else: + properties: + clocks: + minItems: 1 + items: + - description: core clock + - description: bus clock for optional + - description: axi clock for rockchip specified + - description: block clock for rockchip specified + - description: timer clock for rockchip specified + clock-names: + minItems: 1 + items: + - const: core + - const: bus + - const: axi + - const: block + - const: timer + unevaluatedProperties: false examples: From 7af1a8f0965158b608baf11c53882f10a57376fd Mon Sep 17 00:00:00 2001 From: Chen Wang Date: Mon, 5 Aug 2024 17:19:24 +0800 Subject: [PATCH 154/573] mmc: sdhci-of-dwcmshc: Add support for Sophgo SG2042 Add support for the mmc controller of Sophgo SG2042. SG2042 uses Synopsys PHY the same as TH1520 so we reuse the tuning logic from TH1520. Besides this, this patch implement some SG2042 specific work, such as clocks and reset ops. Signed-off-by: Chen Wang Acked-by: Adrian Hunter Link: https://lore.kernel.org/r/eb21847528a6487af54bb80f1ce94adff289cdb0.1722847198.git.unicorn_wang@outlook.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-dwcmshc.c | 125 ++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index 16f420994519..ba8960d8b2d4 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -113,12 +113,15 @@ #define DWC_MSHC_PTR_PHY_R 0x300 /* PHY general configuration */ -#define PHY_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x00) -#define PHY_CNFG_RSTN_DEASSERT 0x1 /* Deassert PHY reset */ -#define PHY_CNFG_PAD_SP_MASK GENMASK(19, 16) /* bits [19:16] */ -#define PHY_CNFG_PAD_SP 0x0c /* PMOS TX drive strength */ -#define PHY_CNFG_PAD_SN_MASK GENMASK(23, 20) /* bits [23:20] */ -#define PHY_CNFG_PAD_SN 0x0c /* NMOS TX drive strength */ +#define PHY_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x00) +#define PHY_CNFG_RSTN_DEASSERT 0x1 /* Deassert PHY reset */ +#define PHY_CNFG_PHY_PWRGOOD_MASK BIT_MASK(1) /* bit [1] */ +#define PHY_CNFG_PAD_SP_MASK GENMASK(19, 16) /* bits [19:16] */ +#define PHY_CNFG_PAD_SP 0x0c /* PMOS TX drive strength */ +#define PHY_CNFG_PAD_SP_SG2042 0x09 /* PMOS TX drive strength for SG2042 */ +#define PHY_CNFG_PAD_SN_MASK GENMASK(23, 20) /* bits [23:20] */ +#define PHY_CNFG_PAD_SN 0x0c /* NMOS TX drive strength */ +#define PHY_CNFG_PAD_SN_SG2042 0x08 /* NMOS TX drive strength for SG2042 */ /* PHY command/response pad settings */ #define PHY_CMDPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x04) @@ -147,10 +150,12 @@ #define PHY_PAD_TXSLEW_CTRL_P 0x3 /* Slew control for P-Type pad TX */ #define PHY_PAD_TXSLEW_CTRL_N_MASK GENMASK(12, 9) /* bits [12:9] */ #define PHY_PAD_TXSLEW_CTRL_N 0x3 /* Slew control for N-Type pad TX */ +#define PHY_PAD_TXSLEW_CTRL_N_SG2042 0x2 /* Slew control for N-Type pad TX for SG2042 */ /* PHY CLK delay line settings */ #define PHY_SDCLKDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1d) -#define PHY_SDCLKDL_CNFG_UPDATE BIT(4) /* set before writing to SDCLKDL_DC */ +#define PHY_SDCLKDL_CNFG_EXTDLY_EN BIT(0) +#define PHY_SDCLKDL_CNFG_UPDATE BIT(4) /* set before writing to SDCLKDL_DC */ /* PHY CLK delay line delay code */ #define PHY_SDCLKDL_DC_R (DWC_MSHC_PTR_PHY_R + 0x1e) @@ -158,10 +163,14 @@ #define PHY_SDCLKDL_DC_DEFAULT 0x32 /* default delay code */ #define PHY_SDCLKDL_DC_HS400 0x18 /* delay code for HS400 mode */ +#define PHY_SMPLDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x20) +#define PHY_SMPLDL_CNFG_BYPASS_EN BIT(1) + /* PHY drift_cclk_rx delay line configuration setting */ #define PHY_ATDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x21) #define PHY_ATDL_CNFG_INPSEL_MASK GENMASK(3, 2) /* bits [3:2] */ #define PHY_ATDL_CNFG_INPSEL 0x3 /* delay line input source */ +#define PHY_ATDL_CNFG_INPSEL_SG2042 0x2 /* delay line input source for SG2042 */ /* PHY DLL control settings */ #define PHY_DLL_CTRL_R (DWC_MSHC_PTR_PHY_R + 0x24) @@ -1013,6 +1022,85 @@ static int cv18xx_sdhci_execute_tuning(struct sdhci_host *host, u32 opcode) return ret; } +static inline void sg2042_sdhci_phy_init(struct sdhci_host *host) +{ + u32 val; + + /* Asset phy reset & set tx drive strength */ + val = sdhci_readl(host, PHY_CNFG_R); + val &= ~PHY_CNFG_RSTN_DEASSERT; + val |= FIELD_PREP(PHY_CNFG_PHY_PWRGOOD_MASK, 1); + val |= FIELD_PREP(PHY_CNFG_PAD_SP_MASK, PHY_CNFG_PAD_SP_SG2042); + val |= FIELD_PREP(PHY_CNFG_PAD_SN_MASK, PHY_CNFG_PAD_SN_SG2042); + sdhci_writel(host, val, PHY_CNFG_R); + + /* Configure phy pads */ + val = PHY_PAD_RXSEL_3V3; + val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, PHY_PAD_WEAKPULL_PULLUP); + val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P); + val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N_SG2042); + sdhci_writew(host, val, PHY_CMDPAD_CNFG_R); + sdhci_writew(host, val, PHY_DATAPAD_CNFG_R); + sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R); + + val = PHY_PAD_RXSEL_3V3; + val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P); + val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N_SG2042); + sdhci_writew(host, val, PHY_CLKPAD_CNFG_R); + + val = PHY_PAD_RXSEL_3V3; + val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, PHY_PAD_WEAKPULL_PULLDOWN); + val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P); + val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N_SG2042); + sdhci_writew(host, val, PHY_STBPAD_CNFG_R); + + /* Configure delay line */ + /* Enable fixed delay */ + sdhci_writeb(host, PHY_SDCLKDL_CNFG_EXTDLY_EN, PHY_SDCLKDL_CNFG_R); + /* + * Set delay line. + * Its recommended that bit UPDATE_DC[4] is 1 when SDCLKDL_DC is being written. + * Ensure UPDATE_DC[4] is '0' when not updating code. + */ + val = sdhci_readb(host, PHY_SDCLKDL_CNFG_R); + val |= PHY_SDCLKDL_CNFG_UPDATE; + sdhci_writeb(host, val, PHY_SDCLKDL_CNFG_R); + /* Add 10 * 70ps = 0.7ns for output delay */ + sdhci_writeb(host, 10, PHY_SDCLKDL_DC_R); + val = sdhci_readb(host, PHY_SDCLKDL_CNFG_R); + val &= ~(PHY_SDCLKDL_CNFG_UPDATE); + sdhci_writeb(host, val, PHY_SDCLKDL_CNFG_R); + + /* Set SMPLDL_CNFG, Bypass */ + sdhci_writeb(host, PHY_SMPLDL_CNFG_BYPASS_EN, PHY_SMPLDL_CNFG_R); + + /* Set ATDL_CNFG, tuning clk not use for init */ + val = FIELD_PREP(PHY_ATDL_CNFG_INPSEL_MASK, PHY_ATDL_CNFG_INPSEL_SG2042); + sdhci_writeb(host, val, PHY_ATDL_CNFG_R); + + /* Deasset phy reset */ + val = sdhci_readl(host, PHY_CNFG_R); + val |= PHY_CNFG_RSTN_DEASSERT; + sdhci_writel(host, val, PHY_CNFG_R); +} + +static void sg2042_sdhci_reset(struct sdhci_host *host, u8 mask) +{ + sdhci_reset(host, mask); + + if (mask & SDHCI_RESET_ALL) + sg2042_sdhci_phy_init(host); +} + +static int sg2042_init(struct device *dev, struct sdhci_host *host, + struct dwcmshc_priv *dwc_priv) +{ + static const char * const clk_ids[] = {"timer"}; + + return dwcmshc_get_enable_other_clks(mmc_dev(host->mmc), dwc_priv, + ARRAY_SIZE(clk_ids), clk_ids); +} + static const struct sdhci_ops sdhci_dwcmshc_ops = { .set_clock = sdhci_set_clock, .set_bus_width = sdhci_set_bus_width, @@ -1054,6 +1142,16 @@ static const struct sdhci_ops sdhci_dwcmshc_cv18xx_ops = { .platform_execute_tuning = cv18xx_sdhci_execute_tuning, }; +static const struct sdhci_ops sdhci_dwcmshc_sg2042_ops = { + .set_clock = sdhci_set_clock, + .set_bus_width = sdhci_set_bus_width, + .set_uhs_signaling = dwcmshc_set_uhs_signaling, + .get_max_clock = dwcmshc_get_max_clock, + .reset = sg2042_sdhci_reset, + .adma_write_desc = dwcmshc_adma_write_desc, + .platform_execute_tuning = th1520_execute_tuning, +}; + static const struct dwcmshc_pltfm_data sdhci_dwcmshc_pdata = { .pdata = { .ops = &sdhci_dwcmshc_ops, @@ -1102,6 +1200,15 @@ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_cv18xx_pdata = { }, }; +static const struct dwcmshc_pltfm_data sdhci_dwcmshc_sg2042_pdata = { + .pdata = { + .ops = &sdhci_dwcmshc_sg2042_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, + }, + .init = sg2042_init, +}; + static const struct cqhci_host_ops dwcmshc_cqhci_ops = { .enable = dwcmshc_sdhci_cqe_enable, .disable = sdhci_cqe_disable, @@ -1194,6 +1301,10 @@ static const struct of_device_id sdhci_dwcmshc_dt_ids[] = { .compatible = "thead,th1520-dwcmshc", .data = &sdhci_dwcmshc_th1520_pdata, }, + { + .compatible = "sophgo,sg2042-dwcmshc", + .data = &sdhci_dwcmshc_sg2042_pdata, + }, {}, }; MODULE_DEVICE_TABLE(of, sdhci_dwcmshc_dt_ids); From fcd56ecaadc88b26fefebf3fb6a1787ac1d059e2 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 5 Aug 2024 22:12:57 +0100 Subject: [PATCH 155/573] dt-bindings: mmc: renesas,sdhi: Remove duplicate compatible and add clock checks Remove the duplicate compatible entry `renesas,sdhi-r9a09g057` and add a restriction for clocks and clock-names for the RZ/V2H(P) SoC, which has four clocks similar to the RZ/G2L SoC. Reported-by: Geert Uytterhoeven Fixes: 32842af74abc ("dt-bindings: mmc: renesas,sdhi: Document RZ/V2H(P) support") Signed-off-by: Lad Prabhakar Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240805211257.61099-1-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Ulf Hansson --- Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml b/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml index 1155b1d79df5..92622d65f12f 100644 --- a/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml +++ b/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml @@ -67,7 +67,6 @@ properties: - renesas,sdhi-r9a07g054 # RZ/V2L - renesas,sdhi-r9a08g045 # RZ/G3S - renesas,sdhi-r9a09g011 # RZ/V2M - - renesas,sdhi-r9a09g057 # RZ/V2H(P) - const: renesas,rzg2l-sdhi reg: @@ -120,7 +119,9 @@ allOf: properties: compatible: contains: - const: renesas,rzg2l-sdhi + enum: + - renesas,sdhi-r9a09g057 + - renesas,rzg2l-sdhi then: properties: clocks: From e634d9873bb13f6a91262099f545af8faf456fd0 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 18 Aug 2024 16:23:01 +0200 Subject: [PATCH 156/573] mmc: mtk-sd: Improve data type in msdc_timeout_cal() The local variable clk_ns uses at most 32 bits and can be a u32. Replace the 64-by-32 do_div() division with a standard divison. Since do_div() casts the divisor to u32 anyway, changing the data type of clk_ns to u32 also removes the following Coccinelle/coccicheck warning reported by do_div.cocci: WARNING: do_div() does a 64-by-32 division, please consider using div64_u64 instead Use min_t(u32,,) to simplify the code and improve its readability. Signed-off-by: Thorsten Blum Link: https://lore.kernel.org/r/20240818142300.64156-2-thorsten.blum@toblux.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/mtk-sd.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/mtk-sd.c b/drivers/mmc/host/mtk-sd.c index e386f78e3267..89018b6c97b9 100644 --- a/drivers/mmc/host/mtk-sd.c +++ b/drivers/mmc/host/mtk-sd.c @@ -795,14 +795,13 @@ static void msdc_unprepare_data(struct msdc_host *host, struct mmc_data *data) static u64 msdc_timeout_cal(struct msdc_host *host, u64 ns, u64 clks) { struct mmc_host *mmc = mmc_from_priv(host); - u64 timeout, clk_ns; - u32 mode = 0; + u64 timeout; + u32 clk_ns, mode = 0; if (mmc->actual_clock == 0) { timeout = 0; } else { - clk_ns = 1000000000ULL; - do_div(clk_ns, mmc->actual_clock); + clk_ns = 1000000000U / mmc->actual_clock; timeout = ns + clk_ns - 1; do_div(timeout, clk_ns); timeout += clks; @@ -831,7 +830,7 @@ static void msdc_set_timeout(struct msdc_host *host, u64 ns, u64 clks) timeout = msdc_timeout_cal(host, ns, clks); sdr_set_field(host->base + SDC_CFG, SDC_CFG_DTOC, - (u32)(timeout > 255 ? 255 : timeout)); + min_t(u32, timeout, 255)); } static void msdc_set_busy_timeout(struct msdc_host *host, u64 ns, u64 clks) @@ -840,7 +839,7 @@ static void msdc_set_busy_timeout(struct msdc_host *host, u64 ns, u64 clks) timeout = msdc_timeout_cal(host, ns, clks); sdr_set_field(host->base + SDC_CFG, SDC_CFG_WRDTOC, - (u32)(timeout > 8191 ? 8191 : timeout)); + min_t(u32, timeout, 8191)); } static void msdc_gate_clock(struct msdc_host *host) From 1645e815cb5c10ee7df736408d2afc9b7bcfd606 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 18 Aug 2024 19:29:23 +0200 Subject: [PATCH 157/573] dt-bindings: mmc: renesas,sdhi: add top-level constraints Properties with variable number of items per each device are expected to have widest constraints in top-level "properties:" block and further customized (narrowed) in "if:then:". Add missing top-level constraints for clocks. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20240818172923.121867-1-krzysztof.kozlowski@linaro.org Signed-off-by: Ulf Hansson --- Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml b/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml index 92622d65f12f..af378b9ff3f4 100644 --- a/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml +++ b/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml @@ -76,9 +76,13 @@ properties: minItems: 1 maxItems: 3 - clocks: true + clocks: + minItems: 1 + maxItems: 4 - clock-names: true + clock-names: + minItems: 1 + maxItems: 4 dmas: minItems: 4 From 1e9046e3a154608f63ce79edcb01e6afd6b10c7c Mon Sep 17 00:00:00 2001 From: Jens Wiklander Date: Wed, 14 Aug 2024 17:35:55 +0200 Subject: [PATCH 158/573] rpmb: add Replay Protected Memory Block (RPMB) subsystem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A number of storage technologies support a specialised hardware partition designed to be resistant to replay attacks. The underlying HW protocols differ but the operations are common. The RPMB partition cannot be accessed via standard block layer, but by a set of specific RPMB commands. Such a partition provides authenticated and replay protected access, hence suitable as a secure storage. The initial aim of this patch is to provide a simple RPMB driver interface which can be accessed by the optee driver to facilitate early RPMB access to OP-TEE OS (secure OS) during the boot time. A TEE device driver can claim the RPMB interface, for example, via rpmb_interface_register() or rpmb_dev_find_device(). The RPMB driver provides a callback to route RPMB frames to the RPMB device accessible via rpmb_route_frames(). The detailed operation of implementing the access is left to the TEE device driver itself. Signed-off-by: Tomas Winkler Signed-off-by: Alex Bennée Signed-off-by: Shyam Saini Signed-off-by: Jens Wiklander Reviewed-by: Linus Walleij Tested-by: Manuel Traut Reviewed-by: Ulf Hansson Reviewed-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20240814153558.708365-2-jens.wiklander@linaro.org Signed-off-by: Ulf Hansson --- MAINTAINERS | 7 ++ drivers/misc/Kconfig | 10 ++ drivers/misc/Makefile | 1 + drivers/misc/rpmb-core.c | 233 +++++++++++++++++++++++++++++++++++++++ include/linux/rpmb.h | 123 +++++++++++++++++++++ 5 files changed, 374 insertions(+) create mode 100644 drivers/misc/rpmb-core.c create mode 100644 include/linux/rpmb.h diff --git a/MAINTAINERS b/MAINTAINERS index 878dcd23b331..ec8c7ccb0a92 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19861,6 +19861,13 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/allwinner,sun8i-a83t-de2-rotate.yaml F: drivers/media/platform/sunxi/sun8i-rotate/ +RPMB SUBSYSTEM +M: Jens Wiklander +L: linux-kernel@vger.kernel.org +S: Supported +F: drivers/misc/rpmb-core.c +F: include/linux/rpmb.h + RPMSG TTY DRIVER M: Arnaud Pouliquen L: linux-remoteproc@vger.kernel.org diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 41c54051347a..3fe7e2a9bd29 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -104,6 +104,16 @@ config PHANTOM If you choose to build module, its name will be phantom. If unsure, say N here. +config RPMB + tristate "RPMB partition interface" + depends on MMC + help + Unified RPMB unit interface for RPMB capable devices such as eMMC and + UFS. Provides interface for in-kernel security controllers to access + RPMB unit. + + If unsure, select N. + config TIFM_CORE tristate "TI Flash Media interface support" depends on PCI diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index c2f990862d2b..a9f94525e181 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_LKDTM) += lkdtm/ obj-$(CONFIG_TIFM_CORE) += tifm_core.o obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_PHANTOM) += phantom.o +obj-$(CONFIG_RPMB) += rpmb-core.o obj-$(CONFIG_QCOM_COINCELL) += qcom-coincell.o obj-$(CONFIG_QCOM_FASTRPC) += fastrpc.o obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o diff --git a/drivers/misc/rpmb-core.c b/drivers/misc/rpmb-core.c new file mode 100644 index 000000000000..c8888267c222 --- /dev/null +++ b/drivers/misc/rpmb-core.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(c) 2015 - 2019 Intel Corporation. All rights reserved. + * Copyright(c) 2021 - 2024 Linaro Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_IDA(rpmb_ida); +static DEFINE_MUTEX(rpmb_mutex); + +/** + * rpmb_dev_get() - increase rpmb device ref counter + * @rdev: rpmb device + */ +struct rpmb_dev *rpmb_dev_get(struct rpmb_dev *rdev) +{ + if (rdev) + get_device(&rdev->dev); + return rdev; +} +EXPORT_SYMBOL_GPL(rpmb_dev_get); + +/** + * rpmb_dev_put() - decrease rpmb device ref counter + * @rdev: rpmb device + */ +void rpmb_dev_put(struct rpmb_dev *rdev) +{ + if (rdev) + put_device(&rdev->dev); +} +EXPORT_SYMBOL_GPL(rpmb_dev_put); + +/** + * rpmb_route_frames() - route rpmb frames to rpmb device + * @rdev: rpmb device + * @req: rpmb request frames + * @req_len: length of rpmb request frames in bytes + * @rsp: rpmb response frames + * @rsp_len: length of rpmb response frames in bytes + * + * Returns: < 0 on failure + */ +int rpmb_route_frames(struct rpmb_dev *rdev, u8 *req, + unsigned int req_len, u8 *rsp, unsigned int rsp_len) +{ + if (!req || !req_len || !rsp || !rsp_len) + return -EINVAL; + + return rdev->descr.route_frames(rdev->dev.parent, req, req_len, + rsp, rsp_len); +} +EXPORT_SYMBOL_GPL(rpmb_route_frames); + +static void rpmb_dev_release(struct device *dev) +{ + struct rpmb_dev *rdev = to_rpmb_dev(dev); + + mutex_lock(&rpmb_mutex); + ida_simple_remove(&rpmb_ida, rdev->id); + mutex_unlock(&rpmb_mutex); + kfree(rdev->descr.dev_id); + kfree(rdev); +} + +static struct class rpmb_class = { + .name = "rpmb", + .dev_release = rpmb_dev_release, +}; + +/** + * rpmb_dev_find_device() - return first matching rpmb device + * @start: rpmb device to begin with + * @data: data for the match function + * @match: the matching function + * + * Iterate over registered RPMB devices, and call @match() for each passing + * it the RPMB device and @data. + * + * The return value of @match() is checked for each call. If it returns + * anything other 0, break and return the found RPMB device. + * + * It's the callers responsibility to call rpmb_dev_put() on the returned + * device, when it's done with it. + * + * Returns: a matching rpmb device or NULL on failure + */ +struct rpmb_dev *rpmb_dev_find_device(const void *data, + const struct rpmb_dev *start, + int (*match)(struct device *dev, + const void *data)) +{ + struct device *dev; + const struct device *start_dev = NULL; + + if (start) + start_dev = &start->dev; + dev = class_find_device(&rpmb_class, start_dev, data, match); + + return dev ? to_rpmb_dev(dev) : NULL; +} +EXPORT_SYMBOL_GPL(rpmb_dev_find_device); + +int rpmb_interface_register(struct class_interface *intf) +{ + intf->class = &rpmb_class; + + return class_interface_register(intf); +} +EXPORT_SYMBOL_GPL(rpmb_interface_register); + +void rpmb_interface_unregister(struct class_interface *intf) +{ + class_interface_unregister(intf); +} +EXPORT_SYMBOL_GPL(rpmb_interface_unregister); + +/** + * rpmb_dev_unregister() - unregister RPMB partition from the RPMB subsystem + * @rdev: the rpmb device to unregister + * + * This function should be called from the release function of the + * underlying device used when the RPMB device was registered. + * + * Returns: < 0 on failure + */ +int rpmb_dev_unregister(struct rpmb_dev *rdev) +{ + if (!rdev) + return -EINVAL; + + device_del(&rdev->dev); + + rpmb_dev_put(rdev); + + return 0; +} +EXPORT_SYMBOL_GPL(rpmb_dev_unregister); + +/** + * rpmb_dev_register - register RPMB partition with the RPMB subsystem + * @dev: storage device of the rpmb device + * @descr: RPMB device description + * + * While registering the RPMB partition extract needed device information + * while needed resources are available. + * + * Returns: a pointer to a 'struct rpmb_dev' or an ERR_PTR on failure + */ +struct rpmb_dev *rpmb_dev_register(struct device *dev, + struct rpmb_descr *descr) +{ + struct rpmb_dev *rdev; + int ret; + + if (!dev || !descr || !descr->route_frames || !descr->dev_id || + !descr->dev_id_len) + return ERR_PTR(-EINVAL); + + rdev = kzalloc(sizeof(*rdev), GFP_KERNEL); + if (!rdev) + return ERR_PTR(-ENOMEM); + rdev->descr = *descr; + rdev->descr.dev_id = kmemdup(descr->dev_id, descr->dev_id_len, + GFP_KERNEL); + if (!rdev->descr.dev_id) { + ret = -ENOMEM; + goto err_free_rdev; + } + + mutex_lock(&rpmb_mutex); + ret = ida_simple_get(&rpmb_ida, 0, 0, GFP_KERNEL); + mutex_unlock(&rpmb_mutex); + if (ret < 0) + goto err_free_dev_id; + rdev->id = ret; + + dev_set_name(&rdev->dev, "rpmb%d", rdev->id); + rdev->dev.class = &rpmb_class; + rdev->dev.parent = dev; + + ret = device_register(&rdev->dev); + if (ret) + goto err_id_remove; + + dev_dbg(&rdev->dev, "registered device\n"); + + return rdev; + +err_id_remove: + mutex_lock(&rpmb_mutex); + ida_simple_remove(&rpmb_ida, rdev->id); + mutex_unlock(&rpmb_mutex); +err_free_dev_id: + kfree(rdev->descr.dev_id); +err_free_rdev: + kfree(rdev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(rpmb_dev_register); + +static int __init rpmb_init(void) +{ + int ret; + + ret = class_register(&rpmb_class); + if (ret) { + pr_err("couldn't create class\n"); + return ret; + } + ida_init(&rpmb_ida); + return 0; +} + +static void __exit rpmb_exit(void) +{ + ida_destroy(&rpmb_ida); + class_unregister(&rpmb_class); +} + +subsys_initcall(rpmb_init); +module_exit(rpmb_exit); + +MODULE_AUTHOR("Jens Wiklander "); +MODULE_DESCRIPTION("RPMB class"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/rpmb.h b/include/linux/rpmb.h new file mode 100644 index 000000000000..cccda73eea4d --- /dev/null +++ b/include/linux/rpmb.h @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2015-2019 Intel Corp. All rights reserved + * Copyright (C) 2021-2022 Linaro Ltd + */ +#ifndef __RPMB_H__ +#define __RPMB_H__ + +#include +#include + +/** + * enum rpmb_type - type of underlying storage technology + * + * @RPMB_TYPE_EMMC : emmc (JESD84-B50.1) + * @RPMB_TYPE_UFS : UFS (JESD220) + * @RPMB_TYPE_NVME : NVM Express + */ +enum rpmb_type { + RPMB_TYPE_EMMC, + RPMB_TYPE_UFS, + RPMB_TYPE_NVME, +}; + +/** + * struct rpmb_descr - RPMB description provided by the underlying block device + * + * @type : block device type + * @route_frames : routes frames to and from the RPMB device + * @dev_id : unique device identifier read from the hardware + * @dev_id_len : length of unique device identifier + * @reliable_wr_count: number of sectors that can be written in one access + * @capacity : capacity of the device in units of 128K + * + * @dev_id is intended to be used as input when deriving the authenticaion key. + */ +struct rpmb_descr { + enum rpmb_type type; + int (*route_frames)(struct device *dev, u8 *req, unsigned int req_len, + u8 *resp, unsigned int resp_len); + u8 *dev_id; + size_t dev_id_len; + u16 reliable_wr_count; + u16 capacity; +}; + +/** + * struct rpmb_dev - device which can support RPMB partition + * + * @dev : device + * @id : device_id + * @list_node : linked list node + * @descr : RPMB description + */ +struct rpmb_dev { + struct device dev; + int id; + struct list_head list_node; + struct rpmb_descr descr; +}; + +#define to_rpmb_dev(x) container_of((x), struct rpmb_dev, dev) + +#if IS_ENABLED(CONFIG_RPMB) +struct rpmb_dev *rpmb_dev_get(struct rpmb_dev *rdev); +void rpmb_dev_put(struct rpmb_dev *rdev); +struct rpmb_dev *rpmb_dev_find_device(const void *data, + const struct rpmb_dev *start, + int (*match)(struct device *dev, + const void *data)); +int rpmb_interface_register(struct class_interface *intf); +void rpmb_interface_unregister(struct class_interface *intf); +struct rpmb_dev *rpmb_dev_register(struct device *dev, + struct rpmb_descr *descr); +int rpmb_dev_unregister(struct rpmb_dev *rdev); + +int rpmb_route_frames(struct rpmb_dev *rdev, u8 *req, + unsigned int req_len, u8 *resp, unsigned int resp_len); + +#else +static inline struct rpmb_dev *rpmb_dev_get(struct rpmb_dev *rdev) +{ + return NULL; +} + +static inline void rpmb_dev_put(struct rpmb_dev *rdev) { } + +static inline struct rpmb_dev * +rpmb_dev_find_device(const void *data, const struct rpmb_dev *start, + int (*match)(struct device *dev, const void *data)) +{ + return NULL; +} + +static inline int rpmb_interface_register(struct class_interface *intf) +{ + return -EOPNOTSUPP; +} + +static inline void rpmb_interface_unregister(struct class_interface *intf) +{ +} + +static inline struct rpmb_dev * +rpmb_dev_register(struct device *dev, struct rpmb_descr *descr) +{ + return NULL; +} + +static inline int rpmb_dev_unregister(struct rpmb_dev *dev) +{ + return 0; +} + +static inline int rpmb_route_frames(struct rpmb_dev *rdev, u8 *req, + unsigned int req_len, u8 *resp, + unsigned int resp_len) +{ + return -EOPNOTSUPP; +} +#endif /* CONFIG_RPMB */ + +#endif /* __RPMB_H__ */ From 7852028a35f03e04c9cdc92fd26894cca4a8a4de Mon Sep 17 00:00:00 2001 From: Jens Wiklander Date: Wed, 14 Aug 2024 17:35:56 +0200 Subject: [PATCH 159/573] mmc: block: register RPMB partition with the RPMB subsystem Register eMMC RPMB partition with the RPMB subsystem and provide an implementation for the RPMB access operations abstracting the actual multi step process. Add a callback to extract the needed device information at registration to avoid accessing the struct mmc_card at a later stage as we're not holding a reference counter for this struct. Taking the needed reference to md->disk in mmc_blk_alloc_rpmb_part() instead of in mmc_rpmb_chrdev_open(). This is needed by the route_frames() function pointer in struct rpmb_ops. Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Jens Wiklander Tested-by: Manuel Traut Reviewed-by: Linus Walleij Reviewed-by: Ulf Hansson Link: https://lore.kernel.org/r/20240814153558.708365-3-jens.wiklander@linaro.org Signed-off-by: Ulf Hansson --- drivers/mmc/core/block.c | 242 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 240 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index 2c9963248fcb..cc7318089cf2 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ #include #include #include +#include #include #include @@ -76,6 +78,48 @@ MODULE_ALIAS("mmc:block"); #define MMC_EXTRACT_INDEX_FROM_ARG(x) ((x & 0x00FF0000) >> 16) #define MMC_EXTRACT_VALUE_FROM_ARG(x) ((x & 0x0000FF00) >> 8) +/** + * struct rpmb_frame - rpmb frame as defined by eMMC 5.1 (JESD84-B51) + * + * @stuff : stuff bytes + * @key_mac : The authentication key or the message authentication + * code (MAC) depending on the request/response type. + * The MAC will be delivered in the last (or the only) + * block of data. + * @data : Data to be written or read by signed access. + * @nonce : Random number generated by the host for the requests + * and copied to the response by the RPMB engine. + * @write_counter: Counter value for the total amount of the successful + * authenticated data write requests made by the host. + * @addr : Address of the data to be programmed to or read + * from the RPMB. Address is the serial number of + * the accessed block (half sector 256B). + * @block_count : Number of blocks (half sectors, 256B) requested to be + * read/programmed. + * @result : Includes information about the status of the write counter + * (valid, expired) and result of the access made to the RPMB. + * @req_resp : Defines the type of request and response to/from the memory. + * + * The stuff bytes and big-endian properties are modeled to fit to the spec. + */ +struct rpmb_frame { + u8 stuff[196]; + u8 key_mac[32]; + u8 data[256]; + u8 nonce[16]; + __be32 write_counter; + __be16 addr; + __be16 block_count; + __be16 result; + __be16 req_resp; +} __packed; + +#define RPMB_PROGRAM_KEY 0x1 /* Program RPMB Authentication Key */ +#define RPMB_GET_WRITE_COUNTER 0x2 /* Read RPMB write counter */ +#define RPMB_WRITE_DATA 0x3 /* Write data to RPMB partition */ +#define RPMB_READ_DATA 0x4 /* Read data from RPMB partition */ +#define RPMB_RESULT_READ 0x5 /* Read result request (Internal) */ + static DEFINE_MUTEX(block_mutex); /* @@ -155,6 +199,7 @@ static const struct bus_type mmc_rpmb_bus_type = { * @id: unique device ID number * @part_index: partition index (0 on first) * @md: parent MMC block device + * @rdev: registered RPMB device * @node: list item, so we can put this device on a list */ struct mmc_rpmb_data { @@ -163,6 +208,7 @@ struct mmc_rpmb_data { int id; unsigned int part_index; struct mmc_blk_data *md; + struct rpmb_dev *rdev; struct list_head node; }; @@ -2670,7 +2716,6 @@ static int mmc_rpmb_chrdev_open(struct inode *inode, struct file *filp) get_device(&rpmb->dev); filp->private_data = rpmb; - mmc_blk_get(rpmb->md->disk); return nonseekable_open(inode, filp); } @@ -2680,7 +2725,6 @@ static int mmc_rpmb_chrdev_release(struct inode *inode, struct file *filp) struct mmc_rpmb_data *rpmb = container_of(inode->i_cdev, struct mmc_rpmb_data, chrdev); - mmc_blk_put(rpmb->md); put_device(&rpmb->dev); return 0; @@ -2701,10 +2745,165 @@ static void mmc_blk_rpmb_device_release(struct device *dev) { struct mmc_rpmb_data *rpmb = dev_get_drvdata(dev); + rpmb_dev_unregister(rpmb->rdev); + mmc_blk_put(rpmb->md); ida_free(&mmc_rpmb_ida, rpmb->id); kfree(rpmb); } +static void free_idata(struct mmc_blk_ioc_data **idata, unsigned int cmd_count) +{ + unsigned int n; + + for (n = 0; n < cmd_count; n++) + kfree(idata[n]); + kfree(idata); +} + +static struct mmc_blk_ioc_data **alloc_idata(struct mmc_rpmb_data *rpmb, + unsigned int cmd_count) +{ + struct mmc_blk_ioc_data **idata; + unsigned int n; + + idata = kcalloc(cmd_count, sizeof(*idata), GFP_KERNEL); + if (!idata) + return NULL; + + for (n = 0; n < cmd_count; n++) { + idata[n] = kcalloc(1, sizeof(**idata), GFP_KERNEL); + if (!idata[n]) { + free_idata(idata, n); + return NULL; + } + idata[n]->rpmb = rpmb; + } + + return idata; +} + +static void set_idata(struct mmc_blk_ioc_data *idata, u32 opcode, + int write_flag, u8 *buf, unsigned int buf_bytes) +{ + /* + * The size of an RPMB frame must match what's expected by the + * hardware. + */ + BUILD_BUG_ON(sizeof(struct rpmb_frame) != 512); + + idata->ic.opcode = opcode; + idata->ic.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + idata->ic.write_flag = write_flag; + idata->ic.blksz = sizeof(struct rpmb_frame); + idata->ic.blocks = buf_bytes / idata->ic.blksz; + idata->buf = buf; + idata->buf_bytes = buf_bytes; +} + +static int mmc_route_rpmb_frames(struct device *dev, u8 *req, + unsigned int req_len, u8 *resp, + unsigned int resp_len) +{ + struct rpmb_frame *frm = (struct rpmb_frame *)req; + struct mmc_rpmb_data *rpmb = dev_get_drvdata(dev); + struct mmc_blk_data *md = rpmb->md; + struct mmc_blk_ioc_data **idata; + struct mmc_queue_req *mq_rq; + unsigned int cmd_count; + struct request *rq; + u16 req_type; + bool write; + int ret; + + if (IS_ERR(md->queue.card)) + return PTR_ERR(md->queue.card); + + if (req_len < sizeof(*frm)) + return -EINVAL; + + req_type = be16_to_cpu(frm->req_resp); + switch (req_type) { + case RPMB_PROGRAM_KEY: + if (req_len != sizeof(struct rpmb_frame) || + resp_len != sizeof(struct rpmb_frame)) + return -EINVAL; + write = true; + break; + case RPMB_GET_WRITE_COUNTER: + if (req_len != sizeof(struct rpmb_frame) || + resp_len != sizeof(struct rpmb_frame)) + return -EINVAL; + write = false; + break; + case RPMB_WRITE_DATA: + if (req_len % sizeof(struct rpmb_frame) || + resp_len != sizeof(struct rpmb_frame)) + return -EINVAL; + write = true; + break; + case RPMB_READ_DATA: + if (req_len != sizeof(struct rpmb_frame) || + resp_len % sizeof(struct rpmb_frame)) + return -EINVAL; + write = false; + break; + default: + return -EINVAL; + } + + if (write) + cmd_count = 3; + else + cmd_count = 2; + + idata = alloc_idata(rpmb, cmd_count); + if (!idata) + return -ENOMEM; + + if (write) { + struct rpmb_frame *frm = (struct rpmb_frame *)resp; + + /* Send write request frame(s) */ + set_idata(idata[0], MMC_WRITE_MULTIPLE_BLOCK, + 1 | MMC_CMD23_ARG_REL_WR, req, req_len); + + /* Send result request frame */ + memset(frm, 0, sizeof(*frm)); + frm->req_resp = cpu_to_be16(RPMB_RESULT_READ); + set_idata(idata[1], MMC_WRITE_MULTIPLE_BLOCK, 1, resp, + resp_len); + + /* Read response frame */ + set_idata(idata[2], MMC_READ_MULTIPLE_BLOCK, 0, resp, resp_len); + } else { + /* Send write request frame(s) */ + set_idata(idata[0], MMC_WRITE_MULTIPLE_BLOCK, 1, req, req_len); + + /* Read response frame */ + set_idata(idata[1], MMC_READ_MULTIPLE_BLOCK, 0, resp, resp_len); + } + + rq = blk_mq_alloc_request(md->queue.queue, REQ_OP_DRV_OUT, 0); + if (IS_ERR(rq)) { + ret = PTR_ERR(rq); + goto out; + } + + mq_rq = req_to_mmc_queue_req(rq); + mq_rq->drv_op = MMC_DRV_OP_IOCTL_RPMB; + mq_rq->drv_op_result = -EIO; + mq_rq->drv_op_data = idata; + mq_rq->ioc_count = cmd_count; + blk_execute_rq(rq, false); + ret = req_to_mmc_queue_req(rq)->drv_op_result; + + blk_mq_free_request(rq); + +out: + free_idata(idata, cmd_count); + return ret; +} + static int mmc_blk_alloc_rpmb_part(struct mmc_card *card, struct mmc_blk_data *md, unsigned int part_index, @@ -2739,6 +2938,7 @@ static int mmc_blk_alloc_rpmb_part(struct mmc_card *card, rpmb->dev.release = mmc_blk_rpmb_device_release; device_initialize(&rpmb->dev); dev_set_drvdata(&rpmb->dev, rpmb); + mmc_blk_get(md->disk); rpmb->md = md; cdev_init(&rpmb->chrdev, &mmc_rpmb_fileops); @@ -3000,6 +3200,42 @@ static void mmc_blk_remove_debugfs(struct mmc_card *card, #endif /* CONFIG_DEBUG_FS */ +static void mmc_blk_rpmb_add(struct mmc_card *card) +{ + struct mmc_blk_data *md = dev_get_drvdata(&card->dev); + struct mmc_rpmb_data *rpmb; + struct rpmb_dev *rdev; + unsigned int n; + u32 cid[4]; + struct rpmb_descr descr = { + .type = RPMB_TYPE_EMMC, + .route_frames = mmc_route_rpmb_frames, + .reliable_wr_count = card->ext_csd.enhanced_rpmb_supported ? + 2 : 32, + .capacity = card->ext_csd.raw_rpmb_size_mult, + .dev_id = (void *)cid, + .dev_id_len = sizeof(cid), + }; + + /* + * Provice CID as an octet array. The CID needs to be interpreted + * when used as input to derive the RPMB key since some fields + * will change due to firmware updates. + */ + for (n = 0; n < 4; n++) + cid[n] = be32_to_cpu((__force __be32)card->raw_cid[n]); + + list_for_each_entry(rpmb, &md->rpmbs, node) { + rdev = rpmb_dev_register(&rpmb->dev, &descr); + if (IS_ERR(rdev)) { + pr_warn("%s: could not register RPMB device\n", + dev_name(&rpmb->dev)); + continue; + } + rpmb->rdev = rdev; + } +} + static int mmc_blk_probe(struct mmc_card *card) { struct mmc_blk_data *md; @@ -3045,6 +3281,8 @@ static int mmc_blk_probe(struct mmc_card *card) pm_runtime_enable(&card->dev); } + mmc_blk_rpmb_add(card); + return 0; out: From c30b855e814d9094e369a19fbd86c9bb5badc154 Mon Sep 17 00:00:00 2001 From: Jens Wiklander Date: Wed, 14 Aug 2024 17:35:57 +0200 Subject: [PATCH 160/573] tee: add tee_device_set_dev_groups() Add tee_device_set_dev_groups() to TEE drivers to supply driver specific attribute groups. The class specific attributes are from now on added via the tee_class, which currently only consist of implementation_id. Signed-off-by: Jens Wiklander Reviewed-by: Sumit Garg Link: https://lore.kernel.org/r/20240814153558.708365-4-jens.wiklander@linaro.org Signed-off-by: Ulf Hansson --- drivers/tee/tee_core.c | 19 +++++++++++++------ include/linux/tee_core.h | 12 ++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c index d52e879b204e..d113679b1e2d 100644 --- a/drivers/tee/tee_core.c +++ b/drivers/tee/tee_core.c @@ -40,10 +40,7 @@ static const uuid_t tee_client_uuid_ns = UUID_INIT(0x58ac9ca0, 0x2086, 0x4683, static DECLARE_BITMAP(dev_mask, TEE_NUM_DEVICES); static DEFINE_SPINLOCK(driver_lock); -static const struct class tee_class = { - .name = "tee", -}; - +static const struct class tee_class; static dev_t tee_devt; struct tee_context *teedev_open(struct tee_device *teedev) @@ -965,6 +962,13 @@ err: } EXPORT_SYMBOL_GPL(tee_device_alloc); +void tee_device_set_dev_groups(struct tee_device *teedev, + const struct attribute_group **dev_groups) +{ + teedev->dev.groups = dev_groups; +} +EXPORT_SYMBOL_GPL(tee_device_set_dev_groups); + static ssize_t implementation_id_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -983,6 +987,11 @@ static struct attribute *tee_dev_attrs[] = { ATTRIBUTE_GROUPS(tee_dev); +static const struct class tee_class = { + .name = "tee", + .dev_groups = tee_dev_groups, +}; + /** * tee_device_register() - Registers a TEE device * @teedev: Device to register @@ -1001,8 +1010,6 @@ int tee_device_register(struct tee_device *teedev) return -EINVAL; } - teedev->dev.groups = tee_dev_groups; - rc = cdev_device_add(&teedev->cdev, &teedev->dev); if (rc) { dev_err(&teedev->dev, diff --git a/include/linux/tee_core.h b/include/linux/tee_core.h index efd16ed52315..a38494d6b5f4 100644 --- a/include/linux/tee_core.h +++ b/include/linux/tee_core.h @@ -154,6 +154,18 @@ int tee_device_register(struct tee_device *teedev); */ void tee_device_unregister(struct tee_device *teedev); +/** + * tee_device_set_dev_groups() - Set device attribute groups + * @teedev: Device to register + * @dev_groups: Attribute groups + * + * Assigns the provided @dev_groups to the @teedev to be registered later + * with tee_device_register(). Calling this function is optional, but if + * it's called it must be called before tee_device_register(). + */ +void tee_device_set_dev_groups(struct tee_device *teedev, + const struct attribute_group **dev_groups); + /** * tee_session_calc_client_uuid() - Calculates client UUID for session * @uuid: Resulting UUID From f0c8431568eedee7705c92f5c341bdc4567e3ad5 Mon Sep 17 00:00:00 2001 From: Jens Wiklander Date: Wed, 14 Aug 2024 17:35:58 +0200 Subject: [PATCH 161/573] optee: probe RPMB device using RPMB subsystem Adds support in the OP-TEE drivers (both SMC and FF-A ABIs) to probe and use an RPMB device via the RPMB subsystem instead of passing the RPMB frames via tee-supplicant in user space. A fallback mechanism is kept to route RPMB frames via tee-supplicant if the RPMB subsystem isn't available. The OP-TEE RPC ABI is extended to support iterating over all RPMB devices until one is found with the expected RPMB key already programmed. Signed-off-by: Jens Wiklander Tested-by: Manuel Traut Reviewed-by: Sumit Garg Link: https://lore.kernel.org/r/20240814153558.708365-5-jens.wiklander@linaro.org Signed-off-by: Ulf Hansson --- Documentation/ABI/testing/sysfs-class-tee | 15 ++ MAINTAINERS | 1 + drivers/tee/optee/core.c | 96 +++++++++++- drivers/tee/optee/device.c | 7 + drivers/tee/optee/ffa_abi.c | 14 ++ drivers/tee/optee/optee_ffa.h | 2 + drivers/tee/optee/optee_private.h | 26 +++- drivers/tee/optee/optee_rpc_cmd.h | 35 +++++ drivers/tee/optee/optee_smc.h | 2 + drivers/tee/optee/rpc.c | 177 ++++++++++++++++++++++ drivers/tee/optee/smc_abi.c | 14 ++ 11 files changed, 387 insertions(+), 2 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-class-tee diff --git a/Documentation/ABI/testing/sysfs-class-tee b/Documentation/ABI/testing/sysfs-class-tee new file mode 100644 index 000000000000..c9144d16003e --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-tee @@ -0,0 +1,15 @@ +What: /sys/class/tee/tee{,priv}X/rpmb_routing_model +Date: May 2024 +KernelVersion: 6.10 +Contact: op-tee@lists.trustedfirmware.org +Description: + RPMB frames can be routed to the RPMB device via the + user-space daemon tee-supplicant or the RPMB subsystem + in the kernel. The value "user" means that the driver + will route the RPMB frames via user space. Conversely, + "kernel" means that the frames are routed via the RPMB + subsystem without assistance from tee-supplicant. It + should be assumed that RPMB frames are routed via user + space if the variable is absent. The primary purpose + of this variable is to let systemd know whether + tee-supplicant is needed in the early boot with initramfs. diff --git a/MAINTAINERS b/MAINTAINERS index ec8c7ccb0a92..cfeb80fbc15f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22458,6 +22458,7 @@ M: Jens Wiklander R: Sumit Garg L: op-tee@lists.trustedfirmware.org S: Maintained +F: Documentation/ABI/testing/sysfs-class-tee F: Documentation/driver-api/tee.rst F: Documentation/tee/ F: Documentation/userspace-api/tee.rst diff --git a/drivers/tee/optee/core.c b/drivers/tee/optee/core.c index 39e688d4e974..c75fddc83576 100644 --- a/drivers/tee/optee/core.c +++ b/drivers/tee/optee/core.c @@ -10,17 +10,85 @@ #include #include #include +#include #include #include #include #include #include "optee_private.h" +struct blocking_notifier_head optee_rpmb_intf_added = + BLOCKING_NOTIFIER_INIT(optee_rpmb_intf_added); + +static int rpmb_add_dev(struct device *dev) +{ + blocking_notifier_call_chain(&optee_rpmb_intf_added, 0, + to_rpmb_dev(dev)); + + return 0; +} + +static struct class_interface rpmb_class_intf = { + .add_dev = rpmb_add_dev, +}; + +void optee_bus_scan_rpmb(struct work_struct *work) +{ + struct optee *optee = container_of(work, struct optee, + rpmb_scan_bus_work); + int ret; + + if (!optee->rpmb_scan_bus_done) { + ret = optee_enumerate_devices(PTA_CMD_GET_DEVICES_RPMB); + optee->rpmb_scan_bus_done = !ret; + if (ret && ret != -ENODEV) + pr_info("Scanning for RPMB device: ret %d\n", ret); + } +} + +int optee_rpmb_intf_rdev(struct notifier_block *intf, unsigned long action, + void *data) +{ + struct optee *optee = container_of(intf, struct optee, rpmb_intf); + + schedule_work(&optee->rpmb_scan_bus_work); + + return 0; +} + static void optee_bus_scan(struct work_struct *work) { WARN_ON(optee_enumerate_devices(PTA_CMD_GET_DEVICES_SUPP)); } +static ssize_t rpmb_routing_model_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct optee *optee = dev_get_drvdata(dev); + const char *s; + + if (optee->in_kernel_rpmb_routing) + s = "kernel"; + else + s = "user"; + + return scnprintf(buf, PAGE_SIZE, "%s\n", s); +} +static DEVICE_ATTR_RO(rpmb_routing_model); + +static struct attribute *optee_dev_attrs[] = { + &dev_attr_rpmb_routing_model.attr, + NULL +}; + +ATTRIBUTE_GROUPS(optee_dev); + +void optee_set_dev_group(struct optee *optee) +{ + tee_device_set_dev_groups(optee->teedev, optee_dev_groups); + tee_device_set_dev_groups(optee->supp_teedev, optee_dev_groups); +} + int optee_open(struct tee_context *ctx, bool cap_memref_null) { struct optee_context_data *ctxdata; @@ -97,6 +165,9 @@ void optee_release_supp(struct tee_context *ctx) void optee_remove_common(struct optee *optee) { + blocking_notifier_chain_unregister(&optee_rpmb_intf_added, + &optee->rpmb_intf); + cancel_work_sync(&optee->rpmb_scan_bus_work); /* Unregister OP-TEE specific client devices on TEE bus */ optee_unregister_devices(); @@ -113,13 +184,18 @@ void optee_remove_common(struct optee *optee) tee_shm_pool_free(optee->pool); optee_supp_uninit(&optee->supp); mutex_destroy(&optee->call_queue.mutex); + rpmb_dev_put(optee->rpmb_dev); + mutex_destroy(&optee->rpmb_dev_mutex); } static int smc_abi_rc; static int ffa_abi_rc; +static bool intf_is_regged; static int __init optee_core_init(void) { + int rc; + /* * The kernel may have crashed at the same time that all available * secure world threads were suspended and we cannot reschedule the @@ -130,18 +206,36 @@ static int __init optee_core_init(void) if (is_kdump_kernel()) return -ENODEV; + if (IS_REACHABLE(CONFIG_RPMB)) { + rc = rpmb_interface_register(&rpmb_class_intf); + if (rc) + return rc; + intf_is_regged = true; + } + smc_abi_rc = optee_smc_abi_register(); ffa_abi_rc = optee_ffa_abi_register(); /* If both failed there's no point with this module */ - if (smc_abi_rc && ffa_abi_rc) + if (smc_abi_rc && ffa_abi_rc) { + if (IS_REACHABLE(CONFIG_RPMB)) { + rpmb_interface_unregister(&rpmb_class_intf); + intf_is_regged = false; + } return smc_abi_rc; + } + return 0; } module_init(optee_core_init); static void __exit optee_core_exit(void) { + if (IS_REACHABLE(CONFIG_RPMB) && intf_is_regged) { + rpmb_interface_unregister(&rpmb_class_intf); + intf_is_regged = false; + } + if (!smc_abi_rc) optee_smc_abi_unregister(); if (!ffa_abi_rc) diff --git a/drivers/tee/optee/device.c b/drivers/tee/optee/device.c index d296c70ddfdc..950b4661d5df 100644 --- a/drivers/tee/optee/device.c +++ b/drivers/tee/optee/device.c @@ -43,6 +43,13 @@ static int get_devices(struct tee_context *ctx, u32 session, ret = tee_client_invoke_func(ctx, &inv_arg, param); if ((ret < 0) || ((inv_arg.ret != TEEC_SUCCESS) && (inv_arg.ret != TEEC_ERROR_SHORT_BUFFER))) { + /* + * TEE_ERROR_STORAGE_NOT_AVAILABLE is returned when getting + * the list of device TAs that depends on RPMB but a usable + * RPMB device isn't found. + */ + if (inv_arg.ret == TEE_ERROR_STORAGE_NOT_AVAILABLE) + return -ENODEV; pr_err("PTA_CMD_GET_DEVICES invoke function err: %x\n", inv_arg.ret); return -EINVAL; diff --git a/drivers/tee/optee/ffa_abi.c b/drivers/tee/optee/ffa_abi.c index 3e73efa51bba..f3af5666bb11 100644 --- a/drivers/tee/optee/ffa_abi.c +++ b/drivers/tee/optee/ffa_abi.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -909,6 +910,10 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev) optee->ffa.bottom_half_value = U32_MAX; optee->rpc_param_count = rpc_param_count; + if (IS_REACHABLE(CONFIG_RPMB) && + (sec_caps & OPTEE_FFA_SEC_CAP_RPMB_PROBE)) + optee->in_kernel_rpmb_routing = true; + teedev = tee_device_alloc(&optee_ffa_clnt_desc, NULL, optee->pool, optee); if (IS_ERR(teedev)) { @@ -925,6 +930,8 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev) } optee->supp_teedev = teedev; + optee_set_dev_group(optee); + rc = tee_device_register(optee->teedev); if (rc) goto err_unreg_supp_teedev; @@ -940,6 +947,7 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev) optee_cq_init(&optee->call_queue, 0); optee_supp_init(&optee->supp); optee_shm_arg_cache_init(optee, arg_cache_flags); + mutex_init(&optee->rpmb_dev_mutex); ffa_dev_set_drvdata(ffa_dev, optee); ctx = teedev_open(optee->teedev); if (IS_ERR(ctx)) { @@ -961,6 +969,10 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev) if (rc) goto err_unregister_devices; + INIT_WORK(&optee->rpmb_scan_bus_work, optee_bus_scan_rpmb); + optee->rpmb_intf.notifier_call = optee_rpmb_intf_rdev; + blocking_notifier_chain_register(&optee_rpmb_intf_added, + &optee->rpmb_intf); pr_info("initialized driver\n"); return 0; @@ -974,6 +986,8 @@ err_close_ctx: teedev_close_context(ctx); err_rhashtable_free: rhashtable_free_and_destroy(&optee->ffa.global_ids, rh_free_fn, NULL); + rpmb_dev_put(optee->rpmb_dev); + mutex_destroy(&optee->rpmb_dev_mutex); optee_supp_uninit(&optee->supp); mutex_destroy(&optee->call_queue.mutex); mutex_destroy(&optee->ffa.mutex); diff --git a/drivers/tee/optee/optee_ffa.h b/drivers/tee/optee/optee_ffa.h index 5db779dc00de..257735ae5b56 100644 --- a/drivers/tee/optee/optee_ffa.h +++ b/drivers/tee/optee/optee_ffa.h @@ -92,6 +92,8 @@ #define OPTEE_FFA_SEC_CAP_ARG_OFFSET BIT(0) /* OP-TEE supports asynchronous notification via FF-A */ #define OPTEE_FFA_SEC_CAP_ASYNC_NOTIF BIT(1) +/* OP-TEE supports probing for RPMB device if needed */ +#define OPTEE_FFA_SEC_CAP_RPMB_PROBE BIT(2) #define OPTEE_FFA_EXCHANGE_CAPABILITIES OPTEE_FFA_BLOCKING_CALL(2) diff --git a/drivers/tee/optee/optee_private.h b/drivers/tee/optee/optee_private.h index 424898cdc4e9..dc0f355ef72a 100644 --- a/drivers/tee/optee/optee_private.h +++ b/drivers/tee/optee/optee_private.h @@ -7,7 +7,9 @@ #define OPTEE_PRIVATE_H #include +#include #include +#include #include #include #include @@ -20,6 +22,7 @@ /* Some Global Platform error codes used in this driver */ #define TEEC_SUCCESS 0x00000000 #define TEEC_ERROR_BAD_PARAMETERS 0xFFFF0006 +#define TEEC_ERROR_ITEM_NOT_FOUND 0xFFFF0008 #define TEEC_ERROR_NOT_SUPPORTED 0xFFFF000A #define TEEC_ERROR_COMMUNICATION 0xFFFF000E #define TEEC_ERROR_OUT_OF_MEMORY 0xFFFF000C @@ -28,6 +31,7 @@ /* API Return Codes are from the GP TEE Internal Core API Specification */ #define TEE_ERROR_TIMEOUT 0xFFFF3001 +#define TEE_ERROR_STORAGE_NOT_AVAILABLE 0xF0100003 #define TEEC_ORIGIN_COMMS 0x00000002 @@ -200,6 +204,12 @@ struct optee_ops { * @notif: notification synchronization struct * @supp: supplicant synchronization struct for RPC to supplicant * @pool: shared memory pool + * @mutex: mutex protecting @rpmb_dev + * @rpmb_dev: current RPMB device or NULL + * @rpmb_scan_bus_done flag if device registation of RPMB dependent devices + * was already done + * @rpmb_scan_bus_work workq to for an RPMB device and to scan optee bus + * and register RPMB dependent optee drivers * @rpc_param_count: If > 0 number of RPC parameters to make room for * @scan_bus_done flag if device registation was already done. * @scan_bus_work workq to scan optee bus and register optee drivers @@ -218,9 +228,16 @@ struct optee { struct optee_notif notif; struct optee_supp supp; struct tee_shm_pool *pool; + /* Protects rpmb_dev pointer */ + struct mutex rpmb_dev_mutex; + struct rpmb_dev *rpmb_dev; + struct notifier_block rpmb_intf; unsigned int rpc_param_count; - bool scan_bus_done; + bool scan_bus_done; + bool rpmb_scan_bus_done; + bool in_kernel_rpmb_routing; struct work_struct scan_bus_work; + struct work_struct rpmb_scan_bus_work; }; struct optee_session { @@ -253,6 +270,8 @@ struct optee_call_ctx { size_t num_entries; }; +extern struct blocking_notifier_head optee_rpmb_intf_added; + int optee_notif_init(struct optee *optee, u_int max_key); void optee_notif_uninit(struct optee *optee); int optee_notif_wait(struct optee *optee, u_int key, u32 timeout); @@ -283,9 +302,14 @@ int optee_cancel_req(struct tee_context *ctx, u32 cancel_id, u32 session); #define PTA_CMD_GET_DEVICES 0x0 #define PTA_CMD_GET_DEVICES_SUPP 0x1 +#define PTA_CMD_GET_DEVICES_RPMB 0x2 int optee_enumerate_devices(u32 func); void optee_unregister_devices(void); +void optee_bus_scan_rpmb(struct work_struct *work); +int optee_rpmb_intf_rdev(struct notifier_block *intf, unsigned long action, + void *data); +void optee_set_dev_group(struct optee *optee); void optee_remove_common(struct optee *optee); int optee_open(struct tee_context *ctx, bool cap_memref_null); void optee_release(struct tee_context *ctx); diff --git a/drivers/tee/optee/optee_rpc_cmd.h b/drivers/tee/optee/optee_rpc_cmd.h index 4576751b490c..87a59cc03480 100644 --- a/drivers/tee/optee/optee_rpc_cmd.h +++ b/drivers/tee/optee/optee_rpc_cmd.h @@ -104,4 +104,39 @@ /* I2C master control flags */ #define OPTEE_RPC_I2C_FLAGS_TEN_BIT BIT(0) +/* + * Reset RPMB probing + * + * Releases an eventually already used RPMB devices and starts over searching + * for RPMB devices. Returns the kind of shared memory to use in subsequent + * OPTEE_RPC_CMD_RPMB_PROBE_NEXT and OPTEE_RPC_CMD_RPMB calls. + * + * [out] value[0].a OPTEE_RPC_SHM_TYPE_*, the parameter for + * OPTEE_RPC_CMD_SHM_ALLOC + */ +#define OPTEE_RPC_CMD_RPMB_PROBE_RESET 22 + +/* + * Probe next RPMB device + * + * [out] value[0].a Type of RPMB device, OPTEE_RPC_RPMB_* + * [out] value[0].b EXT CSD-slice 168 "RPMB Size" + * [out] value[0].c EXT CSD-slice 222 "Reliable Write Sector Count" + * [out] memref[1] Buffer with the raw CID + */ +#define OPTEE_RPC_CMD_RPMB_PROBE_NEXT 23 + +/* Type of RPMB device */ +#define OPTEE_RPC_RPMB_EMMC 0 +#define OPTEE_RPC_RPMB_UFS 1 +#define OPTEE_RPC_RPMB_NVME 2 + +/* + * Replay Protected Memory Block access + * + * [in] memref[0] Frames to device + * [out] memref[1] Frames from device + */ +#define OPTEE_RPC_CMD_RPMB_FRAMES 24 + #endif /*__OPTEE_RPC_CMD_H*/ diff --git a/drivers/tee/optee/optee_smc.h b/drivers/tee/optee/optee_smc.h index 7d9fa426505b..879426300821 100644 --- a/drivers/tee/optee/optee_smc.h +++ b/drivers/tee/optee/optee_smc.h @@ -278,6 +278,8 @@ struct optee_smc_get_shm_config_result { #define OPTEE_SMC_SEC_CAP_ASYNC_NOTIF BIT(5) /* Secure world supports pre-allocating RPC arg struct */ #define OPTEE_SMC_SEC_CAP_RPC_ARG BIT(6) +/* Secure world supports probing for RPMB device if needed */ +#define OPTEE_SMC_SEC_CAP_RPMB_PROBE BIT(7) #define OPTEE_SMC_FUNCID_EXCHANGE_CAPABILITIES 9 #define OPTEE_SMC_EXCHANGE_CAPABILITIES \ diff --git a/drivers/tee/optee/rpc.c b/drivers/tee/optee/rpc.c index 5de4504665be..a4b49fd1d46d 100644 --- a/drivers/tee/optee/rpc.c +++ b/drivers/tee/optee/rpc.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include "optee_private.h" @@ -261,6 +262,154 @@ void optee_rpc_cmd_free_suppl(struct tee_context *ctx, struct tee_shm *shm) optee_supp_thrd_req(ctx, OPTEE_RPC_CMD_SHM_FREE, 1, ¶m); } +static void handle_rpc_func_rpmb_probe_reset(struct tee_context *ctx, + struct optee *optee, + struct optee_msg_arg *arg) +{ + struct tee_param params[1]; + + if (arg->num_params != ARRAY_SIZE(params) || + optee->ops->from_msg_param(optee, params, arg->num_params, + arg->params) || + params[0].attr != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + params[0].u.value.a = OPTEE_RPC_SHM_TYPE_KERNEL; + params[0].u.value.b = 0; + params[0].u.value.c = 0; + if (optee->ops->to_msg_param(optee, arg->params, + arg->num_params, params)) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + mutex_lock(&optee->rpmb_dev_mutex); + rpmb_dev_put(optee->rpmb_dev); + optee->rpmb_dev = NULL; + mutex_unlock(&optee->rpmb_dev_mutex); + + arg->ret = TEEC_SUCCESS; +} + +static int rpmb_type_to_rpc_type(enum rpmb_type rtype) +{ + switch (rtype) { + case RPMB_TYPE_EMMC: + return OPTEE_RPC_RPMB_EMMC; + case RPMB_TYPE_UFS: + return OPTEE_RPC_RPMB_UFS; + case RPMB_TYPE_NVME: + return OPTEE_RPC_RPMB_NVME; + default: + return -1; + } +} + +static int rpc_rpmb_match(struct device *dev, const void *data) +{ + struct rpmb_dev *rdev = to_rpmb_dev(dev); + + return rpmb_type_to_rpc_type(rdev->descr.type) >= 0; +} + +static void handle_rpc_func_rpmb_probe_next(struct tee_context *ctx, + struct optee *optee, + struct optee_msg_arg *arg) +{ + struct rpmb_dev *rdev; + struct tee_param params[2]; + void *buf; + + if (arg->num_params != ARRAY_SIZE(params) || + optee->ops->from_msg_param(optee, params, arg->num_params, + arg->params) || + params[0].attr != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT || + params[1].attr != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + buf = tee_shm_get_va(params[1].u.memref.shm, + params[1].u.memref.shm_offs); + if (!buf) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + mutex_lock(&optee->rpmb_dev_mutex); + rdev = rpmb_dev_find_device(NULL, optee->rpmb_dev, rpc_rpmb_match); + rpmb_dev_put(optee->rpmb_dev); + optee->rpmb_dev = rdev; + mutex_unlock(&optee->rpmb_dev_mutex); + + if (!rdev) { + arg->ret = TEEC_ERROR_ITEM_NOT_FOUND; + return; + } + + if (params[1].u.memref.size < rdev->descr.dev_id_len) { + arg->ret = TEEC_ERROR_SHORT_BUFFER; + return; + } + memcpy(buf, rdev->descr.dev_id, rdev->descr.dev_id_len); + params[1].u.memref.size = rdev->descr.dev_id_len; + params[0].u.value.a = rpmb_type_to_rpc_type(rdev->descr.type); + params[0].u.value.b = rdev->descr.capacity; + params[0].u.value.c = rdev->descr.reliable_wr_count; + if (optee->ops->to_msg_param(optee, arg->params, + arg->num_params, params)) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + arg->ret = TEEC_SUCCESS; +} + +static void handle_rpc_func_rpmb_frames(struct tee_context *ctx, + struct optee *optee, + struct optee_msg_arg *arg) +{ + struct tee_param params[2]; + struct rpmb_dev *rdev; + void *p0, *p1; + + mutex_lock(&optee->rpmb_dev_mutex); + rdev = rpmb_dev_get(optee->rpmb_dev); + mutex_unlock(&optee->rpmb_dev_mutex); + if (!rdev) { + arg->ret = TEEC_ERROR_ITEM_NOT_FOUND; + return; + } + + if (arg->num_params != ARRAY_SIZE(params) || + optee->ops->from_msg_param(optee, params, arg->num_params, + arg->params) || + params[0].attr != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT || + params[1].attr != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + goto out; + } + + p0 = tee_shm_get_va(params[0].u.memref.shm, + params[0].u.memref.shm_offs); + p1 = tee_shm_get_va(params[1].u.memref.shm, + params[1].u.memref.shm_offs); + if (rpmb_route_frames(rdev, p0, params[0].u.memref.size, p1, + params[1].u.memref.size)) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + goto out; + } + if (optee->ops->to_msg_param(optee, arg->params, + arg->num_params, params)) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + goto out; + } + arg->ret = TEEC_SUCCESS; +out: + rpmb_dev_put(rdev); +} + void optee_rpc_cmd(struct tee_context *ctx, struct optee *optee, struct optee_msg_arg *arg) { @@ -277,6 +426,34 @@ void optee_rpc_cmd(struct tee_context *ctx, struct optee *optee, case OPTEE_RPC_CMD_I2C_TRANSFER: handle_rpc_func_cmd_i2c_transfer(ctx, arg); break; + /* + * optee->in_kernel_rpmb_routing true means that OP-TEE supports + * in-kernel RPMB routing _and_ that the RPMB subsystem is + * reachable. This is reported to user space with + * rpmb_routing_model=kernel in sysfs. + * + * rpmb_routing_model=kernel is also a promise to user space that + * RPMB access will not require supplicant support, hence the + * checks below. + */ + case OPTEE_RPC_CMD_RPMB_PROBE_RESET: + if (optee->in_kernel_rpmb_routing) + handle_rpc_func_rpmb_probe_reset(ctx, optee, arg); + else + handle_rpc_supp_cmd(ctx, optee, arg); + break; + case OPTEE_RPC_CMD_RPMB_PROBE_NEXT: + if (optee->in_kernel_rpmb_routing) + handle_rpc_func_rpmb_probe_next(ctx, optee, arg); + else + handle_rpc_supp_cmd(ctx, optee, arg); + break; + case OPTEE_RPC_CMD_RPMB_FRAMES: + if (optee->in_kernel_rpmb_routing) + handle_rpc_func_rpmb_frames(ctx, optee, arg); + else + handle_rpc_supp_cmd(ctx, optee, arg); + break; default: handle_rpc_supp_cmd(ctx, optee, arg); } diff --git a/drivers/tee/optee/smc_abi.c b/drivers/tee/optee/smc_abi.c index 844285d4f03c..e9456e3e74cc 100644 --- a/drivers/tee/optee/smc_abi.c +++ b/drivers/tee/optee/smc_abi.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -1685,6 +1686,10 @@ static int optee_probe(struct platform_device *pdev) optee->smc.sec_caps = sec_caps; optee->rpc_param_count = rpc_param_count; + if (IS_REACHABLE(CONFIG_RPMB) && + (sec_caps & OPTEE_SMC_SEC_CAP_RPMB_PROBE)) + optee->in_kernel_rpmb_routing = true; + teedev = tee_device_alloc(&optee_clnt_desc, NULL, pool, optee); if (IS_ERR(teedev)) { rc = PTR_ERR(teedev); @@ -1699,6 +1704,8 @@ static int optee_probe(struct platform_device *pdev) } optee->supp_teedev = teedev; + optee_set_dev_group(optee); + rc = tee_device_register(optee->teedev); if (rc) goto err_unreg_supp_teedev; @@ -1712,6 +1719,7 @@ static int optee_probe(struct platform_device *pdev) optee->smc.memremaped_shm = memremaped_shm; optee->pool = pool; optee_shm_arg_cache_init(optee, arg_cache_flags); + mutex_init(&optee->rpmb_dev_mutex); platform_set_drvdata(pdev, optee); ctx = teedev_open(optee->teedev); @@ -1766,6 +1774,10 @@ static int optee_probe(struct platform_device *pdev) if (rc) goto err_disable_shm_cache; + INIT_WORK(&optee->rpmb_scan_bus_work, optee_bus_scan_rpmb); + optee->rpmb_intf.notifier_call = optee_rpmb_intf_rdev; + blocking_notifier_chain_register(&optee_rpmb_intf_added, + &optee->rpmb_intf); pr_info("initialized driver\n"); return 0; @@ -1779,6 +1791,8 @@ err_notif_uninit: err_close_ctx: teedev_close_context(ctx); err_supp_uninit: + rpmb_dev_put(optee->rpmb_dev); + mutex_destroy(&optee->rpmb_dev_mutex); optee_shm_arg_cache_uninit(optee); optee_supp_uninit(&optee->supp); mutex_destroy(&optee->call_queue.mutex); From d3596c73011d1b0e9b43ae12f1e0f491d6bb96eb Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Wed, 21 Aug 2024 15:24:06 +0200 Subject: [PATCH 162/573] mmc: core: Remove struct mmc_context_info The 'mmc_context_info' structure is unused. It has been introduced in: - commit 2220eedfd7ae ("mmc: fix async request mechanism for sequential read scenarios") in 2013-02 and its usages have been removed in: - commit 126b62700386 ("mmc: core: Remove code no longer needed after the switch to blk-mq") - commit 0fbfd1251830 ("mmc: block: Remove code no longer needed after the switch to blk-mq") in 2017-12. Now remove this unused structure. Signed-off-by: Christophe JAILLET Reviewed-by: Vladimir Zapolskiy Link: https://lore.kernel.org/r/232106a8a6a374dee25feea9b94498361568c10b.1724246389.git.christophe.jaillet@wanadoo.fr Signed-off-by: Ulf Hansson --- include/linux/mmc/host.h | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 88c6a76042ee..f85df7d045ca 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -291,20 +291,6 @@ struct mmc_slot { void *handler_priv; }; -/** - * mmc_context_info - synchronization details for mmc context - * @is_done_rcv wake up reason was done request - * @is_new_req wake up reason was new request - * @is_waiting_last_req mmc context waiting for single running request - * @wait wait queue - */ -struct mmc_context_info { - bool is_done_rcv; - bool is_new_req; - bool is_waiting_last_req; - wait_queue_head_t wait; -}; - struct regulator; struct mmc_pwrseq; From f5e1638bf3c785600e2ab53e5532007af5296581 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Sat, 24 Aug 2024 01:59:17 +0300 Subject: [PATCH 163/573] mmc: core: remove left-over data structure declarations The last users of 'enum mmc_blk_status' and 'struct mmc_async_req' were removed by commit 126b62700386 ("mmc: core: Remove code no longer needed after the switch to blk-mq") in 2017, remove these two left-over data structures. Signed-off-by: Vladimir Zapolskiy Link: https://lore.kernel.org/r/20240823225917.2826156-1-vladimir.zapolskiy@linaro.org Signed-off-by: Ulf Hansson --- include/linux/mmc/core.h | 12 ------------ include/linux/mmc/host.h | 10 ---------- 2 files changed, 22 deletions(-) diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 2c7928a50907..f0ac2e469b32 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -11,18 +11,6 @@ struct mmc_data; struct mmc_request; -enum mmc_blk_status { - MMC_BLK_SUCCESS = 0, - MMC_BLK_PARTIAL, - MMC_BLK_CMD_ERR, - MMC_BLK_RETRY, - MMC_BLK_ABORT, - MMC_BLK_DATA_ERR, - MMC_BLK_ECC_ERR, - MMC_BLK_NOMEDIUM, - MMC_BLK_NEW_REQUEST, -}; - struct mmc_command { u32 opcode; u32 arg; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index f85df7d045ca..ac01cd1622ef 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -264,16 +264,6 @@ struct mmc_cqe_ops { void (*cqe_recovery_finish)(struct mmc_host *host); }; -struct mmc_async_req { - /* active mmc request */ - struct mmc_request *mrq; - /* - * Check error status of completed mmc request. - * Returns 0 if success otherwise non zero. - */ - enum mmc_blk_status (*err_check)(struct mmc_card *, struct mmc_async_req *); -}; - /** * struct mmc_slot - MMC slot functions * From 4c39529663b93165953ecf9b1a9ea817358dcd06 Mon Sep 17 00:00:00 2001 From: Pedro Falcato Date: Wed, 7 Aug 2024 10:07:46 +0100 Subject: [PATCH 164/573] slab: Warn on duplicate cache names when DEBUG_VM=y Duplicate slab cache names can create havoc for userspace tooling that expects slab cache names to be unique [1]. This is a reasonable expectation. Sadly, too many duplicate name problems are out there in the wild, so simply warn instead of pr_err() + failing the sanity check. [ vbabka@suse.cz: change to WARN_ON(), see the discussion at [2] ] Link: https://lore.kernel.org/linux-fsdevel/2d1d053da1cafb3e7940c4f25952da4f0af34e38.1722293276.git.osandov@fb.com/ [1] Link: https://lore.kernel.org/all/20240807090746.2146479-1-pedro.falcato@gmail.com/ [2] Signed-off-by: Pedro Falcato Acked-by: Christoph Lameter Signed-off-by: Vlastimil Babka --- mm/slab_common.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mm/slab_common.c b/mm/slab_common.c index 40b582a014b8..dea2b05c0e3f 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -88,6 +88,19 @@ unsigned int kmem_cache_size(struct kmem_cache *s) EXPORT_SYMBOL(kmem_cache_size); #ifdef CONFIG_DEBUG_VM + +static bool kmem_cache_is_duplicate_name(const char *name) +{ + struct kmem_cache *s; + + list_for_each_entry(s, &slab_caches, list) { + if (!strcmp(s->name, name)) + return true; + } + + return false; +} + static int kmem_cache_sanity_check(const char *name, unsigned int size) { if (!name || in_interrupt() || size > KMALLOC_MAX_SIZE) { @@ -95,6 +108,10 @@ static int kmem_cache_sanity_check(const char *name, unsigned int size) return -EINVAL; } + /* Duplicate names will confuse slabtop, et al */ + WARN(kmem_cache_is_duplicate_name(name), + "kmem_cache of name '%s' already exists\n", name); + WARN_ON(strchr(name, ' ')); /* It confuses parsers */ return 0; } From bf6b9e9ba0861c92b08c77edbd5d602063443c5f Mon Sep 17 00:00:00 2001 From: Axel Rasmussen Date: Wed, 14 Aug 2024 14:50:37 -0700 Subject: [PATCH 165/573] mm, slub: print CPU id (and its node) on slab OOM Depending on how remote_node_defrag_ratio is configured, allocations can end up in this path as a result of the local node being OOM, despite the allocation overall being unconstrained (node == -1). When we print a warning, printing the current CPU makes that situation more clear (i.e., you can immediately see which node's OOM status matters for the allocation at hand). Acked-by: David Rientjes Signed-off-by: Axel Rasmussen Signed-off-by: Vlastimil Babka --- mm/slub.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index c9d8a2497fd6..3088260bf75d 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -3416,14 +3416,15 @@ slab_out_of_memory(struct kmem_cache *s, gfp_t gfpflags, int nid) { static DEFINE_RATELIMIT_STATE(slub_oom_rs, DEFAULT_RATELIMIT_INTERVAL, DEFAULT_RATELIMIT_BURST); + int cpu = raw_smp_processor_id(); int node; struct kmem_cache_node *n; if ((gfpflags & __GFP_NOWARN) || !__ratelimit(&slub_oom_rs)) return; - pr_warn("SLUB: Unable to allocate memory on node %d, gfp=%#x(%pGg)\n", - nid, gfpflags, &gfpflags); + pr_warn("SLUB: Unable to allocate memory on CPU %u (of node %d) on node %d, gfp=%#x(%pGg)\n", + cpu, cpu_to_node(cpu), nid, gfpflags, &gfpflags); pr_warn(" cache: %s, object size: %u, buffer size: %u, default order: %u, min order: %u\n", s->name, s->object_size, s->size, oo_order(s->oo), oo_order(s->min)); From 1941b31482a61a7bd75300d1905938e7e48c3d25 Mon Sep 17 00:00:00 2001 From: Christoph Lameter Date: Mon, 19 Aug 2024 11:54:05 -0700 Subject: [PATCH 166/573] Reenable NUMA policy support in the slab allocator Revert commit 8014c46ad991 ("slub: use alloc_pages_node() in alloc_slab_page()"). The patch disabled the numa policy support in the slab allocator. It did not consider that alloc_pages() uses memory policies but alloc_pages_node() does not. As a result of this patch slab memory allocations are no longer spread via interleave policy across all available NUMA nodes on bootup. Instead all slab memory is allocated close to the boot processor. This leads to an imbalance of memory accesses on NUMA systems. Also applications using MPOL_INTERLEAVE as a memory policy will no longer spread slab allocations over all nodes in the interleave set but allocate memory locally. This may also result in unbalanced allocations on a single numa node. SLUB does not apply memory policies to individual object allocations. However, it relies on the page allocators support of memory policies through alloc_pages() to do the NUMA memory allocations on a per folio or page level. SLUB also applies memory policies when retrieving partial allocated slab pages from the partial list. Fixes: 8014c46ad991 ("slub: use alloc_pages_node() in alloc_slab_page()") Signed-off-by: Christoph Lameter Reviewed-by: Yang Shi Signed-off-by: Vlastimil Babka --- mm/slub.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mm/slub.c b/mm/slub.c index 3088260bf75d..60004bfc2dc2 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -2318,7 +2318,11 @@ static inline struct slab *alloc_slab_page(gfp_t flags, int node, struct slab *slab; unsigned int order = oo_order(oo); - folio = (struct folio *)alloc_pages_node(node, flags, order); + if (node == NUMA_NO_NODE) + folio = (struct folio *)alloc_pages(flags, order); + else + folio = (struct folio *)__alloc_pages_node(node, flags, order); + if (!folio) return NULL; From 1bf8012fc6997f2117f6919369cde16659db60e0 Mon Sep 17 00:00:00 2001 From: Wen Yang Date: Mon, 19 Aug 2024 22:59:45 +0800 Subject: [PATCH 167/573] pstore: replace spinlock_t by raw_spinlock_t pstore_dump() is called when both preemption and local IRQ are disabled, and a spinlock is obtained, which is problematic for the RT kernel because in this configuration, spinlocks are sleep locks. Replace the spinlock_t with raw_spinlock_t to avoid sleeping in atomic context. Signed-off-by: Wen Yang Cc: Kees Cook Cc: Tony Luck Cc: Guilherme G. Piccoli Cc: linux-hardening@vger.kernel.org Cc: linux-kernel@vger.kernel.org Link: https://lore.kernel.org/r/20240819145945.61274-1-wen.yang@linux.dev Signed-off-by: Kees Cook --- fs/pstore/platform.c | 8 ++++---- include/linux/pstore.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/pstore/platform.c b/fs/pstore/platform.c index 3497ede88aa0..84719e2bcbc4 100644 --- a/fs/pstore/platform.c +++ b/fs/pstore/platform.c @@ -288,13 +288,13 @@ static void pstore_dump(struct kmsg_dumper *dumper, why = kmsg_dump_reason_str(reason); if (pstore_cannot_block_path(reason)) { - if (!spin_trylock_irqsave(&psinfo->buf_lock, flags)) { + if (!raw_spin_trylock_irqsave(&psinfo->buf_lock, flags)) { pr_err("dump skipped in %s path because of concurrent dump\n", in_nmi() ? "NMI" : why); return; } } else { - spin_lock_irqsave(&psinfo->buf_lock, flags); + raw_spin_lock_irqsave(&psinfo->buf_lock, flags); } kmsg_dump_rewind(&iter); @@ -364,7 +364,7 @@ static void pstore_dump(struct kmsg_dumper *dumper, total += record.size; part++; } - spin_unlock_irqrestore(&psinfo->buf_lock, flags); + raw_spin_unlock_irqrestore(&psinfo->buf_lock, flags); if (saved_ret) { pr_err_once("backend (%s) writing error (%d)\n", psinfo->name, @@ -503,7 +503,7 @@ int pstore_register(struct pstore_info *psi) psi->write_user = pstore_write_user_compat; psinfo = psi; mutex_init(&psinfo->read_mutex); - spin_lock_init(&psinfo->buf_lock); + raw_spin_lock_init(&psinfo->buf_lock); if (psi->flags & PSTORE_FLAGS_DMESG) allocate_buf_for_compression(); diff --git a/include/linux/pstore.h b/include/linux/pstore.h index 638507a3c8ff..fed601053c51 100644 --- a/include/linux/pstore.h +++ b/include/linux/pstore.h @@ -182,7 +182,7 @@ struct pstore_info { struct module *owner; const char *name; - spinlock_t buf_lock; + raw_spinlock_t buf_lock; char *buf; size_t bufsize; From 919a4719026f68a9d6b5b87db2e935564cdf42a5 Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Tue, 20 Aug 2024 04:19:42 -0400 Subject: [PATCH 168/573] ABI: testing: sysfs-class-power: clarify charge_type documentation The existing docs here are a bit vague. This reformats and rewords it, and is based upon the wording originally used by the dell-laptop driver battery documentation and also sysfs-class-power-wilco. The wording for "Long Life" and "Bypass" remain the same, because I'm unfamiliar with hardware that use them. Signed-off-by: Andres Salomon Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20240820041942.30ed42f3@5400 Signed-off-by: Sebastian Reichel --- Documentation/ABI/testing/sysfs-class-power | 38 +++++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 7c81f0a25a48..84973f66b42c 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -377,17 +377,33 @@ What: /sys/class/power_supply//charge_type Date: July 2009 Contact: linux-pm@vger.kernel.org Description: - Represents the type of charging currently being applied to the - battery. "Trickle", "Fast", and "Standard" all mean different - charging speeds. "Adaptive" means that the charger uses some - algorithm to adjust the charge rate dynamically, without - any user configuration required. "Custom" means that the charger - uses the charge_control_* properties as configuration for some - different algorithm. "Long Life" means the charger reduces its - charging rate in order to prolong the battery health. "Bypass" - means the charger bypasses the charging path around the - integrated converter allowing for a "smart" wall adaptor to - perform the power conversion externally. + Select the charging algorithm to use for a battery. + + Standard: + Fully charge the battery at a moderate rate. + Fast: + Quickly charge the battery using fast-charge + technology. This is typically harder on the battery + than standard charging and may lower its lifespan. + Trickle: + Users who primarily operate the system while + plugged into an external power source can extend + battery life with this mode. Vendor tooling may + call this "Primarily AC Use". + Adaptive: + Automatically optimize battery charge rate based + on typical usage pattern. + Custom: + Use the charge_control_* properties to determine + when to start and stop charging. Advanced users + can use this to drastically extend battery life. + Long Life: + The charger reduces its charging rate in order to + prolong the battery health. + Bypass: + The charger bypasses the charging path around the + integrated converter allowing for a "smart" wall + adaptor to perform the power conversion externally. Access: Read, Write From 7b2e5b9f1d5ea84802ae5ff531ae1739acd97c54 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 19 Aug 2024 12:08:31 +0800 Subject: [PATCH 169/573] power: supply: max8998_charger: Fix module autoloading Add MODULE_DEVICE_TABLE(), so modules could be properly autoloaded based on the alias from platform_device_id table. Signed-off-by: Jinjie Ruan Link: https://lore.kernel.org/r/20240819040831.2801543-1-ruanjinjie@huawei.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max8998_charger.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/supply/max8998_charger.c b/drivers/power/supply/max8998_charger.c index c26023b19f26..418b882b163d 100644 --- a/drivers/power/supply/max8998_charger.c +++ b/drivers/power/supply/max8998_charger.c @@ -191,6 +191,7 @@ static const struct platform_device_id max8998_battery_id[] = { { "max8998-battery", TYPE_MAX8998 }, { } }; +MODULE_DEVICE_TABLE(platform, max8998_battery_id); static struct platform_driver max8998_battery_driver = { .driver = { From 17656d2215c3978bbe2811a5e249cd07fe3de77f Mon Sep 17 00:00:00 2001 From: Stanislav Jakubek Date: Thu, 15 Aug 2024 12:01:36 +0200 Subject: [PATCH 170/573] dt-bindings: power: supply: sc27xx-fg: add low voltage alarm IRQ The SC27XX fuel gauge supports a low voltage alarm IRQ, which is used for more accurate battery capacity measurements with lower voltages. This was unfortunately never documented in bindings, do so now. The only in-tree user (sc2731.dtsi) has had interrupts specified since its initial fuel-gauge submission and the current kernel driver returns an error when no interrupt is specified, so also add it to the required list. Signed-off-by: Stanislav Jakubek Acked-by: Conor Dooley Link: https://lore.kernel.org/r/Zr3SAHlq5A78QvrW@standask-GA-A55M-S2HP Signed-off-by: Sebastian Reichel --- .../devicetree/bindings/power/supply/sc27xx-fg.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/power/supply/sc27xx-fg.yaml b/Documentation/devicetree/bindings/power/supply/sc27xx-fg.yaml index de43e45a43b7..9108a2841caf 100644 --- a/Documentation/devicetree/bindings/power/supply/sc27xx-fg.yaml +++ b/Documentation/devicetree/bindings/power/supply/sc27xx-fg.yaml @@ -27,6 +27,9 @@ properties: battery-detect-gpios: maxItems: 1 + interrupts: + maxItems: 1 + io-channels: items: - description: Battery Temperature ADC @@ -53,6 +56,7 @@ required: - compatible - reg - battery-detect-gpios + - interrupts - io-channels - io-channel-names - nvmem-cells @@ -88,6 +92,8 @@ examples: compatible = "sprd,sc2731-fgu"; reg = <0xa00>; battery-detect-gpios = <&pmic_eic 9 GPIO_ACTIVE_HIGH>; + interrupt-parent = <&sc2731_pmic>; + interrupts = <4>; io-channels = <&pmic_adc 5>, <&pmic_adc 14>; io-channel-names = "bat-temp", "charge-vol"; nvmem-cells = <&fgu_calib>; From a9125e868f7ad80d527cf5c69e20fa0ada96bff9 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 5 Jul 2024 13:31:12 +0200 Subject: [PATCH 171/573] power: supply: core: simplify with cleanup.h Allocate the memory with scoped/cleanup.h to reduce error handling and make the code a bit simpler. Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240705113113.42851-1-krzysztof.kozlowski@linaro.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 8f6025acd10a..2b845ac51157 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -9,6 +9,7 @@ * Modified: 2004, Oct Szabolcs Gyurko */ +#include #include #include #include @@ -756,10 +757,10 @@ int power_supply_get_battery_info(struct power_supply *psy, for (index = 0; index < len; index++) { struct power_supply_battery_ocv_table *table; - char *propname; int i, tab_len, size; - propname = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d", index); + char *propname __free(kfree) = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d", + index); if (!propname) { power_supply_put_battery_info(psy, info); err = -ENOMEM; @@ -768,13 +769,11 @@ int power_supply_get_battery_info(struct power_supply *psy, list = of_get_property(battery_np, propname, &size); if (!list || !size) { dev_err(&psy->dev, "failed to get %s\n", propname); - kfree(propname); power_supply_put_battery_info(psy, info); err = -EINVAL; goto out_put_node; } - kfree(propname); tab_len = size / (2 * sizeof(__be32)); info->ocv_table_size[index] = tab_len; From e764374f4b57a0e0c0221bc0188034ae9996808e Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 5 Jul 2024 13:31:13 +0200 Subject: [PATCH 172/573] power: supply: twl4030_charger: correct comparision with old current Driver reads existing current value from two 8-bit registers, but then compares only one of them with the new 16-bit value. clang W=1 is also not happy: twl4030_charger.c:243:16: error: variable 'cur_reg' set but not used [-Werror,-Wunused-but-set-variable] Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240705113113.42851-2-krzysztof.kozlowski@linaro.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/twl4030_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c index 7b9b0b3e164e..f3f1a0862e93 100644 --- a/drivers/power/supply/twl4030_charger.c +++ b/drivers/power/supply/twl4030_charger.c @@ -363,7 +363,7 @@ static int twl4030_charger_update_current(struct twl4030_bci *bci) if (status < 0) return status; cur_reg |= oldreg << 8; - if (reg != oldreg) { + if (reg != cur_reg) { /* disable write protection for one write access for * BCIIREF */ status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7, From b5959789e96e01b4b27a6d0354b475398d67aa6f Mon Sep 17 00:00:00 2001 From: Vlastimil Babka Date: Wed, 7 Aug 2024 12:31:14 +0200 Subject: [PATCH 173/573] mm, slab: dissolve shutdown_cache() into its caller There's only one caller of shutdown_cache() so move the code into it. Preparatory patch for further changes, no functional change. Reviewed-by: Jann Horn Signed-off-by: Vlastimil Babka --- mm/slab_common.c | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index 40b582a014b8..b76d65d7fe33 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -540,27 +540,6 @@ static void slab_caches_to_rcu_destroy_workfn(struct work_struct *work) } } -static int shutdown_cache(struct kmem_cache *s) -{ - /* free asan quarantined objects */ - kasan_cache_shutdown(s); - - if (__kmem_cache_shutdown(s) != 0) - return -EBUSY; - - list_del(&s->list); - - if (s->flags & SLAB_TYPESAFE_BY_RCU) { - list_add_tail(&s->list, &slab_caches_to_rcu_destroy); - schedule_work(&slab_caches_to_rcu_destroy_work); - } else { - kfence_shutdown_cache(s); - debugfs_slab_release(s); - } - - return 0; -} - void slab_kmem_cache_release(struct kmem_cache *s) { __kmem_cache_release(s); @@ -585,9 +564,26 @@ void kmem_cache_destroy(struct kmem_cache *s) if (s->refcount) goto out_unlock; - err = shutdown_cache(s); + /* free asan quarantined objects */ + kasan_cache_shutdown(s); + + err = __kmem_cache_shutdown(s); WARN(err, "%s %s: Slab cache still has objects when called from %pS", __func__, s->name, (void *)_RET_IP_); + + if (err) + goto out_unlock; + + list_del(&s->list); + + if (rcu_set) { + list_add_tail(&s->list, &slab_caches_to_rcu_destroy); + schedule_work(&slab_caches_to_rcu_destroy_work); + } else { + kfence_shutdown_cache(s); + debugfs_slab_release(s); + } + out_unlock: mutex_unlock(&slab_mutex); cpus_read_unlock(); From 4ec10268ed98a3d568a39861e7b7d0a0fa7cbe60 Mon Sep 17 00:00:00 2001 From: Vlastimil Babka Date: Wed, 7 Aug 2024 12:31:15 +0200 Subject: [PATCH 174/573] mm, slab: unlink slabinfo, sysfs and debugfs immediately kmem_cache_destroy() includes removing the associated sysfs and debugfs directories, and the cache from the list of caches that appears in /proc/slabinfo. Currently this might not happen immediately when: - the cache is SLAB_TYPESAFE_BY_RCU and the cleanup is delayed, including the directores removal - __kmem_cache_shutdown() fails due to outstanding objects - the directories remain indefinitely When a cache is recreated with the same name, such as due to module unload followed by a load, the directories will fail to be recreated for the new instance of the cache due to the old directories being present. The cache will also appear twice in /proc/slabinfo. While we want to convert the SLAB_TYPESAFE_BY_RCU cleanup to be synchronous again, the second point remains. So let's fix this first and have the directories and slabinfo removed immediately in kmem_cache_destroy() and regardless of __kmem_cache_shutdown() success. This should not make debugging harder if __kmem_cache_shutdown() fails, because a detailed report of outstanding objects is printed into dmesg already due to the failure. Also simplify kmem_cache_release() sysfs handling by using __is_defined(SLAB_SUPPORTS_SYSFS). Note the resulting code in kmem_cache_destroy() is a bit ugly but will be further simplified - this is in order to make small bisectable steps. Reviewed-by: Jann Horn Signed-off-by: Vlastimil Babka --- mm/slab_common.c | 57 ++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index b76d65d7fe33..db61df3b4282 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -484,31 +484,19 @@ fail: } EXPORT_SYMBOL(kmem_buckets_create); -#ifdef SLAB_SUPPORTS_SYSFS /* * For a given kmem_cache, kmem_cache_destroy() should only be called * once or there will be a use-after-free problem. The actual deletion * and release of the kobject does not need slab_mutex or cpu_hotplug_lock * protection. So they are now done without holding those locks. - * - * Note that there will be a slight delay in the deletion of sysfs files - * if kmem_cache_release() is called indrectly from a work function. */ static void kmem_cache_release(struct kmem_cache *s) { - if (slab_state >= FULL) { - sysfs_slab_unlink(s); + if (__is_defined(SLAB_SUPPORTS_SYSFS) && slab_state >= FULL) sysfs_slab_release(s); - } else { + else slab_kmem_cache_release(s); - } } -#else -static void kmem_cache_release(struct kmem_cache *s) -{ - slab_kmem_cache_release(s); -} -#endif static void slab_caches_to_rcu_destroy_workfn(struct work_struct *work) { @@ -534,7 +522,6 @@ static void slab_caches_to_rcu_destroy_workfn(struct work_struct *work) rcu_barrier(); list_for_each_entry_safe(s, s2, &to_destroy, list) { - debugfs_slab_release(s); kfence_shutdown_cache(s); kmem_cache_release(s); } @@ -549,8 +536,8 @@ void slab_kmem_cache_release(struct kmem_cache *s) void kmem_cache_destroy(struct kmem_cache *s) { - int err = -EBUSY; bool rcu_set; + int err; if (unlikely(!s) || !kasan_check_byte(s)) return; @@ -558,11 +545,14 @@ void kmem_cache_destroy(struct kmem_cache *s) cpus_read_lock(); mutex_lock(&slab_mutex); - rcu_set = s->flags & SLAB_TYPESAFE_BY_RCU; - s->refcount--; - if (s->refcount) - goto out_unlock; + if (s->refcount) { + mutex_unlock(&slab_mutex); + cpus_read_unlock(); + return; + } + + rcu_set = s->flags & SLAB_TYPESAFE_BY_RCU; /* free asan quarantined objects */ kasan_cache_shutdown(s); @@ -571,24 +561,29 @@ void kmem_cache_destroy(struct kmem_cache *s) WARN(err, "%s %s: Slab cache still has objects when called from %pS", __func__, s->name, (void *)_RET_IP_); - if (err) - goto out_unlock; - list_del(&s->list); - if (rcu_set) { - list_add_tail(&s->list, &slab_caches_to_rcu_destroy); - schedule_work(&slab_caches_to_rcu_destroy_work); - } else { + if (!err && !rcu_set) kfence_shutdown_cache(s); - debugfs_slab_release(s); - } -out_unlock: mutex_unlock(&slab_mutex); cpus_read_unlock(); - if (!err && !rcu_set) + + if (slab_state >= FULL) + sysfs_slab_unlink(s); + debugfs_slab_release(s); + + if (err) + return; + + if (rcu_set) { + mutex_lock(&slab_mutex); + list_add_tail(&s->list, &slab_caches_to_rcu_destroy); + schedule_work(&slab_caches_to_rcu_destroy_work); + mutex_unlock(&slab_mutex); + } else { kmem_cache_release(s); + } } EXPORT_SYMBOL(kmem_cache_destroy); From f77d0cda4a8ebd070bfa1ef9a153c470ea3601ce Mon Sep 17 00:00:00 2001 From: Vlastimil Babka Date: Wed, 7 Aug 2024 12:31:16 +0200 Subject: [PATCH 175/573] mm, slab: move kfence_shutdown_cache() outside slab_mutex kfence_shutdown_cache() is called under slab_mutex when the cache is destroyed synchronously, and outside slab_mutex during the delayed destruction of SLAB_TYPESAFE_BY_RCU caches. It seems it should always be safe to call it outside of slab_mutex so we can just move the call to kmem_cache_release(), which is called outside. Reviewed-by: Jann Horn Signed-off-by: Vlastimil Babka --- mm/slab_common.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index db61df3b4282..a079b8540334 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -492,6 +492,7 @@ EXPORT_SYMBOL(kmem_buckets_create); */ static void kmem_cache_release(struct kmem_cache *s) { + kfence_shutdown_cache(s); if (__is_defined(SLAB_SUPPORTS_SYSFS) && slab_state >= FULL) sysfs_slab_release(s); else @@ -521,10 +522,8 @@ static void slab_caches_to_rcu_destroy_workfn(struct work_struct *work) rcu_barrier(); - list_for_each_entry_safe(s, s2, &to_destroy, list) { - kfence_shutdown_cache(s); + list_for_each_entry_safe(s, s2, &to_destroy, list) kmem_cache_release(s); - } } void slab_kmem_cache_release(struct kmem_cache *s) @@ -563,9 +562,6 @@ void kmem_cache_destroy(struct kmem_cache *s) list_del(&s->list); - if (!err && !rcu_set) - kfence_shutdown_cache(s); - mutex_unlock(&slab_mutex); cpus_read_unlock(); From 2eb14c1c2717396f2fb1e4a4c5a1ec87cdd174f6 Mon Sep 17 00:00:00 2001 From: Vlastimil Babka Date: Wed, 7 Aug 2024 12:31:17 +0200 Subject: [PATCH 176/573] mm, slab: reintroduce rcu_barrier() into kmem_cache_destroy() There used to be a rcu_barrier() for SLAB_TYPESAFE_BY_RCU caches in kmem_cache_destroy() until commit 657dc2f97220 ("slab: remove synchronous rcu_barrier() call in memcg cache release path") moved it to an asynchronous work that finishes the destroying of such caches. The motivation for that commit was the MEMCG_KMEM integration that at the time created and removed clones of the global slab caches together with their cgroups, and blocking cgroups removal was unwelcome. The implementation later changed to per-object memcg tracking using a single cache, so there should be no more need for a fast non-blocking kmem_cache_destroy(), which is typically only done when a module is unloaded etc. Going back to synchronous barrier has the following advantages: - simpler implementation - it's easier to test the result of kmem_cache_destroy() in a kunit test Thus effectively revert commit 657dc2f97220. It is not a 1:1 revert as the code has changed since. The main part is that kmem_cache_release(s) is always called from kmem_cache_destroy(), but for SLAB_TYPESAFE_BY_RCU caches there's a rcu_barrier() first. Suggested-by: Mateusz Guzik Reviewed-by: Jann Horn Signed-off-by: Vlastimil Babka --- mm/slab_common.c | 47 ++++------------------------------------------- 1 file changed, 4 insertions(+), 43 deletions(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index a079b8540334..c40227d5fa07 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -40,11 +40,6 @@ LIST_HEAD(slab_caches); DEFINE_MUTEX(slab_mutex); struct kmem_cache *kmem_cache; -static LIST_HEAD(slab_caches_to_rcu_destroy); -static void slab_caches_to_rcu_destroy_workfn(struct work_struct *work); -static DECLARE_WORK(slab_caches_to_rcu_destroy_work, - slab_caches_to_rcu_destroy_workfn); - /* * Set of flags that will prevent slab merging */ @@ -499,33 +494,6 @@ static void kmem_cache_release(struct kmem_cache *s) slab_kmem_cache_release(s); } -static void slab_caches_to_rcu_destroy_workfn(struct work_struct *work) -{ - LIST_HEAD(to_destroy); - struct kmem_cache *s, *s2; - - /* - * On destruction, SLAB_TYPESAFE_BY_RCU kmem_caches are put on the - * @slab_caches_to_rcu_destroy list. The slab pages are freed - * through RCU and the associated kmem_cache are dereferenced - * while freeing the pages, so the kmem_caches should be freed only - * after the pending RCU operations are finished. As rcu_barrier() - * is a pretty slow operation, we batch all pending destructions - * asynchronously. - */ - mutex_lock(&slab_mutex); - list_splice_init(&slab_caches_to_rcu_destroy, &to_destroy); - mutex_unlock(&slab_mutex); - - if (list_empty(&to_destroy)) - return; - - rcu_barrier(); - - list_for_each_entry_safe(s, s2, &to_destroy, list) - kmem_cache_release(s); -} - void slab_kmem_cache_release(struct kmem_cache *s) { __kmem_cache_release(s); @@ -535,7 +503,6 @@ void slab_kmem_cache_release(struct kmem_cache *s) void kmem_cache_destroy(struct kmem_cache *s) { - bool rcu_set; int err; if (unlikely(!s) || !kasan_check_byte(s)) @@ -551,8 +518,6 @@ void kmem_cache_destroy(struct kmem_cache *s) return; } - rcu_set = s->flags & SLAB_TYPESAFE_BY_RCU; - /* free asan quarantined objects */ kasan_cache_shutdown(s); @@ -572,14 +537,10 @@ void kmem_cache_destroy(struct kmem_cache *s) if (err) return; - if (rcu_set) { - mutex_lock(&slab_mutex); - list_add_tail(&s->list, &slab_caches_to_rcu_destroy); - schedule_work(&slab_caches_to_rcu_destroy_work); - mutex_unlock(&slab_mutex); - } else { - kmem_cache_release(s); - } + if (s->flags & SLAB_TYPESAFE_BY_RCU) + rcu_barrier(); + + kmem_cache_release(s); } EXPORT_SYMBOL(kmem_cache_destroy); From 2b55d6a42d14c8675e38d6d9adca3014fdf01951 Mon Sep 17 00:00:00 2001 From: "Uladzislau Rezki (Sony)" Date: Tue, 20 Aug 2024 17:59:35 +0200 Subject: [PATCH 177/573] rcu/kvfree: Add kvfree_rcu_barrier() API Add a kvfree_rcu_barrier() function. It waits until all in-flight pointers are freed over RCU machinery. It does not wait any GP completion and it is within its right to return immediately if there are no outstanding pointers. This function is useful when there is a need to guarantee that a memory is fully freed before destroying memory caches. For example, during unloading a kernel module. Signed-off-by: Uladzislau Rezki (Sony) Signed-off-by: Vlastimil Babka --- include/linux/rcutiny.h | 5 ++ include/linux/rcutree.h | 1 + kernel/rcu/tree.c | 109 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 107 insertions(+), 8 deletions(-) diff --git a/include/linux/rcutiny.h b/include/linux/rcutiny.h index d9ac7b136aea..522123050ff8 100644 --- a/include/linux/rcutiny.h +++ b/include/linux/rcutiny.h @@ -111,6 +111,11 @@ static inline void __kvfree_call_rcu(struct rcu_head *head, void *ptr) kvfree(ptr); } +static inline void kvfree_rcu_barrier(void) +{ + rcu_barrier(); +} + #ifdef CONFIG_KASAN_GENERIC void kvfree_call_rcu(struct rcu_head *head, void *ptr); #else diff --git a/include/linux/rcutree.h b/include/linux/rcutree.h index 254244202ea9..58e7db80f3a8 100644 --- a/include/linux/rcutree.h +++ b/include/linux/rcutree.h @@ -35,6 +35,7 @@ static inline void rcu_virt_note_context_switch(void) void synchronize_rcu_expedited(void); void kvfree_call_rcu(struct rcu_head *head, void *ptr); +void kvfree_rcu_barrier(void); void rcu_barrier(void); void rcu_momentary_dyntick_idle(void); diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c index e641cc681901..be00aac5f4e7 100644 --- a/kernel/rcu/tree.c +++ b/kernel/rcu/tree.c @@ -3584,18 +3584,15 @@ kvfree_rcu_drain_ready(struct kfree_rcu_cpu *krcp) } /* - * This function is invoked after the KFREE_DRAIN_JIFFIES timeout. + * Return: %true if a work is queued, %false otherwise. */ -static void kfree_rcu_monitor(struct work_struct *work) +static bool +kvfree_rcu_queue_batch(struct kfree_rcu_cpu *krcp) { - struct kfree_rcu_cpu *krcp = container_of(work, - struct kfree_rcu_cpu, monitor_work.work); unsigned long flags; + bool queued = false; int i, j; - // Drain ready for reclaim. - kvfree_rcu_drain_ready(krcp); - raw_spin_lock_irqsave(&krcp->lock, flags); // Attempt to start a new batch. @@ -3634,11 +3631,27 @@ static void kfree_rcu_monitor(struct work_struct *work) // be that the work is in the pending state when // channels have been detached following by each // other. - queue_rcu_work(system_wq, &krwp->rcu_work); + queued = queue_rcu_work(system_wq, &krwp->rcu_work); } } raw_spin_unlock_irqrestore(&krcp->lock, flags); + return queued; +} + +/* + * This function is invoked after the KFREE_DRAIN_JIFFIES timeout. + */ +static void kfree_rcu_monitor(struct work_struct *work) +{ + struct kfree_rcu_cpu *krcp = container_of(work, + struct kfree_rcu_cpu, monitor_work.work); + + // Drain ready for reclaim. + kvfree_rcu_drain_ready(krcp); + + // Queue a batch for a rest. + kvfree_rcu_queue_batch(krcp); // If there is nothing to detach, it means that our job is // successfully done here. In case of having at least one @@ -3859,6 +3872,86 @@ unlock_return: } EXPORT_SYMBOL_GPL(kvfree_call_rcu); +/** + * kvfree_rcu_barrier - Wait until all in-flight kvfree_rcu() complete. + * + * Note that a single argument of kvfree_rcu() call has a slow path that + * triggers synchronize_rcu() following by freeing a pointer. It is done + * before the return from the function. Therefore for any single-argument + * call that will result in a kfree() to a cache that is to be destroyed + * during module exit, it is developer's responsibility to ensure that all + * such calls have returned before the call to kmem_cache_destroy(). + */ +void kvfree_rcu_barrier(void) +{ + struct kfree_rcu_cpu_work *krwp; + struct kfree_rcu_cpu *krcp; + bool queued; + int i, cpu; + + /* + * Firstly we detach objects and queue them over an RCU-batch + * for all CPUs. Finally queued works are flushed for each CPU. + * + * Please note. If there are outstanding batches for a particular + * CPU, those have to be finished first following by queuing a new. + */ + for_each_possible_cpu(cpu) { + krcp = per_cpu_ptr(&krc, cpu); + + /* + * Check if this CPU has any objects which have been queued for a + * new GP completion. If not(means nothing to detach), we are done + * with it. If any batch is pending/running for this "krcp", below + * per-cpu flush_rcu_work() waits its completion(see last step). + */ + if (!need_offload_krc(krcp)) + continue; + + while (1) { + /* + * If we are not able to queue a new RCU work it means: + * - batches for this CPU are still in flight which should + * be flushed first and then repeat; + * - no objects to detach, because of concurrency. + */ + queued = kvfree_rcu_queue_batch(krcp); + + /* + * Bail out, if there is no need to offload this "krcp" + * anymore. As noted earlier it can run concurrently. + */ + if (queued || !need_offload_krc(krcp)) + break; + + /* There are ongoing batches. */ + for (i = 0; i < KFREE_N_BATCHES; i++) { + krwp = &(krcp->krw_arr[i]); + flush_rcu_work(&krwp->rcu_work); + } + } + } + + /* + * Now we guarantee that all objects are flushed. + */ + for_each_possible_cpu(cpu) { + krcp = per_cpu_ptr(&krc, cpu); + + /* + * A monitor work can drain ready to reclaim objects + * directly. Wait its completion if running or pending. + */ + cancel_delayed_work_sync(&krcp->monitor_work); + + for (i = 0; i < KFREE_N_BATCHES; i++) { + krwp = &(krcp->krw_arr[i]); + flush_rcu_work(&krwp->rcu_work); + } + } +} +EXPORT_SYMBOL_GPL(kvfree_rcu_barrier); + static unsigned long kfree_rcu_shrink_count(struct shrinker *shrink, struct shrink_control *sc) { From 6c6c47b063b593785202be158e61fe5c827d6677 Mon Sep 17 00:00:00 2001 From: Vlastimil Babka Date: Wed, 7 Aug 2024 12:31:19 +0200 Subject: [PATCH 178/573] mm, slab: call kvfree_rcu_barrier() from kmem_cache_destroy() We would like to replace call_rcu() users with kfree_rcu() where the existing callback is just a kmem_cache_free(). However this causes issues when the cache can be destroyed (such as due to module unload). Currently such modules should be issuing rcu_barrier() before kmem_cache_destroy() to have their call_rcu() callbacks processed first. This barrier is however not sufficient for kfree_rcu() in flight due to the batching introduced by a35d16905efc ("rcu: Add basic support for kfree_rcu() batching"). This is not a problem for kmalloc caches which are never destroyed, but since removing SLOB, kfree_rcu() is allowed also for any other cache, that might be destroyed. In order not to complicate the API, put the responsibility for handling outstanding kfree_rcu() in kmem_cache_destroy() itself. Use the newly introduced kvfree_rcu_barrier() to wait before destroying the cache. This is similar to how we issue rcu_barrier() for SLAB_TYPESAFE_BY_RCU caches, but has to be done earlier, as the latter only needs to wait for the empty slab pages to finish freeing, and not objects from the slab. Users of call_rcu() with arbitrary callbacks should still issue rcu_barrier() before destroying the cache and unloading the module, as kvfree_rcu_barrier() is not a superset of rcu_barrier() and the callbacks may be invoking module code or performing other actions that are necessary for a successful unload. Signed-off-by: Vlastimil Babka --- mm/slab_common.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mm/slab_common.c b/mm/slab_common.c index c40227d5fa07..1a2873293f5d 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -508,6 +508,9 @@ void kmem_cache_destroy(struct kmem_cache *s) if (unlikely(!s) || !kasan_check_byte(s)) return; + /* in-flight kfree_rcu()'s may include objects from our cache */ + kvfree_rcu_barrier(); + cpus_read_lock(); mutex_lock(&slab_mutex); From 4e1c44b3db79ba910adec32e2e1b920a0e34890a Mon Sep 17 00:00:00 2001 From: Vlastimil Babka Date: Wed, 7 Aug 2024 12:31:20 +0200 Subject: [PATCH 179/573] kunit, slub: add test_kfree_rcu() and test_leak_destroy() Add a test that will create cache, allocate one object, kfree_rcu() it and attempt to destroy it. As long as the usage of kvfree_rcu_barrier() in kmem_cache_destroy() works correctly, there should be no warnings in dmesg and the test should pass. Additionally add a test_leak_destroy() test that leaks an object on purpose and verifies that kmem_cache_destroy() catches it. Signed-off-by: Vlastimil Babka --- lib/slub_kunit.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/slub_kunit.c b/lib/slub_kunit.c index e6667a28c014..6e3a1e5a7142 100644 --- a/lib/slub_kunit.c +++ b/lib/slub_kunit.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "../mm/slab.h" static struct kunit_resource resource; @@ -157,6 +158,34 @@ static void test_kmalloc_redzone_access(struct kunit *test) kmem_cache_destroy(s); } +struct test_kfree_rcu_struct { + struct rcu_head rcu; +}; + +static void test_kfree_rcu(struct kunit *test) +{ + struct kmem_cache *s = test_kmem_cache_create("TestSlub_kfree_rcu", + sizeof(struct test_kfree_rcu_struct), + SLAB_NO_MERGE); + struct test_kfree_rcu_struct *p = kmem_cache_alloc(s, GFP_KERNEL); + + kfree_rcu(p, rcu); + kmem_cache_destroy(s); + + KUNIT_EXPECT_EQ(test, 0, slab_errors); +} + +static void test_leak_destroy(struct kunit *test) +{ + struct kmem_cache *s = test_kmem_cache_create("TestSlub_kfree_rcu", + 64, SLAB_NO_MERGE); + kmem_cache_alloc(s, GFP_KERNEL); + + kmem_cache_destroy(s); + + KUNIT_EXPECT_EQ(test, 1, slab_errors); +} + static int test_init(struct kunit *test) { slab_errors = 0; @@ -177,6 +206,8 @@ static struct kunit_case test_cases[] = { KUNIT_CASE(test_clobber_redzone_free), KUNIT_CASE(test_kmalloc_redzone_access), + KUNIT_CASE(test_kfree_rcu), + KUNIT_CASE(test_leak_destroy), {} }; From b3c34245756adada8a50bdaedbb3965b071c7b0a Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Fri, 9 Aug 2024 17:36:55 +0200 Subject: [PATCH 180/573] kasan: catch invalid free before SLUB reinitializes the object Currently, when KASAN is combined with init-on-free behavior, the initialization happens before KASAN's "invalid free" checks. More importantly, a subsequent commit will want to RCU-delay the actual SLUB freeing of an object, and we'd like KASAN to still validate synchronously that freeing the object is permitted. (Otherwise this change will make the existing testcase kmem_cache_invalid_free fail.) So add a new KASAN hook that allows KASAN to pre-validate a kmem_cache_free() operation before SLUB actually starts modifying the object or its metadata. Inside KASAN, this: - moves checks from poison_slab_object() into check_slab_allocation() - moves kasan_arch_is_ready() up into callers of poison_slab_object() - removes "ip" argument of poison_slab_object() and __kasan_slab_free() (since those functions no longer do any reporting) Acked-by: Vlastimil Babka #slub Reviewed-by: Andrey Konovalov Signed-off-by: Jann Horn Signed-off-by: Vlastimil Babka --- include/linux/kasan.h | 54 ++++++++++++++++++++++++++++++++++--- mm/kasan/common.c | 63 +++++++++++++++++++++++++------------------ mm/slub.c | 7 +++++ 3 files changed, 95 insertions(+), 29 deletions(-) diff --git a/include/linux/kasan.h b/include/linux/kasan.h index 70d6a8f6e25d..1570c7191176 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -175,13 +175,55 @@ static __always_inline void * __must_check kasan_init_slab_obj( return (void *)object; } -bool __kasan_slab_free(struct kmem_cache *s, void *object, - unsigned long ip, bool init); +bool __kasan_slab_pre_free(struct kmem_cache *s, void *object, + unsigned long ip); +/** + * kasan_slab_pre_free - Check whether freeing a slab object is safe. + * @object: Object to be freed. + * + * This function checks whether freeing the given object is safe. It may + * check for double-free and invalid-free bugs and report them. + * + * This function is intended only for use by the slab allocator. + * + * @Return true if freeing the object is unsafe; false otherwise. + */ +static __always_inline bool kasan_slab_pre_free(struct kmem_cache *s, + void *object) +{ + if (kasan_enabled()) + return __kasan_slab_pre_free(s, object, _RET_IP_); + return false; +} + +bool __kasan_slab_free(struct kmem_cache *s, void *object, bool init); +/** + * kasan_slab_free - Poison, initialize, and quarantine a slab object. + * @object: Object to be freed. + * @init: Whether to initialize the object. + * + * This function informs that a slab object has been freed and is not + * supposed to be accessed anymore, except for objects in + * SLAB_TYPESAFE_BY_RCU caches. + * + * For KASAN modes that have integrated memory initialization + * (kasan_has_integrated_init() == true), this function also initializes + * the object's memory. For other modes, the @init argument is ignored. + * + * This function might also take ownership of the object to quarantine it. + * When this happens, KASAN will defer freeing the object to a later + * stage and handle it internally until then. The return value indicates + * whether KASAN took ownership of the object. + * + * This function is intended only for use by the slab allocator. + * + * @Return true if KASAN took ownership of the object; false otherwise. + */ static __always_inline bool kasan_slab_free(struct kmem_cache *s, void *object, bool init) { if (kasan_enabled()) - return __kasan_slab_free(s, object, _RET_IP_, init); + return __kasan_slab_free(s, object, init); return false; } @@ -371,6 +413,12 @@ static inline void *kasan_init_slab_obj(struct kmem_cache *cache, { return (void *)object; } + +static inline bool kasan_slab_pre_free(struct kmem_cache *s, void *object) +{ + return false; +} + static inline bool kasan_slab_free(struct kmem_cache *s, void *object, bool init) { return false; diff --git a/mm/kasan/common.c b/mm/kasan/common.c index 85e7c6b4575c..f26bbc087b3b 100644 --- a/mm/kasan/common.c +++ b/mm/kasan/common.c @@ -208,15 +208,12 @@ void * __must_check __kasan_init_slab_obj(struct kmem_cache *cache, return (void *)object; } -static inline bool poison_slab_object(struct kmem_cache *cache, void *object, - unsigned long ip, bool init) +/* Returns true when freeing the object is not safe. */ +static bool check_slab_allocation(struct kmem_cache *cache, void *object, + unsigned long ip) { - void *tagged_object; + void *tagged_object = object; - if (!kasan_arch_is_ready()) - return false; - - tagged_object = object; object = kasan_reset_tag(object); if (unlikely(nearest_obj(cache, virt_to_slab(object), object) != object)) { @@ -224,37 +221,46 @@ static inline bool poison_slab_object(struct kmem_cache *cache, void *object, return true; } - /* RCU slabs could be legally used after free within the RCU period. */ - if (unlikely(cache->flags & SLAB_TYPESAFE_BY_RCU)) - return false; - if (!kasan_byte_accessible(tagged_object)) { kasan_report_invalid_free(tagged_object, ip, KASAN_REPORT_DOUBLE_FREE); return true; } + return false; +} + +static inline void poison_slab_object(struct kmem_cache *cache, void *object, + bool init) +{ + void *tagged_object = object; + + object = kasan_reset_tag(object); + + /* RCU slabs could be legally used after free within the RCU period. */ + if (unlikely(cache->flags & SLAB_TYPESAFE_BY_RCU)) + return; + kasan_poison(object, round_up(cache->object_size, KASAN_GRANULE_SIZE), KASAN_SLAB_FREE, init); if (kasan_stack_collection_enabled()) kasan_save_free_info(cache, tagged_object); - - return false; } -bool __kasan_slab_free(struct kmem_cache *cache, void *object, - unsigned long ip, bool init) +bool __kasan_slab_pre_free(struct kmem_cache *cache, void *object, + unsigned long ip) { - if (is_kfence_address(object)) + if (!kasan_arch_is_ready() || is_kfence_address(object)) + return false; + return check_slab_allocation(cache, object, ip); +} + +bool __kasan_slab_free(struct kmem_cache *cache, void *object, bool init) +{ + if (!kasan_arch_is_ready() || is_kfence_address(object)) return false; - /* - * If the object is buggy, do not let slab put the object onto the - * freelist. The object will thus never be allocated again and its - * metadata will never get released. - */ - if (poison_slab_object(cache, object, ip, init)) - return true; + poison_slab_object(cache, object, init); /* * If the object is put into quarantine, do not let slab put the object @@ -504,11 +510,16 @@ bool __kasan_mempool_poison_object(void *ptr, unsigned long ip) return true; } - if (is_kfence_address(ptr)) - return false; + if (is_kfence_address(ptr) || !kasan_arch_is_ready()) + return true; slab = folio_slab(folio); - return !poison_slab_object(slab->slab_cache, ptr, ip, false); + + if (check_slab_allocation(slab->slab_cache, ptr, ip)) + return false; + + poison_slab_object(slab->slab_cache, ptr, false); + return true; } void __kasan_mempool_unpoison_object(void *ptr, size_t size, unsigned long ip) diff --git a/mm/slub.c b/mm/slub.c index c9d8a2497fd6..4946488cb5a7 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -2226,6 +2226,13 @@ bool slab_free_hook(struct kmem_cache *s, void *x, bool init) if (kfence_free(x)) return false; + /* + * Give KASAN a chance to notice an invalid free operation before we + * modify the object. + */ + if (kasan_slab_pre_free(s, x)) + return false; + /* * As memory initialization might be integrated into KASAN, * kasan_slab_free and initialization memset's must be From b8c8ba73c68bb3c3e9dad22f488b86c540c839f9 Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Fri, 9 Aug 2024 17:36:56 +0200 Subject: [PATCH 181/573] slub: Introduce CONFIG_SLUB_RCU_DEBUG Currently, KASAN is unable to catch use-after-free in SLAB_TYPESAFE_BY_RCU slabs because use-after-free is allowed within the RCU grace period by design. Add a SLUB debugging feature which RCU-delays every individual kmem_cache_free() before either actually freeing the object or handing it off to KASAN, and change KASAN to poison freed objects as normal when this option is enabled. For now I've configured Kconfig.debug to default-enable this feature in the KASAN GENERIC and SW_TAGS modes; I'm not enabling it by default in HW_TAGS mode because I'm not sure if it might have unwanted performance degradation effects there. Note that this is mostly useful with KASAN in the quarantine-based GENERIC mode; SLAB_TYPESAFE_BY_RCU slabs are basically always also slabs with a ->ctor, and KASAN's assign_tag() currently has to assign fixed tags for those, reducing the effectiveness of SW_TAGS/HW_TAGS mode. (A possible future extension of this work would be to also let SLUB call the ->ctor() on every allocation instead of only when the slab page is allocated; then tag-based modes would be able to assign new tags on every reallocation.) Tested-by: syzbot+263726e59eab6b442723@syzkaller.appspotmail.com Reviewed-by: Andrey Konovalov Acked-by: Marco Elver Acked-by: Vlastimil Babka #slab Signed-off-by: Jann Horn Signed-off-by: Vlastimil Babka --- include/linux/kasan.h | 17 ++++++---- mm/Kconfig.debug | 32 ++++++++++++++++++ mm/kasan/common.c | 11 +++--- mm/kasan/kasan_test.c | 46 +++++++++++++++++++++++++ mm/slab_common.c | 16 +++++++++ mm/slub.c | 79 ++++++++++++++++++++++++++++++++++++++----- 6 files changed, 182 insertions(+), 19 deletions(-) diff --git a/include/linux/kasan.h b/include/linux/kasan.h index 1570c7191176..00a3bf7c0d8f 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -196,15 +196,18 @@ static __always_inline bool kasan_slab_pre_free(struct kmem_cache *s, return false; } -bool __kasan_slab_free(struct kmem_cache *s, void *object, bool init); +bool __kasan_slab_free(struct kmem_cache *s, void *object, bool init, + bool still_accessible); /** * kasan_slab_free - Poison, initialize, and quarantine a slab object. * @object: Object to be freed. * @init: Whether to initialize the object. + * @still_accessible: Whether the object contents are still accessible. * * This function informs that a slab object has been freed and is not - * supposed to be accessed anymore, except for objects in - * SLAB_TYPESAFE_BY_RCU caches. + * supposed to be accessed anymore, except when @still_accessible is set + * (indicating that the object is in a SLAB_TYPESAFE_BY_RCU cache and an RCU + * grace period might not have passed yet). * * For KASAN modes that have integrated memory initialization * (kasan_has_integrated_init() == true), this function also initializes @@ -220,10 +223,11 @@ bool __kasan_slab_free(struct kmem_cache *s, void *object, bool init); * @Return true if KASAN took ownership of the object; false otherwise. */ static __always_inline bool kasan_slab_free(struct kmem_cache *s, - void *object, bool init) + void *object, bool init, + bool still_accessible) { if (kasan_enabled()) - return __kasan_slab_free(s, object, init); + return __kasan_slab_free(s, object, init, still_accessible); return false; } @@ -419,7 +423,8 @@ static inline bool kasan_slab_pre_free(struct kmem_cache *s, void *object) return false; } -static inline bool kasan_slab_free(struct kmem_cache *s, void *object, bool init) +static inline bool kasan_slab_free(struct kmem_cache *s, void *object, + bool init, bool still_accessible) { return false; } diff --git a/mm/Kconfig.debug b/mm/Kconfig.debug index afc72fde0f03..41a58536531d 100644 --- a/mm/Kconfig.debug +++ b/mm/Kconfig.debug @@ -70,6 +70,38 @@ config SLUB_DEBUG_ON off in a kernel built with CONFIG_SLUB_DEBUG_ON by specifying "slab_debug=-". +config SLUB_RCU_DEBUG + bool "Enable UAF detection in TYPESAFE_BY_RCU caches (for KASAN)" + depends on SLUB_DEBUG + # SLUB_RCU_DEBUG should build fine without KASAN, but is currently useless + # without KASAN, so mark it as a dependency of KASAN for now. + depends on KASAN + default KASAN_GENERIC || KASAN_SW_TAGS + help + Make SLAB_TYPESAFE_BY_RCU caches behave approximately as if the cache + was not marked as SLAB_TYPESAFE_BY_RCU and every caller used + kfree_rcu() instead. + + This is intended for use in combination with KASAN, to enable KASAN to + detect use-after-free accesses in such caches. + (KFENCE is able to do that independent of this flag.) + + This might degrade performance. + Unfortunately this also prevents a very specific bug pattern from + triggering (insufficient checks against an object being recycled + within the RCU grace period); so this option can be turned off even on + KASAN builds, in case you want to test for such a bug. + + If you're using this for testing bugs / fuzzing and care about + catching all the bugs WAY more than performance, you might want to + also turn on CONFIG_RCU_STRICT_GRACE_PERIOD. + + WARNING: + This is designed as a debugging feature, not a security feature. + Objects are sometimes recycled without RCU delay under memory pressure. + + If unsure, say N. + config PAGE_OWNER bool "Track page owner" depends on DEBUG_KERNEL && STACKTRACE_SUPPORT diff --git a/mm/kasan/common.c b/mm/kasan/common.c index f26bbc087b3b..ed4873e18c75 100644 --- a/mm/kasan/common.c +++ b/mm/kasan/common.c @@ -230,14 +230,14 @@ static bool check_slab_allocation(struct kmem_cache *cache, void *object, } static inline void poison_slab_object(struct kmem_cache *cache, void *object, - bool init) + bool init, bool still_accessible) { void *tagged_object = object; object = kasan_reset_tag(object); /* RCU slabs could be legally used after free within the RCU period. */ - if (unlikely(cache->flags & SLAB_TYPESAFE_BY_RCU)) + if (unlikely(still_accessible)) return; kasan_poison(object, round_up(cache->object_size, KASAN_GRANULE_SIZE), @@ -255,12 +255,13 @@ bool __kasan_slab_pre_free(struct kmem_cache *cache, void *object, return check_slab_allocation(cache, object, ip); } -bool __kasan_slab_free(struct kmem_cache *cache, void *object, bool init) +bool __kasan_slab_free(struct kmem_cache *cache, void *object, bool init, + bool still_accessible) { if (!kasan_arch_is_ready() || is_kfence_address(object)) return false; - poison_slab_object(cache, object, init); + poison_slab_object(cache, object, init, still_accessible); /* * If the object is put into quarantine, do not let slab put the object @@ -518,7 +519,7 @@ bool __kasan_mempool_poison_object(void *ptr, unsigned long ip) if (check_slab_allocation(slab->slab_cache, ptr, ip)) return false; - poison_slab_object(slab->slab_cache, ptr, false); + poison_slab_object(slab->slab_cache, ptr, false, false); return true; } diff --git a/mm/kasan/kasan_test.c b/mm/kasan/kasan_test.c index 7b32be2a3cf0..567d33b493e2 100644 --- a/mm/kasan/kasan_test.c +++ b/mm/kasan/kasan_test.c @@ -996,6 +996,51 @@ static void kmem_cache_invalid_free(struct kunit *test) kmem_cache_destroy(cache); } +static void kmem_cache_rcu_uaf(struct kunit *test) +{ + char *p; + size_t size = 200; + struct kmem_cache *cache; + + KASAN_TEST_NEEDS_CONFIG_ON(test, CONFIG_SLUB_RCU_DEBUG); + + cache = kmem_cache_create("test_cache", size, 0, SLAB_TYPESAFE_BY_RCU, + NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cache); + + p = kmem_cache_alloc(cache, GFP_KERNEL); + if (!p) { + kunit_err(test, "Allocation failed: %s\n", __func__); + kmem_cache_destroy(cache); + return; + } + *p = 1; + + rcu_read_lock(); + + /* Free the object - this will internally schedule an RCU callback. */ + kmem_cache_free(cache, p); + + /* + * We should still be allowed to access the object at this point because + * the cache is SLAB_TYPESAFE_BY_RCU and we've been in an RCU read-side + * critical section since before the kmem_cache_free(). + */ + READ_ONCE(*p); + + rcu_read_unlock(); + + /* + * Wait for the RCU callback to execute; after this, the object should + * have actually been freed from KASAN's perspective. + */ + rcu_barrier(); + + KUNIT_EXPECT_KASAN_FAIL(test, READ_ONCE(*p)); + + kmem_cache_destroy(cache); +} + static void empty_cache_ctor(void *object) { } static void kmem_cache_double_destroy(struct kunit *test) @@ -1937,6 +1982,7 @@ static struct kunit_case kasan_kunit_test_cases[] = { KUNIT_CASE(kmem_cache_oob), KUNIT_CASE(kmem_cache_double_free), KUNIT_CASE(kmem_cache_invalid_free), + KUNIT_CASE(kmem_cache_rcu_uaf), KUNIT_CASE(kmem_cache_double_destroy), KUNIT_CASE(kmem_cache_accounted), KUNIT_CASE(kmem_cache_bulk), diff --git a/mm/slab_common.c b/mm/slab_common.c index 1a2873293f5d..884e8e70a56d 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -511,6 +511,22 @@ void kmem_cache_destroy(struct kmem_cache *s) /* in-flight kfree_rcu()'s may include objects from our cache */ kvfree_rcu_barrier(); + if (IS_ENABLED(CONFIG_SLUB_RCU_DEBUG) && + (s->flags & SLAB_TYPESAFE_BY_RCU)) { + /* + * Under CONFIG_SLUB_RCU_DEBUG, when objects in a + * SLAB_TYPESAFE_BY_RCU slab are freed, SLUB will internally + * defer their freeing with call_rcu(). + * Wait for such call_rcu() invocations here before actually + * destroying the cache. + * + * It doesn't matter that we haven't looked at the slab refcount + * yet - slabs with SLAB_TYPESAFE_BY_RCU can't be merged, so + * the refcount should be 1 here. + */ + rcu_barrier(); + } + cpus_read_lock(); mutex_lock(&slab_mutex); diff --git a/mm/slub.c b/mm/slub.c index 4946488cb5a7..95977f25a760 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -2200,16 +2200,30 @@ static inline void memcg_slab_free_hook(struct kmem_cache *s, struct slab *slab, } #endif /* CONFIG_MEMCG */ +#ifdef CONFIG_SLUB_RCU_DEBUG +static void slab_free_after_rcu_debug(struct rcu_head *rcu_head); + +struct rcu_delayed_free { + struct rcu_head head; + void *object; +}; +#endif + /* * Hooks for other subsystems that check memory allocations. In a typical * production configuration these hooks all should produce no code at all. * * Returns true if freeing of the object can proceed, false if its reuse - * was delayed by KASAN quarantine, or it was returned to KFENCE. + * was delayed by CONFIG_SLUB_RCU_DEBUG or KASAN quarantine, or it was returned + * to KFENCE. */ static __always_inline -bool slab_free_hook(struct kmem_cache *s, void *x, bool init) +bool slab_free_hook(struct kmem_cache *s, void *x, bool init, + bool after_rcu_delay) { + /* Are the object contents still accessible? */ + bool still_accessible = (s->flags & SLAB_TYPESAFE_BY_RCU) && !after_rcu_delay; + kmemleak_free_recursive(x, s->flags); kmsan_slab_free(s, x); @@ -2219,7 +2233,7 @@ bool slab_free_hook(struct kmem_cache *s, void *x, bool init) debug_check_no_obj_freed(x, s->object_size); /* Use KCSAN to help debug racy use-after-free. */ - if (!(s->flags & SLAB_TYPESAFE_BY_RCU)) + if (!still_accessible) __kcsan_check_access(x, s->object_size, KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT); @@ -2233,6 +2247,28 @@ bool slab_free_hook(struct kmem_cache *s, void *x, bool init) if (kasan_slab_pre_free(s, x)) return false; +#ifdef CONFIG_SLUB_RCU_DEBUG + if (still_accessible) { + struct rcu_delayed_free *delayed_free; + + delayed_free = kmalloc(sizeof(*delayed_free), GFP_NOWAIT); + if (delayed_free) { + /* + * Let KASAN track our call stack as a "related work + * creation", just like if the object had been freed + * normally via kfree_rcu(). + * We have to do this manually because the rcu_head is + * not located inside the object. + */ + kasan_record_aux_stack_noalloc(x); + + delayed_free->object = x; + call_rcu(&delayed_free->head, slab_free_after_rcu_debug); + return false; + } + } +#endif /* CONFIG_SLUB_RCU_DEBUG */ + /* * As memory initialization might be integrated into KASAN, * kasan_slab_free and initialization memset's must be @@ -2256,7 +2292,7 @@ bool slab_free_hook(struct kmem_cache *s, void *x, bool init) s->size - inuse - rsize); } /* KASAN might put x into memory quarantine, delaying its reuse. */ - return !kasan_slab_free(s, x, init); + return !kasan_slab_free(s, x, init, still_accessible); } static __fastpath_inline @@ -2270,7 +2306,7 @@ bool slab_free_freelist_hook(struct kmem_cache *s, void **head, void **tail, bool init; if (is_kfence_address(next)) { - slab_free_hook(s, next, false); + slab_free_hook(s, next, false, false); return false; } @@ -2285,7 +2321,7 @@ bool slab_free_freelist_hook(struct kmem_cache *s, void **head, void **tail, next = get_freepointer(s, object); /* If object's reuse doesn't have to be delayed */ - if (likely(slab_free_hook(s, object, init))) { + if (likely(slab_free_hook(s, object, init, false))) { /* Move object to the new freelist */ set_freepointer(s, object, *head); *head = object; @@ -4477,7 +4513,7 @@ void slab_free(struct kmem_cache *s, struct slab *slab, void *object, memcg_slab_free_hook(s, slab, &object, 1); alloc_tagging_slab_free_hook(s, slab, &object, 1); - if (likely(slab_free_hook(s, object, slab_want_init_on_free(s)))) + if (likely(slab_free_hook(s, object, slab_want_init_on_free(s), false))) do_slab_free(s, slab, object, object, 1, addr); } @@ -4486,7 +4522,7 @@ void slab_free(struct kmem_cache *s, struct slab *slab, void *object, static noinline void memcg_alloc_abort_single(struct kmem_cache *s, void *object) { - if (likely(slab_free_hook(s, object, slab_want_init_on_free(s)))) + if (likely(slab_free_hook(s, object, slab_want_init_on_free(s), false))) do_slab_free(s, virt_to_slab(object), object, object, 1, _RET_IP_); } #endif @@ -4505,6 +4541,33 @@ void slab_free_bulk(struct kmem_cache *s, struct slab *slab, void *head, do_slab_free(s, slab, head, tail, cnt, addr); } +#ifdef CONFIG_SLUB_RCU_DEBUG +static void slab_free_after_rcu_debug(struct rcu_head *rcu_head) +{ + struct rcu_delayed_free *delayed_free = + container_of(rcu_head, struct rcu_delayed_free, head); + void *object = delayed_free->object; + struct slab *slab = virt_to_slab(object); + struct kmem_cache *s; + + kfree(delayed_free); + + if (WARN_ON(is_kfence_address(object))) + return; + + /* find the object and the cache again */ + if (WARN_ON(!slab)) + return; + s = slab->slab_cache; + if (WARN_ON(!(s->flags & SLAB_TYPESAFE_BY_RCU))) + return; + + /* resume freeing */ + if (slab_free_hook(s, object, slab_want_init_on_free(s), true)) + do_slab_free(s, slab, object, object, 1, _THIS_IP_); +} +#endif /* CONFIG_SLUB_RCU_DEBUG */ + #ifdef CONFIG_KASAN_GENERIC void ___cache_free(struct kmem_cache *cache, void *x, unsigned long addr) { From 2d42d3ba443706c9164fa0bef4e5fd1c36bc1bd9 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Sat, 24 Aug 2024 01:12:01 +0200 Subject: [PATCH 182/573] iommu/arm-smmu-qcom: Work around SDM845 Adreno SMMU w/ 16K pages SDM845's Adreno SMMU is unique in that it actually advertizes support for 16K (and 32M) pages, which doesn't hold for newer SoCs. This however, seems either broken in the hardware implementation, the hypervisor middleware that abstracts the SMMU, or there's a bug in the Linux kernel somewhere down the line that nobody managed to track down. Booting SDM845 with 16K page sizes and drm/msm results in: *** gpu fault: ttbr0=0000000000000000 iova=000100000000c000 dir=READ type=TRANSLATION source=CP (0,0,0,0) right after loading the firmware. The GPU then starts spitting out illegal intstruction errors, as it's quite obvious that it got a bogus pointer. Moreover, it seems like this issue also concerns other implementations of SMMUv2 on Qualcomm SoCs, such as the one on SC7180. Hide 16K support on such instances to work around this. Reported-by: Sumit Semwal Signed-off-by: Konrad Dybcio Link: https://lore.kernel.org/r/20240824-topic-845_gpu_smmu-v2-1-a302b8acc052@quicinc.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c index b981ff25a983..087fb4f6f4d3 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c @@ -345,6 +345,14 @@ static int qcom_smmu_cfg_probe(struct arm_smmu_device *smmu) return 0; } +static int qcom_adreno_smmuv2_cfg_probe(struct arm_smmu_device *smmu) +{ + /* Support for 16K pages is advertised on some SoCs, but it doesn't seem to work */ + smmu->features &= ~ARM_SMMU_FEAT_FMT_AARCH64_16K; + + return 0; +} + static void qcom_smmu_write_s2cr(struct arm_smmu_device *smmu, int idx) { struct arm_smmu_s2cr *s2cr = smmu->s2crs + idx; @@ -443,6 +451,7 @@ static const struct arm_smmu_impl sdm845_smmu_500_impl = { static const struct arm_smmu_impl qcom_adreno_smmu_v2_impl = { .init_context = qcom_adreno_smmu_init_context, + .cfg_probe = qcom_adreno_smmuv2_cfg_probe, .def_domain_type = qcom_smmu_def_domain_type, .alloc_context_bank = qcom_adreno_smmu_alloc_context_bank, .write_sctlr = qcom_adreno_smmu_write_sctlr, From c3eddf5e8c30adb6f43fc0b149e88b9feb76f381 Mon Sep 17 00:00:00 2001 From: Yang Ruibin <11162571@vivo.com> Date: Tue, 27 Aug 2024 20:08:16 +0800 Subject: [PATCH 183/573] HSI: omap-ssi: Remove unnecessary debugfs_create_dir() error check Remove the debugfs_create_dir() error check. It's safe to pass in error pointers to the debugfs API, hence the user isn't supposed to include error checking of the return values. Signed-off-by: Yang Ruibin <11162571@vivo.com> Link: https://lore.kernel.org/r/20240827120816.3910198-1-11162571@vivo.com Signed-off-by: Sebastian Reichel --- drivers/hsi/controllers/omap_ssi_core.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/drivers/hsi/controllers/omap_ssi_core.c b/drivers/hsi/controllers/omap_ssi_core.c index 3140990a6164..15cb759151e6 100644 --- a/drivers/hsi/controllers/omap_ssi_core.c +++ b/drivers/hsi/controllers/omap_ssi_core.c @@ -116,22 +116,13 @@ static int ssi_debug_add_ctrl(struct hsi_controller *ssi) /* SSI controller */ omap_ssi->dir = debugfs_create_dir(dev_name(&ssi->device), NULL); - if (!omap_ssi->dir) - return -ENOMEM; + debugfs_create_file("regs", S_IRUGO, omap_ssi->dir, ssi, &ssi_regs_fops); - debugfs_create_file("regs", S_IRUGO, omap_ssi->dir, ssi, - &ssi_regs_fops); /* SSI GDD (DMA) */ dir = debugfs_create_dir("gdd", omap_ssi->dir); - if (!dir) - goto rback; debugfs_create_file("regs", S_IRUGO, dir, ssi, &ssi_gdd_regs_fops); return 0; -rback: - debugfs_remove_recursive(omap_ssi->dir); - - return -ENOMEM; } static void ssi_debug_remove_ctrl(struct hsi_controller *ssi) From 3beb2fb68184fda063cdd3cdd3bbe52c5cae56bb Mon Sep 17 00:00:00 2001 From: Yan Zhen Date: Thu, 22 Aug 2024 10:27:04 +0800 Subject: [PATCH 184/573] mm, slab: use kmem_cache_free() to free from kmem_buckets_cache In kmem_buckets_create(), the kmem_buckets object is allocated by kmem_cache_alloc() from kmem_buckets_cache, but in the failure case, it's freed by kfree(). This is not wrong, but using kmem_cache_free() is the more common pattern, so use it. Signed-off-by: Yan Zhen Reviewed-by: Christoph Lameter Signed-off-by: Vlastimil Babka --- mm/slab_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index dea2b05c0e3f..ca694f5553b4 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -495,7 +495,7 @@ kmem_buckets *kmem_buckets_create(const char *name, slab_flags_t flags, fail: for (idx = 0; idx < ARRAY_SIZE(kmalloc_caches[KMALLOC_NORMAL]); idx++) kmem_cache_destroy((*b)[idx]); - kfree(b); + kmem_cache_free(kmem_buckets_cache, b); return NULL; } From 85f72ffe0f302c0837dc9dad5ba75dfcd5b35acd Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Wed, 31 Jul 2024 13:12:51 -0600 Subject: [PATCH 185/573] hwmon: (vexpress) Use of_property_present() Use of_property_present() to test for property presence rather than of_get_property(). This is part of a larger effort to remove callers of of_get_property() and similar functions. of_get_property() leaks the DT property data pointer which is a problem for dynamically allocated nodes which may be freed. Signed-off-by: Rob Herring (Arm) Reviewed-by: Sudeep Holla Link: https://lore.kernel.org/r/20240731191312.1710417-13-robh@kernel.org Signed-off-by: Guenter Roeck --- drivers/hwmon/vexpress-hwmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/vexpress-hwmon.c b/drivers/hwmon/vexpress-hwmon.c index d82a3b454d0e..a2e350f52a9e 100644 --- a/drivers/hwmon/vexpress-hwmon.c +++ b/drivers/hwmon/vexpress-hwmon.c @@ -72,7 +72,7 @@ static umode_t vexpress_hwmon_attr_is_visible(struct kobject *kobj, struct device_attribute, attr); if (dev_attr->show == vexpress_hwmon_label_show && - !of_get_property(dev->of_node, "label", NULL)) + !of_property_present(dev->of_node, "label")) return 0; return attr->mode; From 3a3dbff8a71ae7c8c6fbf900864ab79fd001bd29 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 17 Jul 2024 12:13:12 -0700 Subject: [PATCH 186/573] hwmon: (lm92) Improve auto-detection accuracy Checking three configuration register bits and the manufacturer ID register to auto-detect LM92 is a bit vague. Repeat twice on replicated register addresses to improve detection accuracy. Check the manufacturer ID first and bail out immediately without reading the other register if there is a mismatch to reduce the number of i2c transfers needed in that case. Also explicitly test for an error from reading the configuration register to avoid potential situations where the returned error masked against 0xe0 is 0. While at it, drop "lm92: Found National Semiconductor LM92 chip" detection noise. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/lm92.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/drivers/hwmon/lm92.c b/drivers/hwmon/lm92.c index 46579a3e1715..e2d8c8afcbfa 100644 --- a/drivers/hwmon/lm92.c +++ b/drivers/hwmon/lm92.c @@ -272,20 +272,28 @@ static int lm92_detect(struct i2c_client *new_client, struct i2c_board_info *info) { struct i2c_adapter *adapter = new_client->adapter; - u8 config; - u16 man_id; + u8 config_addr = LM92_REG_CONFIG; + u8 man_id_addr = LM92_REG_MAN_ID; + int i, regval; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) return -ENODEV; - config = i2c_smbus_read_byte_data(new_client, LM92_REG_CONFIG); - man_id = i2c_smbus_read_word_data(new_client, LM92_REG_MAN_ID); - - if ((config & 0xe0) == 0x00 && man_id == 0x0180) - pr_info("lm92: Found National Semiconductor LM92 chip\n"); - else - return -ENODEV; + /* + * Register values repeat with multiples of 8. + * Read twice to improve detection accuracy. + */ + for (i = 0; i < 2; i++) { + regval = i2c_smbus_read_word_data(new_client, man_id_addr); + if (regval != 0x0180) + return -ENODEV; + regval = i2c_smbus_read_byte_data(new_client, config_addr); + if (regval < 0 || (regval & 0xe0)) + return -ENODEV; + config_addr += 8; + man_id_addr += 8; + } strscpy(info->type, "lm92", I2C_NAME_SIZE); From 82efdeff3ce722c74af7e95b7c8c060e272c4dd0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 16 Jul 2024 20:57:19 -0700 Subject: [PATCH 187/573] hwmon: (lm92) Reorder include files to alphabetic order Simplify driver maintenance by reordering files to alphabetic order. No functional change. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/lm92.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/lm92.c b/drivers/hwmon/lm92.c index e2d8c8afcbfa..b0179d72b779 100644 --- a/drivers/hwmon/lm92.c +++ b/drivers/hwmon/lm92.c @@ -27,15 +27,15 @@ * with the LM92. */ -#include -#include -#include -#include +#include #include #include -#include -#include +#include +#include #include +#include +#include +#include /* * The LM92 and MAX6635 have 2 two-state pins for address selection, From fcb49571b4d7a993190d4dc40331ac5c7350d2dd Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 16 Jul 2024 21:48:07 -0700 Subject: [PATCH 188/573] hwmon: (lm92) Replace chip IDs with limit register resolution The chip IDs are not used by the driver. Drop them. Use driver data to store the limit register resolution instead, and use this information when writing temperature limits to improve chip specific rounding and to avoid writing into unused register bits. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/lm92.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/lm92.c b/drivers/hwmon/lm92.c index b0179d72b779..5ff45a0033f1 100644 --- a/drivers/hwmon/lm92.c +++ b/drivers/hwmon/lm92.c @@ -43,8 +43,6 @@ */ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, I2C_CLIENT_END }; -enum chips { lm92, max6635 }; - /* The LM92 registers */ #define LM92_REG_CONFIG 0x01 /* 8-bit, RW */ #define LM92_REG_TEMP 0x00 /* 16-bit, RO */ @@ -66,10 +64,10 @@ static inline int TEMP_FROM_REG(s16 reg) return reg / 8 * 625 / 10; } -static inline s16 TEMP_TO_REG(long val) +static inline s16 TEMP_TO_REG(long val, int resolution) { val = clamp_val(val, -60000, 160000); - return val * 10 / 625 * 8; + return DIV_ROUND_CLOSEST(val << (resolution - 9), 1000) << (16 - resolution); } /* Alarm flags are stored in the 3 LSB of the temperature register */ @@ -99,6 +97,7 @@ static const u8 regs[t_num_regs] = { struct lm92_data { struct i2c_client *client; struct mutex update_lock; + int resolution; bool valid; /* false until following fields are valid */ unsigned long last_updated; /* in jiffies */ @@ -159,7 +158,7 @@ static ssize_t temp_store(struct device *dev, return err; mutex_lock(&data->update_lock); - data->temp[nr] = TEMP_TO_REG(val); + data->temp[nr] = TEMP_TO_REG(val, data->resolution); i2c_smbus_write_word_swapped(client, regs[nr], data->temp[nr]); mutex_unlock(&data->update_lock); return count; @@ -201,7 +200,8 @@ static ssize_t temp_hyst_store(struct device *dev, val = clamp_val(val, -120000, 220000); mutex_lock(&data->update_lock); data->temp[t_hyst] = - TEMP_TO_REG(TEMP_FROM_REG(data->temp[attr->index]) - val); + TEMP_TO_REG(TEMP_FROM_REG(data->temp[attr->index]) - val, + data->resolution); i2c_smbus_write_word_swapped(client, LM92_REG_TEMP_HYST, data->temp[t_hyst]); mutex_unlock(&data->update_lock); @@ -311,6 +311,7 @@ static int lm92_probe(struct i2c_client *new_client) return -ENOMEM; data->client = new_client; + data->resolution = (unsigned long)i2c_get_match_data(new_client); mutex_init(&data->update_lock); /* Initialize the chipset */ @@ -326,9 +327,10 @@ static int lm92_probe(struct i2c_client *new_client) * Module and driver stuff */ +/* .driver_data is limit register resolution */ static const struct i2c_device_id lm92_id[] = { - { "lm92", lm92 }, - { "max6635", max6635 }, + { "lm92", 13 }, + { "max6635", 9 }, { } }; MODULE_DEVICE_TABLE(i2c, lm92_id); From 1359590a5c48d1d28cd11808178931f146a539e2 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 16 Jul 2024 17:54:25 -0700 Subject: [PATCH 189/573] hwmon: (lm92) Convert to use regmap Use regmap for local caching and for multi-byte operations to be able to use regmap API functions and to reduce the need for locking in the driver. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 1 + drivers/hwmon/lm92.c | 204 +++++++++++++++++++++++++++--------------- 2 files changed, 131 insertions(+), 74 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e838a55bb3cb..437334d10d3b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1511,6 +1511,7 @@ config SENSORS_LM90 config SENSORS_LM92 tristate "National Semiconductor LM92 and compatibles" depends on I2C + select REGMAP_I2C help If you say yes here you get support for National Semiconductor LM92 and Maxim MAX6635 sensor chips. diff --git a/drivers/hwmon/lm92.c b/drivers/hwmon/lm92.c index 5ff45a0033f1..89befc7f59a5 100644 --- a/drivers/hwmon/lm92.c +++ b/drivers/hwmon/lm92.c @@ -32,9 +32,9 @@ #include #include #include -#include #include #include +#include #include /* @@ -81,65 +81,40 @@ enum temp_index { t_crit, t_min, t_max, - t_hyst, t_num_regs }; -static const u8 regs[t_num_regs] = { +static const u8 lm92_regs[t_num_regs] = { [t_input] = LM92_REG_TEMP, [t_crit] = LM92_REG_TEMP_CRIT, [t_min] = LM92_REG_TEMP_LOW, [t_max] = LM92_REG_TEMP_HIGH, - [t_hyst] = LM92_REG_TEMP_HYST, }; /* Client data (each client gets its own) */ struct lm92_data { - struct i2c_client *client; + struct regmap *regmap; struct mutex update_lock; int resolution; - bool valid; /* false until following fields are valid */ - unsigned long last_updated; /* in jiffies */ - - /* registers values */ - s16 temp[t_num_regs]; /* index with enum temp_index */ }; /* * Sysfs attributes and callback functions */ -static struct lm92_data *lm92_update_device(struct device *dev) -{ - struct lm92_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - int i; - - mutex_lock(&data->update_lock); - - if (time_after(jiffies, data->last_updated + HZ) || - !data->valid) { - dev_dbg(&client->dev, "Updating lm92 data\n"); - for (i = 0; i < t_num_regs; i++) { - data->temp[i] = - i2c_smbus_read_word_swapped(client, regs[i]); - } - data->last_updated = jiffies; - data->valid = true; - } - - mutex_unlock(&data->update_lock); - - return data; -} - static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct lm92_data *data = lm92_update_device(dev); + struct lm92_data *data = dev_get_drvdata(dev); + u32 temp; + int err; - return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index])); + err = regmap_read(data->regmap, lm92_regs[attr->index], &temp); + if (err) + return err; + + return sprintf(buf, "%d\n", TEMP_FROM_REG(temp)); } static ssize_t temp_store(struct device *dev, @@ -148,7 +123,7 @@ static ssize_t temp_store(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct lm92_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; + struct regmap *regmap = data->regmap; int nr = attr->index; long val; int err; @@ -157,10 +132,9 @@ static ssize_t temp_store(struct device *dev, if (err) return err; - mutex_lock(&data->update_lock); - data->temp[nr] = TEMP_TO_REG(val, data->resolution); - i2c_smbus_write_word_swapped(client, regs[nr], data->temp[nr]); - mutex_unlock(&data->update_lock); + err = regmap_write(regmap, lm92_regs[nr], TEMP_TO_REG(val, data->resolution)); + if (err) + return err; return count; } @@ -168,28 +142,42 @@ static ssize_t temp_hyst_show(struct device *dev, struct device_attribute *devattr, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct lm92_data *data = lm92_update_device(dev); + u32 regs[2] = { lm92_regs[attr->index], LM92_REG_TEMP_HYST }; + struct lm92_data *data = dev_get_drvdata(dev); + u16 regvals[2]; + int err; - return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index]) - - TEMP_FROM_REG(data->temp[t_hyst])); + err = regmap_multi_reg_read(data->regmap, regs, regvals, 2); + if (err) + return err; + + return sprintf(buf, "%d\n", + TEMP_FROM_REG(regvals[0]) - TEMP_FROM_REG(regvals[1])); } static ssize_t temp1_min_hyst_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct lm92_data *data = lm92_update_device(dev); + static u32 regs[2] = { LM92_REG_TEMP_LOW, LM92_REG_TEMP_HYST }; + struct lm92_data *data = dev_get_drvdata(dev); + u16 regvals[2]; + int err; - return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[t_min]) - + TEMP_FROM_REG(data->temp[t_hyst])); + err = regmap_multi_reg_read(data->regmap, regs, regvals, 2); + if (err) + return err; + + return sprintf(buf, "%d\n", + TEMP_FROM_REG(regvals[0]) + TEMP_FROM_REG(regvals[1])); } static ssize_t temp_hyst_store(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct lm92_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; + struct regmap *regmap = data->regmap; + u32 temp; long val; int err; @@ -199,29 +187,45 @@ static ssize_t temp_hyst_store(struct device *dev, val = clamp_val(val, -120000, 220000); mutex_lock(&data->update_lock); - data->temp[t_hyst] = - TEMP_TO_REG(TEMP_FROM_REG(data->temp[attr->index]) - val, - data->resolution); - i2c_smbus_write_word_swapped(client, LM92_REG_TEMP_HYST, - data->temp[t_hyst]); + err = regmap_read(regmap, LM92_REG_TEMP_CRIT, &temp); + if (err) + goto unlock; + val = TEMP_TO_REG(TEMP_FROM_REG(temp) - val, data->resolution); + err = regmap_write(regmap, LM92_REG_TEMP_HYST, val); +unlock: mutex_unlock(&data->update_lock); + if (err) + return err; return count; } static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct lm92_data *data = lm92_update_device(dev); + struct lm92_data *data = dev_get_drvdata(dev); + u32 temp; + int err; - return sprintf(buf, "%d\n", ALARMS_FROM_REG(data->temp[t_input])); + err = regmap_read(data->regmap, LM92_REG_TEMP, &temp); + if (err) + return err; + + return sprintf(buf, "%d\n", ALARMS_FROM_REG(temp)); } static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct lm92_data *data = dev_get_drvdata(dev); int bitnr = to_sensor_dev_attr(attr)->index; - struct lm92_data *data = lm92_update_device(dev); - return sprintf(buf, "%d\n", (data->temp[t_input] >> bitnr) & 1); + u32 temp; + int err; + + err = regmap_read(data->regmap, LM92_REG_TEMP, &temp); + if (err) + return err; + + return sprintf(buf, "%d\n", (temp >> bitnr) & 1); } static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, t_input); @@ -240,15 +244,9 @@ static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 1); * Detection and registration */ -static void lm92_init_client(struct i2c_client *client) +static int lm92_init_client(struct regmap *regmap) { - u8 config; - - /* Start the conversions if needed */ - config = i2c_smbus_read_byte_data(client, LM92_REG_CONFIG); - if (config & 0x01) - i2c_smbus_write_byte_data(client, LM92_REG_CONFIG, - config & 0xFE); + return regmap_clear_bits(regmap, LM92_REG_CONFIG, 0x01); } static struct attribute *lm92_attrs[] = { @@ -300,25 +298,83 @@ static int lm92_detect(struct i2c_client *new_client, return 0; } -static int lm92_probe(struct i2c_client *new_client) +/* regmap */ + +static int lm92_reg_read(void *context, unsigned int reg, unsigned int *val) { + int ret; + + if (reg == LM92_REG_CONFIG) + ret = i2c_smbus_read_byte_data(context, reg); + else + ret = i2c_smbus_read_word_swapped(context, reg); + if (ret < 0) + return ret; + + *val = ret; + return 0; +} + +static int lm92_reg_write(void *context, unsigned int reg, unsigned int val) +{ + if (reg == LM92_REG_CONFIG) + return i2c_smbus_write_byte_data(context, LM92_REG_CONFIG, val); + + return i2c_smbus_write_word_swapped(context, reg, val); +} + +static bool lm92_regmap_is_volatile(struct device *dev, unsigned int reg) +{ + return reg == LM92_REG_TEMP; +} + +static bool lm92_regmap_is_writeable(struct device *dev, unsigned int reg) +{ + return reg >= LM92_REG_CONFIG; +} + +static const struct regmap_config lm92_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = LM92_REG_TEMP_HIGH, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = lm92_regmap_is_volatile, + .writeable_reg = lm92_regmap_is_writeable, +}; + +static const struct regmap_bus lm92_regmap_bus = { + .reg_write = lm92_reg_write, + .reg_read = lm92_reg_read, +}; + +static int lm92_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; struct device *hwmon_dev; struct lm92_data *data; + struct regmap *regmap; + int err; - data = devm_kzalloc(&new_client->dev, sizeof(struct lm92_data), - GFP_KERNEL); + regmap = devm_regmap_init(dev, &lm92_regmap_bus, client, + &lm92_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + data = devm_kzalloc(dev, sizeof(struct lm92_data), GFP_KERNEL); if (!data) return -ENOMEM; - data->client = new_client; - data->resolution = (unsigned long)i2c_get_match_data(new_client); + data->regmap = regmap; + data->resolution = (unsigned long)i2c_get_match_data(client); mutex_init(&data->update_lock); /* Initialize the chipset */ - lm92_init_client(new_client); + err = lm92_init_client(regmap); + if (err) + return err; - hwmon_dev = devm_hwmon_device_register_with_groups(&new_client->dev, - new_client->name, + hwmon_dev = devm_hwmon_device_register_with_groups(dev, + client->name, data, lm92_groups); return PTR_ERR_OR_ZERO(hwmon_dev); } From c21eced1157d8b78745332b2585e47eb459d6a40 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 16 Jul 2024 21:46:43 -0700 Subject: [PATCH 190/573] hwmon: (lm92) Convert to with_info hwmon API Convert driver to with_info hwmon API to simplify the code and to reduce its size. No functional change. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/lm92.c | 347 +++++++++++++++++++++++-------------------- 1 file changed, 188 insertions(+), 159 deletions(-) diff --git a/drivers/hwmon/lm92.c b/drivers/hwmon/lm92.c index 89befc7f59a5..0be439b38ee1 100644 --- a/drivers/hwmon/lm92.c +++ b/drivers/hwmon/lm92.c @@ -29,7 +29,6 @@ #include #include -#include #include #include #include @@ -76,21 +75,6 @@ static inline u8 ALARMS_FROM_REG(s16 reg) return reg & 0x0007; } -enum temp_index { - t_input, - t_crit, - t_min, - t_max, - t_num_regs -}; - -static const u8 lm92_regs[t_num_regs] = { - [t_input] = LM92_REG_TEMP, - [t_crit] = LM92_REG_TEMP_CRIT, - [t_min] = LM92_REG_TEMP_LOW, - [t_max] = LM92_REG_TEMP_HIGH, -}; - /* Client data (each client gets its own) */ struct lm92_data { struct regmap *regmap; @@ -98,147 +82,209 @@ struct lm92_data { int resolution; }; -/* - * Sysfs attributes and callback functions - */ - -static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, - char *buf) +static int lm92_temp_read(struct lm92_data *data, u32 attr, int channel, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct lm92_data *data = dev_get_drvdata(dev); - u32 temp; - int err; - - err = regmap_read(data->regmap, lm92_regs[attr->index], &temp); - if (err) - return err; - - return sprintf(buf, "%d\n", TEMP_FROM_REG(temp)); -} - -static ssize_t temp_store(struct device *dev, - struct device_attribute *devattr, const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct lm92_data *data = dev_get_drvdata(dev); - struct regmap *regmap = data->regmap; - int nr = attr->index; - long val; - int err; - - err = kstrtol(buf, 10, &val); - if (err) - return err; - - err = regmap_write(regmap, lm92_regs[nr], TEMP_TO_REG(val, data->resolution)); - if (err) - return err; - return count; -} - -static ssize_t temp_hyst_show(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - u32 regs[2] = { lm92_regs[attr->index], LM92_REG_TEMP_HYST }; - struct lm92_data *data = dev_get_drvdata(dev); - u16 regvals[2]; - int err; - - err = regmap_multi_reg_read(data->regmap, regs, regvals, 2); - if (err) - return err; - - return sprintf(buf, "%d\n", - TEMP_FROM_REG(regvals[0]) - TEMP_FROM_REG(regvals[1])); -} - -static ssize_t temp1_min_hyst_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - static u32 regs[2] = { LM92_REG_TEMP_LOW, LM92_REG_TEMP_HYST }; - struct lm92_data *data = dev_get_drvdata(dev); - u16 regvals[2]; - int err; - - err = regmap_multi_reg_read(data->regmap, regs, regvals, 2); - if (err) - return err; - - return sprintf(buf, "%d\n", - TEMP_FROM_REG(regvals[0]) + TEMP_FROM_REG(regvals[1])); -} - -static ssize_t temp_hyst_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - struct lm92_data *data = dev_get_drvdata(dev); + int reg = -1, hyst_reg = -1, alarm_bit = 0; struct regmap *regmap = data->regmap; u32 temp; - long val; - int err; + int ret; - err = kstrtol(buf, 10, &val); - if (err) - return err; + switch (attr) { + case hwmon_temp_input: + reg = LM92_REG_TEMP; + break; + case hwmon_temp_min: + reg = LM92_REG_TEMP_LOW; + break; + case hwmon_temp_max: + reg = LM92_REG_TEMP_HIGH; + break; + case hwmon_temp_crit: + reg = LM92_REG_TEMP_CRIT; + break; + case hwmon_temp_min_hyst: + hyst_reg = LM92_REG_TEMP_LOW; + break; + case hwmon_temp_max_hyst: + hyst_reg = LM92_REG_TEMP_HIGH; + break; + case hwmon_temp_crit_hyst: + hyst_reg = LM92_REG_TEMP_CRIT; + break; + case hwmon_temp_min_alarm: + alarm_bit = 0; + break; + case hwmon_temp_max_alarm: + alarm_bit = 1; + break; + case hwmon_temp_crit_alarm: + alarm_bit = 2; + break; + default: + return -EOPNOTSUPP; + } + if (reg >= 0) { + ret = regmap_read(regmap, reg, &temp); + if (ret < 0) + return ret; + *val = TEMP_FROM_REG(temp); + } else if (hyst_reg >= 0) { + u32 regs[2] = { hyst_reg, LM92_REG_TEMP_HYST }; + u16 regvals[2]; - val = clamp_val(val, -120000, 220000); - mutex_lock(&data->update_lock); - err = regmap_read(regmap, LM92_REG_TEMP_CRIT, &temp); - if (err) - goto unlock; - val = TEMP_TO_REG(TEMP_FROM_REG(temp) - val, data->resolution); - err = regmap_write(regmap, LM92_REG_TEMP_HYST, val); + ret = regmap_multi_reg_read(regmap, regs, regvals, 2); + if (ret) + return ret; + if (attr == hwmon_temp_min_hyst) + *val = TEMP_FROM_REG(regvals[0]) + TEMP_FROM_REG(regvals[1]); + else + *val = TEMP_FROM_REG(regvals[0]) - TEMP_FROM_REG(regvals[1]); + } else { + ret = regmap_read(regmap, LM92_REG_TEMP, &temp); + if (ret) + return ret; + *val = !!(temp & BIT(alarm_bit)); + } + return 0; +} + +static int lm92_chip_read(struct lm92_data *data, u32 attr, long *val) +{ + u32 temp; + int ret; + + switch (attr) { + case hwmon_chip_alarms: + ret = regmap_read(data->regmap, LM92_REG_TEMP, &temp); + if (ret) + return ret; + *val = ALARMS_FROM_REG(temp); + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int lm92_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct lm92_data *data = dev_get_drvdata(dev); + + switch (type) { + case hwmon_chip: + return lm92_chip_read(data, attr, val); + case hwmon_temp: + return lm92_temp_read(data, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int lm92_temp_write(struct lm92_data *data, u32 attr, long val) +{ + struct regmap *regmap = data->regmap; + int reg, err; + u32 temp; + + switch (attr) { + case hwmon_temp_min: + reg = LM92_REG_TEMP_LOW; + break; + case hwmon_temp_max: + reg = LM92_REG_TEMP_HIGH; + break; + case hwmon_temp_crit: + reg = LM92_REG_TEMP_CRIT; + break; + case hwmon_temp_crit_hyst: + val = clamp_val(val, -120000, 220000); + mutex_lock(&data->update_lock); + err = regmap_read(regmap, LM92_REG_TEMP_CRIT, &temp); + if (err) + goto unlock; + val = TEMP_TO_REG(TEMP_FROM_REG(temp) - val, data->resolution); + err = regmap_write(regmap, LM92_REG_TEMP_HYST, val); unlock: - mutex_unlock(&data->update_lock); - if (err) + mutex_unlock(&data->update_lock); return err; - return count; + default: + return -EOPNOTSUPP; + } + return regmap_write(regmap, reg, TEMP_TO_REG(val, data->resolution)); } -static ssize_t alarms_show(struct device *dev, struct device_attribute *attr, - char *buf) +static int lm92_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) { struct lm92_data *data = dev_get_drvdata(dev); - u32 temp; - int err; - err = regmap_read(data->regmap, LM92_REG_TEMP, &temp); - if (err) - return err; - - return sprintf(buf, "%d\n", ALARMS_FROM_REG(temp)); + switch (type) { + case hwmon_temp: + return lm92_temp_write(data, attr, val); + default: + return -EOPNOTSUPP; + } } -static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, - char *buf) +static umode_t lm92_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) { - struct lm92_data *data = dev_get_drvdata(dev); - int bitnr = to_sensor_dev_attr(attr)->index; - u32 temp; - int err; - - err = regmap_read(data->regmap, LM92_REG_TEMP, &temp); - if (err) - return err; - - return sprintf(buf, "%d\n", (temp >> bitnr) & 1); + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_alarms: + return 0444; + default: + break; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + case hwmon_temp_crit_hyst: + return 0644; + case hwmon_temp_input: + case hwmon_temp_min_hyst: + case hwmon_temp_max_hyst: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + return 0444; + default: + break; + } + break; + default: + break; + } + return 0; } -static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, t_input); -static SENSOR_DEVICE_ATTR_RW(temp1_crit, temp, t_crit); -static SENSOR_DEVICE_ATTR_RW(temp1_crit_hyst, temp_hyst, t_crit); -static SENSOR_DEVICE_ATTR_RW(temp1_min, temp, t_min); -static DEVICE_ATTR_RO(temp1_min_hyst); -static SENSOR_DEVICE_ATTR_RW(temp1_max, temp, t_max); -static SENSOR_DEVICE_ATTR_RO(temp1_max_hyst, temp_hyst, t_max); -static DEVICE_ATTR_RO(alarms); -static SENSOR_DEVICE_ATTR_RO(temp1_crit_alarm, alarm, 2); -static SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, alarm, 0); -static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 1); +static const struct hwmon_channel_info * const lm92_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_ALARMS), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | + HWMON_T_MIN | HWMON_T_MIN_HYST | + HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM), + NULL +}; + +static const struct hwmon_ops lm92_hwmon_ops = { + .is_visible = lm92_is_visible, + .read = lm92_read, + .write = lm92_write, +}; + +static const struct hwmon_chip_info lm92_chip_info = { + .ops = &lm92_hwmon_ops, + .info = lm92_info, +}; /* * Detection and registration @@ -249,22 +295,6 @@ static int lm92_init_client(struct regmap *regmap) return regmap_clear_bits(regmap, LM92_REG_CONFIG, 0x01); } -static struct attribute *lm92_attrs[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp1_crit.dev_attr.attr, - &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &dev_attr_temp1_min_hyst.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, - &dev_attr_alarms.attr, - &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - NULL -}; -ATTRIBUTE_GROUPS(lm92); - /* Return 0 if detection is successful, -ENODEV otherwise */ static int lm92_detect(struct i2c_client *new_client, struct i2c_board_info *info) @@ -373,9 +403,8 @@ static int lm92_probe(struct i2c_client *client) if (err) return err; - hwmon_dev = devm_hwmon_device_register_with_groups(dev, - client->name, - data, lm92_groups); + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &lm92_chip_info, NULL); return PTR_ERR_OR_ZERO(hwmon_dev); } From 05aa8cbeabc07b07ff7f035e9b848aa008b831ee Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 28 Jul 2024 15:52:25 -0700 Subject: [PATCH 191/573] hwmon: (lm92) Update documentation Update datasheet references. Replace misleading 'force parameter needed' with 'must be instantiated explicitly'. Explain the reason for the missing auto-detection. Mention all supported chips in Kconfig. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- Documentation/hwmon/lm92.rst | 26 +++++++++++++------------- drivers/hwmon/Kconfig | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Documentation/hwmon/lm92.rst b/Documentation/hwmon/lm92.rst index c131b923ed36..d71cdb2af339 100644 --- a/Documentation/hwmon/lm92.rst +++ b/Documentation/hwmon/lm92.rst @@ -3,29 +3,29 @@ Kernel driver lm92 Supported chips: - * National Semiconductor LM92 + * National Semiconductor / Texas Instruments LM92 Prefix: 'lm92' Addresses scanned: I2C 0x48 - 0x4b - Datasheet: http://www.national.com/pf/LM/LM92.html + Datasheet: https://www.ti.com/lit/gpn/LM92 - * National Semiconductor LM76 + * National Semiconductor / Texas Instruments LM76 Prefix: 'lm92' - Addresses scanned: none, force parameter needed + Addresses scanned: none, must be instantiated explicitly - Datasheet: http://www.national.com/pf/LM/LM76.html + Datasheet: https://www.ti.com/lit/gpn/LM76 - * Maxim MAX6633/MAX6634/MAX6635 + * Maxim /Analog Devices MAX6633/MAX6634/MAX6635 Prefix: 'max6635' - Addresses scanned: none, force parameter needed + Addresses scanned: none, must be instantiated explicitly - Datasheet: http://www.maxim-ic.com/quick_view2.cfm/qv_pk/3074 + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max6633-max6635.pdf Authors: @@ -36,13 +36,13 @@ Authors: Description ----------- -This driver implements support for the National Semiconductor LM92 -temperature sensor. +This driver implements support for the National Semiconductor / Texas +Instruments LM92 temperature sensor. Each LM92 temperature sensor supports a single temperature sensor. There are alarms for high, low, and critical thresholds. There's also an hysteresis to control the thresholds for resetting alarms. -Support was added later for the LM76 and Maxim MAX6633/MAX6634/MAX6635, -which are mostly compatible. They have not all been tested, so you -may need to use the force parameter. +The driver also supports LM76 and Maxim MAX6633/MAX6634/MAX6635, which are +mostly compatible but do not have a vendor ID register and therefore must be +instantiated explicitly. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 437334d10d3b..810c7d907235 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1514,7 +1514,7 @@ config SENSORS_LM92 select REGMAP_I2C help If you say yes here you get support for National Semiconductor LM92 - and Maxim MAX6635 sensor chips. + and LM76 as well as Maxim MAX6633/6634/6635 sensor chips. This driver can also be built as a module. If so, the module will be called lm92. From f775f6d17035408be4a4b30c55d812fb7e6aab57 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Tue, 13 Aug 2024 00:59:53 +0200 Subject: [PATCH 192/573] hwmon: (chipcap2) Drop cc2_disable() in the probe and return dev_err_probe() There is no need to actively disable a regulator that has not been enabled by the driver, which makes the call to cc2_disable() in the probe function meaningless, because the probe function never enables the device's dedicated regulator. Once the call to cc2_disable() is dropped, the error paths can directly return dev_err_probe() in all cases. Signed-off-by: Javier Carrasco Message-ID: <20240813-chipcap2-probe-improvements-v2-1-e9a2932a8a00@gmail.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index 6ccceae21f70..edf454474f11 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -740,37 +740,26 @@ static int cc2_probe(struct i2c_client *client) data->client = client; data->regulator = devm_regulator_get_exclusive(dev, "vdd"); - if (IS_ERR(data->regulator)) { - dev_err_probe(dev, PTR_ERR(data->regulator), - "Failed to get regulator\n"); - return PTR_ERR(data->regulator); - } + if (IS_ERR(data->regulator)) + return dev_err_probe(dev, PTR_ERR(data->regulator), + "Failed to get regulator\n"); ret = cc2_request_ready_irq(data, dev); - if (ret) { - dev_err_probe(dev, ret, "Failed to request ready irq\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "Failed to request ready irq\n"); ret = cc2_request_alarm_irqs(data, dev); - if (ret) { - dev_err_probe(dev, ret, "Failed to request alarm irqs\n"); - goto disable; - } + if (ret) + return dev_err_probe(dev, ret, "Failed to request alarm irqs\n"); data->hwmon = devm_hwmon_device_register_with_info(dev, client->name, data, &cc2_chip_info, NULL); - if (IS_ERR(data->hwmon)) { - dev_err_probe(dev, PTR_ERR(data->hwmon), - "Failed to register hwmon device\n"); - ret = PTR_ERR(data->hwmon); - } + if (IS_ERR(data->hwmon)) + return dev_err_probe(dev, PTR_ERR(data->hwmon), + "Failed to register hwmon device\n"); -disable: - cc2_disable(data); - - return ret; + return 0; } static void cc2_remove(struct i2c_client *client) From 1f432e4cf1dd3ecfec5ed80051b4611632a0fd51 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Mon, 12 Aug 2024 18:26:38 +0300 Subject: [PATCH 193/573] hwmon: (nct6775) add G15CF to ASUS WMI monitoring list Boards G15CF has got a nct6775 chip, but by default there's no use of it because of resource conflict with WMI method. Add the board to the WMI monitoring list. Link: https://bugzilla.kernel.org/show_bug.cgi?id=204807 Signed-off-by: Denis Pauk Tested-by: Attila Message-ID: <20240812152652.1303-1-pauk.denis@gmail.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775-platform.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c index 9aa4dcf4a6f3..096f1daa8f2b 100644 --- a/drivers/hwmon/nct6775-platform.c +++ b/drivers/hwmon/nct6775-platform.c @@ -1269,6 +1269,7 @@ static const char * const asus_msi_boards[] = { "EX-B760M-V5 D4", "EX-H510M-V3", "EX-H610M-V3 D4", + "G15CF", "PRIME A620M-A", "PRIME B560-PLUS", "PRIME B560-PLUS AC-HES", From 63be321e5a096746b396fc43c7d25135a61617c9 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 11 Aug 2024 02:14:41 +0200 Subject: [PATCH 194/573] hwmon: Add thermal sensor driver for Surface Aggregator Module Some of the newer Microsoft Surface devices (such as the Surface Book 3 and Pro 9) have thermal sensors connected via the Surface Aggregator Module (the embedded controller on those devices). Add a basic driver to read out the temperature values of those sensors. The EC can have up to 16 thermal sensors connected via a single sub-device, each providing temperature readings and a label string. Link: https://github.com/linux-surface/surface-aggregator-module/issues/59 Reviewed-by: Hans de Goede Co-developed-by: Ivor Wanders Signed-off-by: Ivor Wanders Signed-off-by: Maximilian Luz Message-ID: <20240811001503.753728-1-luzmaximilian@gmail.com> Signed-off-by: Guenter Roeck --- MAINTAINERS | 6 + drivers/hwmon/Kconfig | 11 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/surface_temp.c | 235 +++++++++++++++++++++++++++++++++++ 4 files changed, 253 insertions(+) create mode 100644 drivers/hwmon/surface_temp.c diff --git a/MAINTAINERS b/MAINTAINERS index 42decde38320..39c61db0169c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15200,6 +15200,12 @@ S: Maintained F: Documentation/hwmon/surface_fan.rst F: drivers/hwmon/surface_fan.c +MICROSOFT SURFACE SENSOR THERMAL DRIVER +M: Maximilian Luz +L: linux-hwmon@vger.kernel.org +S: Maintained +F: drivers/hwmon/surface_temp.c + MICROSOFT SURFACE GPE LID SUPPORT DRIVER M: Maximilian Luz L: platform-driver-x86@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 810c7d907235..1db349ecb1a4 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2082,6 +2082,17 @@ config SENSORS_SURFACE_FAN Select M or Y here, if you want to be able to read the fan's speed. +config SENSORS_SURFACE_TEMP + tristate "Microsoft Surface Thermal Sensor Driver" + depends on SURFACE_AGGREGATOR + depends on SURFACE_AGGREGATOR_BUS + help + Driver for monitoring thermal sensors connected via the Surface + Aggregator Module (embedded controller) on Microsoft Surface devices. + + This driver can also be built as a module. If so, the module + will be called surface_temp. + config SENSORS_ADC128D818 tristate "Texas Instruments ADC128D818" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index b1c7056c37db..3ce8d6a9202e 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -209,6 +209,7 @@ obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o obj-$(CONFIG_SENSORS_SPD5118) += spd5118.o obj-$(CONFIG_SENSORS_STTS751) += stts751.o obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o +obj-$(CONFIG_SENSORS_SURFACE_TEMP)+= surface_temp.o obj-$(CONFIG_SENSORS_SY7636A) += sy7636a-hwmon.o obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o obj-$(CONFIG_SENSORS_TC74) += tc74.o diff --git a/drivers/hwmon/surface_temp.c b/drivers/hwmon/surface_temp.c new file mode 100644 index 000000000000..cd21f331f157 --- /dev/null +++ b/drivers/hwmon/surface_temp.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Thermal sensor subsystem driver for Surface System Aggregator Module (SSAM). + * + * Copyright (C) 2022-2023 Maximilian Luz + */ + +#include +#include +#include +#include +#include + +#include +#include + +/* -- SAM interface. -------------------------------------------------------- */ + +/* + * Available sensors are indicated by a 16-bit bitfield, where a 1 marks the + * presence of a sensor. So we have at most 16 possible sensors/channels. + */ +#define SSAM_TMP_SENSOR_MAX_COUNT 16 + +/* + * All names observed so far are 6 characters long, but there's only + * zeros after the name, so perhaps they can be longer. This number reflects + * the maximum zero-padded space observed in the returned buffer. + */ +#define SSAM_TMP_SENSOR_NAME_LENGTH 18 + +struct ssam_tmp_get_name_rsp { + __le16 unknown1; + char unknown2; + char name[SSAM_TMP_SENSOR_NAME_LENGTH]; +} __packed; + +static_assert(sizeof(struct ssam_tmp_get_name_rsp) == 21); + +SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_get_available_sensors, __le16, { + .target_category = SSAM_SSH_TC_TMP, + .command_id = 0x04, +}); + +SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_temperature, __le16, { + .target_category = SSAM_SSH_TC_TMP, + .command_id = 0x01, +}); + +SSAM_DEFINE_SYNC_REQUEST_MD_R(__ssam_tmp_get_name, struct ssam_tmp_get_name_rsp, { + .target_category = SSAM_SSH_TC_TMP, + .command_id = 0x0e, +}); + +static int ssam_tmp_get_available_sensors(struct ssam_device *sdev, s16 *sensors) +{ + __le16 sensors_le; + int status; + + status = __ssam_tmp_get_available_sensors(sdev, &sensors_le); + if (status) + return status; + + *sensors = le16_to_cpu(sensors_le); + return 0; +} + +static int ssam_tmp_get_temperature(struct ssam_device *sdev, u8 iid, long *temperature) +{ + __le16 temp_le; + int status; + + status = __ssam_tmp_get_temperature(sdev->ctrl, sdev->uid.target, iid, &temp_le); + if (status) + return status; + + /* Convert 1/10 °K to 1/1000 °C */ + *temperature = (le16_to_cpu(temp_le) - 2731) * 100L; + return 0; +} + +static int ssam_tmp_get_name(struct ssam_device *sdev, u8 iid, char *buf, size_t buf_len) +{ + struct ssam_tmp_get_name_rsp name_rsp; + int status; + + status = __ssam_tmp_get_name(sdev->ctrl, sdev->uid.target, iid, &name_rsp); + if (status) + return status; + + /* + * This should not fail unless the name in the returned struct is not + * null-terminated or someone changed something in the struct + * definitions above, since our buffer and struct have the same + * capacity by design. So if this fails, log an error message. Since + * the more likely cause is that the returned string isn't + * null-terminated, we might have received garbage (as opposed to just + * an incomplete string), so also fail the function. + */ + status = strscpy(buf, name_rsp.name, buf_len); + if (status < 0) { + dev_err(&sdev->dev, "received non-null-terminated sensor name string\n"); + return status; + } + + return 0; +} + +/* -- Driver.---------------------------------------------------------------- */ + +struct ssam_temp { + struct ssam_device *sdev; + s16 sensors; + char names[SSAM_TMP_SENSOR_MAX_COUNT][SSAM_TMP_SENSOR_NAME_LENGTH]; +}; + +static umode_t ssam_temp_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct ssam_temp *ssam_temp = data; + + if (!(ssam_temp->sensors & BIT(channel))) + return 0; + + return 0444; +} + +static int ssam_temp_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + const struct ssam_temp *ssam_temp = dev_get_drvdata(dev); + + return ssam_tmp_get_temperature(ssam_temp->sdev, channel + 1, value); +} + +static int ssam_temp_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + const struct ssam_temp *ssam_temp = dev_get_drvdata(dev); + + *str = ssam_temp->names[channel]; + return 0; +} + +static const struct hwmon_channel_info * const ssam_temp_hwmon_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + NULL +}; + +static const struct hwmon_ops ssam_temp_hwmon_ops = { + .is_visible = ssam_temp_hwmon_is_visible, + .read = ssam_temp_hwmon_read, + .read_string = ssam_temp_hwmon_read_string, +}; + +static const struct hwmon_chip_info ssam_temp_hwmon_chip_info = { + .ops = &ssam_temp_hwmon_ops, + .info = ssam_temp_hwmon_info, +}; + +static int ssam_temp_probe(struct ssam_device *sdev) +{ + struct ssam_temp *ssam_temp; + struct device *hwmon_dev; + s16 sensors; + int channel; + int status; + + status = ssam_tmp_get_available_sensors(sdev, &sensors); + if (status) + return status; + + ssam_temp = devm_kzalloc(&sdev->dev, sizeof(*ssam_temp), GFP_KERNEL); + if (!ssam_temp) + return -ENOMEM; + + ssam_temp->sdev = sdev; + ssam_temp->sensors = sensors; + + /* Retrieve the name for each available sensor. */ + for (channel = 0; channel < SSAM_TMP_SENSOR_MAX_COUNT; channel++) { + if (!(sensors & BIT(channel))) + continue; + + status = ssam_tmp_get_name(sdev, channel + 1, ssam_temp->names[channel], + SSAM_TMP_SENSOR_NAME_LENGTH); + if (status) + return status; + } + + hwmon_dev = devm_hwmon_device_register_with_info(&sdev->dev, "surface_thermal", ssam_temp, + &ssam_temp_hwmon_chip_info, NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct ssam_device_id ssam_temp_match[] = { + { SSAM_SDEV(TMP, SAM, 0x00, 0x02) }, + { }, +}; +MODULE_DEVICE_TABLE(ssam, ssam_temp_match); + +static struct ssam_device_driver ssam_temp = { + .probe = ssam_temp_probe, + .match_table = ssam_temp_match, + .driver = { + .name = "surface_temp", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_ssam_device_driver(ssam_temp); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Thermal sensor subsystem driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); From fa8df3cbd3c0a1b62b4127d779fbf51eb17e22f1 Mon Sep 17 00:00:00 2001 From: Liao Chen Date: Wed, 14 Aug 2024 02:45:55 +0000 Subject: [PATCH 195/573] hwmon: (gsc-hwmon) fix module autoloading Add MODULE_DEVICE_TABLE(), so modules could be properly autoloaded based on the alias from of_device_id table. Signed-off-by: Liao Chen Message-ID: <20240814024555.3875387-1-liaochen4@huawei.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/gsc-hwmon.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/gsc-hwmon.c b/drivers/hwmon/gsc-hwmon.c index cb2f01dc4326..4514f3ed90cc 100644 --- a/drivers/hwmon/gsc-hwmon.c +++ b/drivers/hwmon/gsc-hwmon.c @@ -400,6 +400,7 @@ static const struct of_device_id gsc_hwmon_of_match[] = { { .compatible = "gw,gsc-adc", }, {} }; +MODULE_DEVICE_TABLE(of, gsc_hwmon_of_match); static struct platform_driver gsc_hwmon_driver = { .driver = { From 720c741c22d18b03eb017683f001a645a23a2e65 Mon Sep 17 00:00:00 2001 From: Yue Haibing Date: Fri, 16 Aug 2024 17:57:40 +0800 Subject: [PATCH 196/573] hwmon: (sch5627) Remove unused declaration sch56xx_watchdog_unregister() Commit 2be5f0d75325 ("hwmon: (sch56xx) Use devres functions for watchdog") removed the implementation but leave declaration. Signed-off-by: Yue Haibing Message-ID: <20240816095740.877729-1-yuehaibing@huawei.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/sch56xx-common.h | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/hwmon/sch56xx-common.h b/drivers/hwmon/sch56xx-common.h index 7479a549a026..601987c6b4cd 100644 --- a/drivers/hwmon/sch56xx-common.h +++ b/drivers/hwmon/sch56xx-common.h @@ -22,4 +22,3 @@ int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg, void sch56xx_watchdog_register(struct device *parent, u16 addr, u32 revision, struct mutex *io_lock, int check_enabled); -void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data); From b6964d66a07a9003868e428a956949e17ab44d7e Mon Sep 17 00:00:00 2001 From: Yuntao Liu Date: Thu, 15 Aug 2024 08:30:21 +0000 Subject: [PATCH 197/573] hwmon: (ntc_thermistor) fix module autoloading Add MODULE_DEVICE_TABLE(), so modules could be properly autoloaded based on the alias from of_device_id table. Fixes: 9e8269de100d ("hwmon: (ntc_thermistor) Add DT with IIO support to NTC thermistor driver") Signed-off-by: Yuntao Liu Message-ID: <20240815083021.756134-1-liuyuntao12@huawei.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/ntc_thermistor.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index ef75b63f5894..b5352900463f 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -62,6 +62,7 @@ static const struct platform_device_id ntc_thermistor_id[] = { [NTC_SSG1404001221] = { "ssg1404_001221", TYPE_NCPXXWB473 }, [NTC_LAST] = { }, }; +MODULE_DEVICE_TABLE(platform, ntc_thermistor_id); /* * A compensation table should be sorted by the values of .ohm From 0830d3bbdabc4fb6ee0318142c2e81398e4815d9 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Mon, 26 Aug 2024 14:18:11 -0500 Subject: [PATCH 198/573] hwmon: (stts751) Add "st" vendor prefix to "stts751" compatible string The documented compatible string is "st,stts751", not "stts751". Even if "stts751" was in use, there's no need to list "stts751" in the DT match table. The I2C core will strip any vendor prefix and match against the i2c_device_id table which has an "stts751" entry. Signed-off-by: Rob Herring (Arm) Message-ID: <20240826191811.1416011-1-robh@kernel.org> Signed-off-by: Guenter Roeck --- drivers/hwmon/stts751.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/stts751.c b/drivers/hwmon/stts751.c index e7632081a1d1..f9e8b2869164 100644 --- a/drivers/hwmon/stts751.c +++ b/drivers/hwmon/stts751.c @@ -77,7 +77,7 @@ static const struct i2c_device_id stts751_id[] = { }; static const struct of_device_id __maybe_unused stts751_of_match[] = { - { .compatible = "stts751" }, + { .compatible = "st,stts751" }, { }, }; MODULE_DEVICE_TABLE(of, stts751_of_match); From b82b38a49926b7d8e17d79db7959586313a99582 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Thu, 22 Aug 2024 11:35:25 -0700 Subject: [PATCH 199/573] hwmon: (oxp-sensors) Add support for multiple new devices. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the OrangePi NEO-01. It uses different registers for PWM manual mode, set PWM, and read fan speed than previous devices. Valid PWM input and duty cycle is 1-244, we scale this from 1-255 to maintain compatibility with the existing interface. Add OneXPlayer 2 series, OneXFly, and X1 series models. The 2/X1 series use new registers for turbo button takeover and read fan speed. X1 has an Intel variant so change the CPU detection at init to only check for the affected devices. While at it, adjust formatting of some constants and reorder all cases alphabetically for consistency. Rename OXP_OLD constants to OXP_MINI for disambiguation. Update code comments for clarity. Add support for AYANEO models 2S, AIR 1S, Flip series, GEEK 1S, and KUN. Signed-off-by: Derek J. Clark Tested-by: Kevin Greenberg Tested-by: Joshua Tam Tested-by: Parth Menon Tested-by: Philip Müller Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202408160329.TLNbIwRC-lkp@intel.com/ Message-ID: <20240822183525.27289-2-derekjohn.clark@gmail.com> Signed-off-by: Guenter Roeck --- Documentation/hwmon/oxp-sensors.rst | 54 +++-- drivers/hwmon/oxp-sensors.c | 299 +++++++++++++++++++++++----- 2 files changed, 290 insertions(+), 63 deletions(-) diff --git a/Documentation/hwmon/oxp-sensors.rst b/Documentation/hwmon/oxp-sensors.rst index 50618f064379..581c4dafbfa1 100644 --- a/Documentation/hwmon/oxp-sensors.rst +++ b/Documentation/hwmon/oxp-sensors.rst @@ -10,41 +10,59 @@ Authors: Description: ------------ -Handheld devices from One Netbook and Aya Neo provide fan readings and fan -control through their embedded controllers. +Handheld devices from OneNetbook, AOKZOE, AYANEO, And OrangePi provide fan +readings and fan control through their embedded controllers. -Currently only supports AMD boards from One X Player, AOK ZOE, and some Aya -Neo devices. One X Player Intel boards could be supported if we could figure -out the EC registers and values to write to since the EC layout and model is -different. Aya Neo devices preceding the AIR may not be supportable as the EC -model is different and do not appear to have manual control capabilities. +Currently supports OneXPlayer devices, AOKZOE, AYANEO, and OrangePi +handheld devices. AYANEO devices preceding the AIR and OneXPlayer devices +preceding the Mini A07 are not supportable as the EC model is different +and do not have manual control capabilities. -Some models have a toggle for changing the behaviour of the "Turbo/Silent" -button of the device. It will change the key event that it triggers with -a flip of the `tt_toggle` attribute. See below for boards that support this -function. +Some OneXPlayer and AOKZOE models have a toggle for changing the behaviour +of the "Turbo/Silent" button of the device. It will change the key event +that it triggers with a flip of the `tt_toggle` attribute. See below for +boards that support this function. Supported devices ----------------- Currently the driver supports the following handhelds: - - AOK ZOE A1 - - AOK ZOE A1 PRO - - Aya Neo 2 - - Aya Neo AIR - - Aya Neo AIR Plus (Mendocino) - - Aya Neo AIR Pro - - Aya Neo Geek + - AOKZOE A1 + - AOKZOE A1 PRO + - AYANEO 2 + - AYANEO 2S + - AYANEO AIR + - AYANEO AIR 1S + - AYANEO AIR Plus (Mendocino) + - AYANEO AIR Pro + - AYANEO Flip DS + - AYANEO Flip KB + - AYANEO Geek + - AYANEO Geek 1S + - AYANEO KUN + - OneXPlayer 2 + - OneXPlayer 2 Pro - OneXPlayer AMD - OneXPlayer mini AMD - OneXPlayer mini AMD PRO + - OneXPlayer OneXFly + - OneXPlayer X1 A + - OneXPlayer X1 i + - OneXPlayer X1 mini + - OrangePi NEO-01 "Turbo/Silent" button behaviour toggle is only supported on: - AOK ZOE A1 - AOK ZOE A1 PRO + - OneXPlayer 2 + - OneXPlayer 2 Pro - OneXPlayer mini AMD (only with updated alpha BIOS) - OneXPlayer mini AMD PRO + - OneXPlayer OneXFly + - OneXPlayer X1 A + - OneXPlayer X1 i + - OneXPlayer X1 mini Sysfs entries ------------- diff --git a/drivers/hwmon/oxp-sensors.c b/drivers/hwmon/oxp-sensors.c index 8d3b0f86cc57..b6d06370469d 100644 --- a/drivers/hwmon/oxp-sensors.c +++ b/drivers/hwmon/oxp-sensors.c @@ -1,18 +1,21 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Platform driver for OneXPlayer, AOK ZOE, and Aya Neo Handhelds that expose - * fan reading and control via hwmon sysfs. + * Platform driver for OneXPlayer, AOKZOE, AYANEO, and OrangePi Handhelds + * that expose fan reading and control via hwmon sysfs. * * Old OXP boards have the same DMI strings and they are told apart by - * the boot cpu vendor (Intel/AMD). Currently only AMD boards are - * supported but the code is made to be simple to add other handheld - * boards in the future. + * the boot cpu vendor (Intel/AMD). Of these older models only AMD is + * supported. + * * Fan control is provided via pwm interface in the range [0-255]. * Old AMD boards use [0-100] as range in the EC, the written value is * scaled to accommodate for that. Newer boards like the mini PRO and - * AOK ZOE are not scaled but have the same EC layout. + * AOKZOE are not scaled but have the same EC layout. Newer models + * like the 2 and X1 are [0-184] and are scaled to 0-255. OrangePi + * are [1-244] and scaled to 0-255. * * Copyright (C) 2022 Joaquín I. Aramendía + * Copyright (C) 2024 Derek J. Clark */ #include @@ -43,32 +46,48 @@ enum oxp_board { aok_zoe_a1 = 1, aya_neo_2, aya_neo_air, + aya_neo_air_1s, aya_neo_air_plus_mendo, aya_neo_air_pro, + aya_neo_flip, aya_neo_geek, + aya_neo_kun, + orange_pi_neo, + oxp_2, + oxp_fly, oxp_mini_amd, oxp_mini_amd_a07, oxp_mini_amd_pro, + oxp_x1, }; static enum oxp_board board; /* Fan reading and PWM */ -#define OXP_SENSOR_FAN_REG 0x76 /* Fan reading is 2 registers long */ -#define OXP_SENSOR_PWM_ENABLE_REG 0x4A /* PWM enable is 1 register long */ -#define OXP_SENSOR_PWM_REG 0x4B /* PWM reading is 1 register long */ +#define OXP_SENSOR_FAN_REG 0x76 /* Fan reading is 2 registers long */ +#define OXP_2_SENSOR_FAN_REG 0x58 /* Fan reading is 2 registers long */ +#define OXP_SENSOR_PWM_ENABLE_REG 0x4A /* PWM enable is 1 register long */ +#define OXP_SENSOR_PWM_REG 0x4B /* PWM reading is 1 register long */ +#define PWM_MODE_AUTO 0x00 +#define PWM_MODE_MANUAL 0x01 + +/* OrangePi fan reading and PWM */ +#define ORANGEPI_SENSOR_FAN_REG 0x78 /* Fan reading is 2 registers long */ +#define ORANGEPI_SENSOR_PWM_ENABLE_REG 0x40 /* PWM enable is 1 register long */ +#define ORANGEPI_SENSOR_PWM_REG 0x38 /* PWM reading is 1 register long */ /* Turbo button takeover function - * Older boards have different values and EC registers + * Different boards have different values and EC registers * for the same function */ -#define OXP_OLD_TURBO_SWITCH_REG 0x1E -#define OXP_OLD_TURBO_TAKE_VAL 0x01 -#define OXP_OLD_TURBO_RETURN_VAL 0x00 +#define OXP_TURBO_SWITCH_REG 0xF1 /* Mini Pro, OneXFly, AOKZOE */ +#define OXP_2_TURBO_SWITCH_REG 0xEB /* OXP2 and X1 */ +#define OXP_MINI_TURBO_SWITCH_REG 0x1E /* Mini AO7 */ -#define OXP_TURBO_SWITCH_REG 0xF1 -#define OXP_TURBO_TAKE_VAL 0x40 -#define OXP_TURBO_RETURN_VAL 0x00 +#define OXP_MINI_TURBO_TAKE_VAL 0x01 /* Mini AO7 */ +#define OXP_TURBO_TAKE_VAL 0x40 /* All other models */ + +#define OXP_TURBO_RETURN_VAL 0x00 /* Common return val */ static const struct dmi_system_id dmi_table[] = { { @@ -88,7 +107,7 @@ static const struct dmi_system_id dmi_table[] = { { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 2"), + DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"), }, .driver_data = (void *)aya_neo_2, }, @@ -99,6 +118,13 @@ static const struct dmi_system_id dmi_table[] = { }, .driver_data = (void *)aya_neo_air, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S"), + }, + .driver_data = (void *)aya_neo_air_1s, + }, { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), @@ -116,10 +142,31 @@ static const struct dmi_system_id dmi_table[] = { { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "GEEK"), + DMI_MATCH(DMI_BOARD_NAME, "FLIP"), + }, + .driver_data = (void *)aya_neo_flip, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_MATCH(DMI_BOARD_NAME, "GEEK"), }, .driver_data = (void *)aya_neo_geek, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "KUN"), + }, + .driver_data = (void *)aya_neo_kun, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "OrangePi"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "NEO-01"), + }, + .driver_data = (void *)orange_pi_neo, + }, { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), @@ -127,6 +174,20 @@ static const struct dmi_system_id dmi_table[] = { }, .driver_data = (void *)oxp_mini_amd, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_MATCH(DMI_BOARD_NAME, "ONEXPLAYER 2"), + }, + .driver_data = (void *)oxp_2, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1"), + }, + .driver_data = (void *)oxp_fly, + }, { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), @@ -141,6 +202,13 @@ static const struct dmi_system_id dmi_table[] = { }, .driver_data = (void *)oxp_mini_amd_pro, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1"), + }, + .driver_data = (void *)oxp_x1, + }, {}, }; @@ -192,14 +260,20 @@ static int tt_toggle_enable(void) switch (board) { case oxp_mini_amd_a07: - reg = OXP_OLD_TURBO_SWITCH_REG; - val = OXP_OLD_TURBO_TAKE_VAL; + reg = OXP_MINI_TURBO_SWITCH_REG; + val = OXP_MINI_TURBO_TAKE_VAL; break; - case oxp_mini_amd_pro: case aok_zoe_a1: + case oxp_fly: + case oxp_mini_amd_pro: reg = OXP_TURBO_SWITCH_REG; val = OXP_TURBO_TAKE_VAL; break; + case oxp_2: + case oxp_x1: + reg = OXP_2_TURBO_SWITCH_REG; + val = OXP_TURBO_TAKE_VAL; + break; default: return -EINVAL; } @@ -213,14 +287,20 @@ static int tt_toggle_disable(void) switch (board) { case oxp_mini_amd_a07: - reg = OXP_OLD_TURBO_SWITCH_REG; - val = OXP_OLD_TURBO_RETURN_VAL; + reg = OXP_MINI_TURBO_SWITCH_REG; + val = OXP_TURBO_RETURN_VAL; break; - case oxp_mini_amd_pro: case aok_zoe_a1: + case oxp_fly: + case oxp_mini_amd_pro: reg = OXP_TURBO_SWITCH_REG; val = OXP_TURBO_RETURN_VAL; break; + case oxp_2: + case oxp_x1: + reg = OXP_2_TURBO_SWITCH_REG; + val = OXP_TURBO_RETURN_VAL; + break; default: return -EINVAL; } @@ -233,8 +313,11 @@ static umode_t tt_toggle_is_visible(struct kobject *kobj, { switch (board) { case aok_zoe_a1: + case oxp_2: + case oxp_fly: case oxp_mini_amd_a07: case oxp_mini_amd_pro: + case oxp_x1: return attr->mode; default: break; @@ -273,12 +356,17 @@ static ssize_t tt_toggle_show(struct device *dev, switch (board) { case oxp_mini_amd_a07: - reg = OXP_OLD_TURBO_SWITCH_REG; + reg = OXP_MINI_TURBO_SWITCH_REG; break; - case oxp_mini_amd_pro: case aok_zoe_a1: + case oxp_fly: + case oxp_mini_amd_pro: reg = OXP_TURBO_SWITCH_REG; break; + case oxp_2: + case oxp_x1: + reg = OXP_2_TURBO_SWITCH_REG; + break; default: return -EINVAL; } @@ -295,12 +383,53 @@ static DEVICE_ATTR_RW(tt_toggle); /* PWM enable/disable functions */ static int oxp_pwm_enable(void) { - return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, 0x01); + switch (board) { + case orange_pi_neo: + return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL); + case aok_zoe_a1: + case aya_neo_2: + case aya_neo_air: + case aya_neo_air_plus_mendo: + case aya_neo_air_pro: + case aya_neo_flip: + case aya_neo_geek: + case aya_neo_kun: + case oxp_2: + case oxp_fly: + case oxp_mini_amd: + case oxp_mini_amd_a07: + case oxp_mini_amd_pro: + case oxp_x1: + return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL); + default: + return -EINVAL; + } } static int oxp_pwm_disable(void) { - return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, 0x00); + switch (board) { + case orange_pi_neo: + return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO); + case aok_zoe_a1: + case aya_neo_2: + case aya_neo_air: + case aya_neo_air_1s: + case aya_neo_air_plus_mendo: + case aya_neo_air_pro: + case aya_neo_flip: + case aya_neo_geek: + case aya_neo_kun: + case oxp_2: + case oxp_fly: + case oxp_mini_amd: + case oxp_mini_amd_a07: + case oxp_mini_amd_pro: + case oxp_x1: + return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO); + default: + return -EINVAL; + } } /* Callbacks for hwmon interface */ @@ -326,7 +455,29 @@ static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type, case hwmon_fan: switch (attr) { case hwmon_fan_input: - return read_from_ec(OXP_SENSOR_FAN_REG, 2, val); + switch (board) { + case orange_pi_neo: + return read_from_ec(ORANGEPI_SENSOR_FAN_REG, 2, val); + case oxp_2: + case oxp_x1: + return read_from_ec(OXP_2_SENSOR_FAN_REG, 2, val); + case aok_zoe_a1: + case aya_neo_2: + case aya_neo_air: + case aya_neo_air_1s: + case aya_neo_air_plus_mendo: + case aya_neo_air_pro: + case aya_neo_flip: + case aya_neo_geek: + case aya_neo_kun: + case oxp_fly: + case oxp_mini_amd: + case oxp_mini_amd_a07: + case oxp_mini_amd_pro: + return read_from_ec(OXP_SENSOR_FAN_REG, 2, val); + default: + break; + } default: break; } @@ -334,31 +485,74 @@ static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type, case hwmon_pwm: switch (attr) { case hwmon_pwm_input: - ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); - if (ret) - return ret; switch (board) { + case orange_pi_neo: + ret = read_from_ec(ORANGEPI_SENSOR_PWM_REG, 1, val); + if (ret) + return ret; + /* scale from range [1-244] */ + *val = ((*val - 1) * 254 / 243) + 1; + break; + case oxp_2: + case oxp_x1: + ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); + if (ret) + return ret; + /* scale from range [0-184] */ + *val = (*val * 255) / 184; + break; case aya_neo_2: case aya_neo_air: + case aya_neo_air_1s: case aya_neo_air_plus_mendo: case aya_neo_air_pro: + case aya_neo_flip: case aya_neo_geek: + case aya_neo_kun: case oxp_mini_amd: case oxp_mini_amd_a07: + ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); + if (ret) + return ret; + /* scale from range [0-100] */ *val = (*val * 255) / 100; break; - case oxp_mini_amd_pro: case aok_zoe_a1: + case oxp_fly: + case oxp_mini_amd_pro: default: + ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); + if (ret) + return ret; break; } return 0; case hwmon_pwm_enable: - return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val); + switch (board) { + case orange_pi_neo: + return read_from_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, 1, val); + case aok_zoe_a1: + case aya_neo_2: + case aya_neo_air: + case aya_neo_air_1s: + case aya_neo_air_plus_mendo: + case aya_neo_air_pro: + case aya_neo_flip: + case aya_neo_geek: + case aya_neo_kun: + case oxp_2: + case oxp_fly: + case oxp_mini_amd: + case oxp_mini_amd_a07: + case oxp_mini_amd_pro: + case oxp_x1: + return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val); + default: + break; + } default: break; } - break; default: break; } @@ -381,21 +575,35 @@ static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type, if (val < 0 || val > 255) return -EINVAL; switch (board) { + case orange_pi_neo: + /* scale to range [1-244] */ + val = ((val - 1) * 243 / 254) + 1; + return write_to_ec(ORANGEPI_SENSOR_PWM_REG, val); + case oxp_2: + case oxp_x1: + /* scale to range [0-184] */ + val = (val * 184) / 255; + return write_to_ec(OXP_SENSOR_PWM_REG, val); case aya_neo_2: case aya_neo_air: + case aya_neo_air_1s: case aya_neo_air_plus_mendo: case aya_neo_air_pro: + case aya_neo_flip: case aya_neo_geek: + case aya_neo_kun: case oxp_mini_amd: case oxp_mini_amd_a07: + /* scale to range [0-100] */ val = (val * 100) / 255; - break; + return write_to_ec(OXP_SENSOR_PWM_REG, val); case aok_zoe_a1: + case oxp_fly: case oxp_mini_amd_pro: + return write_to_ec(OXP_SENSOR_PWM_REG, val); default: break; } - return write_to_ec(OXP_SENSOR_PWM_REG, val); default: break; } @@ -467,19 +675,20 @@ static int __init oxp_platform_init(void) { const struct dmi_system_id *dmi_entry; - /* - * Have to check for AMD processor here because DMI strings are the - * same between Intel and AMD boards, the only way to tell them apart - * is the CPU. - * Intel boards seem to have different EC registers and values to - * read/write. - */ dmi_entry = dmi_first_match(dmi_table); - if (!dmi_entry || boot_cpu_data.x86_vendor != X86_VENDOR_AMD) + if (!dmi_entry) return -ENODEV; board = (enum oxp_board)(unsigned long)dmi_entry->driver_data; + /* + * Have to check for AMD processor here because DMI strings are the same + * between Intel and AMD boards on older OneXPlayer devices, the only way + * to tell them apart is the CPU. Old Intel boards have an unsupported EC. + */ + if (board == oxp_mini_amd && boot_cpu_data.x86_vendor != X86_VENDOR_AMD) + return -ENODEV; + oxp_platform_device = platform_create_bundle(&oxp_platform_driver, oxp_platform_probe, NULL, 0, NULL, 0); From 0050c167016b599c9a3fe79a8f021898a6211d83 Mon Sep 17 00:00:00 2001 From: Chanh Nguyen Date: Thu, 22 Aug 2024 08:48:08 +0000 Subject: [PATCH 200/573] dt-bindings: hwmon: Add maxim max31790 Add device tree bindings and an example for max31790 device. Signed-off-by: Chanh Nguyen Reviewed-by: Krzysztof Kozlowski Message-ID: <20240822084808.299884-1-chanh@os.amperecomputing.com> Signed-off-by: Guenter Roeck --- .../bindings/hwmon/maxim,max31790.yaml | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/maxim,max31790.yaml diff --git a/Documentation/devicetree/bindings/hwmon/maxim,max31790.yaml b/Documentation/devicetree/bindings/hwmon/maxim,max31790.yaml new file mode 100644 index 000000000000..b1ff496f87f9 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/maxim,max31790.yaml @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/maxim,max31790.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: The Maxim MAX31790 Fan Controller + +maintainers: + - Guenter Roeck + - Chanh Nguyen + +description: > + The MAX31790 controls the speeds of up to six fans using six + independent PWM outputs. The desired fan speeds (or PWM duty cycles) + are written through the I2C interface. + + Datasheets: + https://datasheets.maximintegrated.com/en/ds/MAX31790.pdf + +properties: + compatible: + const: maxim,max31790 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + "#pwm-cells": + const: 1 + +patternProperties: + "^fan-[0-9]+$": + $ref: fan-common.yaml# + unevaluatedProperties: false + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pwm_provider: fan-controller@20 { + compatible = "maxim,max31790"; + reg = <0x20>; + clocks = <&sys_clk>; + resets = <&reset 0>; + #pwm-cells = <1>; + + fan-0 { + pwms = <&pwm_provider 1>; + }; + + fan-1 { + pwms = <&pwm_provider 2>; + }; + }; + }; + From 4599510091a1d6d4759f1302dff32b24481eec15 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 20 Aug 2024 00:35:57 -0500 Subject: [PATCH 201/573] hwmon: (k10temp): Use cpu_feature_enabled() for detecting zen This removes some boilerplate from the code and will allow adding future CPUs by just device IDs. Signed-off-by: Mario Limonciello Message-ID: <20240820053558.1052853-1-superm1@kernel.org> Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 543526bac042..85a7632f3b50 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -438,16 +438,21 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) data->disp_negative = true; } - if (boot_cpu_data.x86 == 0x15 && + data->is_zen = cpu_feature_enabled(X86_FEATURE_ZEN); + if (data->is_zen) { + data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK; + data->read_tempreg = read_tempreg_nb_zen; + } else if (boot_cpu_data.x86 == 0x15 && ((boot_cpu_data.x86_model & 0xf0) == 0x60 || (boot_cpu_data.x86_model & 0xf0) == 0x70)) { data->read_htcreg = read_htcreg_nb_f15; data->read_tempreg = read_tempreg_nb_f15; - } else if (boot_cpu_data.x86 == 0x17 || boot_cpu_data.x86 == 0x18) { - data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK; - data->read_tempreg = read_tempreg_nb_zen; - data->is_zen = true; + } else { + data->read_htcreg = read_htcreg_pci; + data->read_tempreg = read_tempreg_pci; + } + if (boot_cpu_data.x86 == 0x17 || boot_cpu_data.x86 == 0x18) { switch (boot_cpu_data.x86_model) { case 0x1: /* Zen */ case 0x8: /* Zen+ */ @@ -469,10 +474,6 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) break; } } else if (boot_cpu_data.x86 == 0x19) { - data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK; - data->read_tempreg = read_tempreg_nb_zen; - data->is_zen = true; - switch (boot_cpu_data.x86_model) { case 0x0 ... 0x1: /* Zen3 SP3/TR */ case 0x8: /* Zen3 TR Chagall */ @@ -496,13 +497,6 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) k10temp_get_ccd_support(data, 12); break; } - } else if (boot_cpu_data.x86 == 0x1a) { - data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK; - data->read_tempreg = read_tempreg_nb_zen; - data->is_zen = true; - } else { - data->read_htcreg = read_htcreg_pci; - data->read_tempreg = read_tempreg_pci; } for (i = 0; i < ARRAY_SIZE(tctl_offset_table); i++) { From 24b070d7d8d84f6af7de6057fdf0d80079c7e929 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Thu, 22 Aug 2024 14:29:49 +0800 Subject: [PATCH 202/573] hwmon: (aspeed-g6-pwm-tacho): Simplify with scoped for each OF child loop Use scoped for_each_child_of_node_scoped() when iterating over device nodes to make code a bit simpler. Signed-off-by: Jinjie Ruan Reviewed-by: Andrew Jeffery Message-ID: <20240822062956.3490387-2-ruanjinjie@huawei.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/aspeed-g6-pwm-tach.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hwmon/aspeed-g6-pwm-tach.c b/drivers/hwmon/aspeed-g6-pwm-tach.c index 08a2ded95e45..75eadda738ab 100644 --- a/drivers/hwmon/aspeed-g6-pwm-tach.c +++ b/drivers/hwmon/aspeed-g6-pwm-tach.c @@ -456,7 +456,6 @@ static int aspeed_pwm_tach_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev, *hwmon; int ret; - struct device_node *child; struct aspeed_pwm_tach_data *priv; struct pwm_chip *chip; @@ -498,10 +497,9 @@ static int aspeed_pwm_tach_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "Failed to add PWM chip\n"); - for_each_child_of_node(dev->of_node, child) { + for_each_child_of_node_scoped(dev->of_node, child) { ret = aspeed_create_fan_monitor(dev, child, priv); if (ret) { - of_node_put(child); dev_warn(dev, "Failed to create fan %d", ret); return 0; } From d5733a9387c894a277e0d3d4111e968b917a7375 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Thu, 22 Aug 2024 14:29:50 +0800 Subject: [PATCH 203/573] hwmon: (aspeed-pwm-tacho): Simplify with scoped for each OF child loop Use scoped for_each_child_of_node_scoped() when iterating over device nodes to make code a bit simpler. Signed-off-by: Jinjie Ruan Reviewed-by: Andrew Jeffery Message-ID: <20240822062956.3490387-3-ruanjinjie@huawei.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/aspeed-pwm-tacho.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/aspeed-pwm-tacho.c b/drivers/hwmon/aspeed-pwm-tacho.c index 4acc1858d8ac..aa159bf158a3 100644 --- a/drivers/hwmon/aspeed-pwm-tacho.c +++ b/drivers/hwmon/aspeed-pwm-tacho.c @@ -907,7 +907,7 @@ static void aspeed_pwm_tacho_remove(void *data) static int aspeed_pwm_tacho_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *np, *child; + struct device_node *np; struct aspeed_pwm_tacho_data *priv; void __iomem *regs; struct device *hwmon; @@ -951,12 +951,10 @@ static int aspeed_pwm_tacho_probe(struct platform_device *pdev) aspeed_create_type(priv); - for_each_child_of_node(np, child) { + for_each_child_of_node_scoped(np, child) { ret = aspeed_create_fan(dev, child, priv); - if (ret) { - of_node_put(child); + if (ret) return ret; - } } priv->groups[0] = &pwm_dev_group; From 3fd38c6434ce54e09d8c3fbd2daf67b9672a65d6 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Thu, 22 Aug 2024 14:29:51 +0800 Subject: [PATCH 204/573] hwmon: (ina3221): Simplify with scoped for each OF child loop Use scoped for_each_child_of_node_scoped() when iterating over device nodes to make code a bit simpler. Signed-off-by: Jinjie Ruan Message-ID: <20240822062956.3490387-4-ruanjinjie@huawei.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/ina3221.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/ina3221.c b/drivers/hwmon/ina3221.c index f0053f87e3e6..1bf479a0f793 100644 --- a/drivers/hwmon/ina3221.c +++ b/drivers/hwmon/ina3221.c @@ -813,7 +813,6 @@ static int ina3221_probe_child_from_dt(struct device *dev, static int ina3221_probe_from_dt(struct device *dev, struct ina3221_data *ina) { const struct device_node *np = dev->of_node; - struct device_node *child; int ret; /* Compatible with non-DT platforms */ @@ -822,12 +821,10 @@ static int ina3221_probe_from_dt(struct device *dev, struct ina3221_data *ina) ina->single_shot = of_property_read_bool(np, "ti,single-shot"); - for_each_child_of_node(np, child) { + for_each_child_of_node_scoped(np, child) { ret = ina3221_probe_child_from_dt(dev, child, ina); - if (ret) { - of_node_put(child); + if (ret) return ret; - } } return 0; From bf0b61f0aa99933f29b0424854f18de9435ed5a0 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Thu, 22 Aug 2024 14:29:52 +0800 Subject: [PATCH 205/573] hwmon: (lm90): Simplify with scoped for each OF child loop Use scoped for_each_child_of_node_scoped() when iterating over device nodes to make code a bit simpler. Signed-off-by: Jinjie Ruan Message-ID: <20240822062956.3490387-5-ruanjinjie@huawei.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/lm90.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index ca5c52b38c0f..511d95a0efb3 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -2674,19 +2674,16 @@ static int lm90_parse_dt_channel_info(struct i2c_client *client, struct lm90_data *data) { int err; - struct device_node *child; struct device *dev = &client->dev; const struct device_node *np = dev->of_node; - for_each_child_of_node(np, child) { + for_each_child_of_node_scoped(np, child) { if (strcmp(child->name, "channel")) continue; err = lm90_probe_channel_from_dt(client, child, data); - if (err) { - of_node_put(child); + if (err) return err; - } } return 0; From 1d20db6b544a54ff5c8e8ff3237905cb47dc38c6 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Thu, 22 Aug 2024 14:29:53 +0800 Subject: [PATCH 206/573] hwmon: (nct7802): Simplify with scoped for each OF child loop Use scoped for_each_child_of_node_scoped() when iterating over device nodes to make code a bit simpler. Signed-off-by: Jinjie Ruan Message-ID: <20240822062956.3490387-6-ruanjinjie@huawei.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/nct7802.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/nct7802.c b/drivers/hwmon/nct7802.c index 5e83504800a3..8c9351da12c6 100644 --- a/drivers/hwmon/nct7802.c +++ b/drivers/hwmon/nct7802.c @@ -1129,17 +1129,14 @@ static int nct7802_configure_channels(struct device *dev, { /* Enable local temperature sensor by default */ u8 mode_mask = MODE_LTD_EN, mode_val = MODE_LTD_EN; - struct device_node *node; int err; if (dev->of_node) { - for_each_child_of_node(dev->of_node, node) { + for_each_child_of_node_scoped(dev->of_node, node) { err = nct7802_get_channel_config(dev, node, &mode_mask, &mode_val); - if (err) { - of_node_put(node); + if (err) return err; - } } } From 1b79bcace44b1f4aea798db589cb6052e902dc66 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Thu, 22 Aug 2024 14:29:54 +0800 Subject: [PATCH 207/573] hwmon: (npcm750-pwm-fan): Simplify with scoped for each OF child loop Use scoped for_each_child_of_node_scoped() when iterating over device nodes to make code a bit simpler. Signed-off-by: Jinjie Ruan Message-ID: <20240822062956.3490387-7-ruanjinjie@huawei.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/npcm750-pwm-fan.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c index bc8db1dc595d..db3b551828eb 100644 --- a/drivers/hwmon/npcm750-pwm-fan.c +++ b/drivers/hwmon/npcm750-pwm-fan.c @@ -927,7 +927,7 @@ static int npcm7xx_en_pwm_fan(struct device *dev, static int npcm7xx_pwm_fan_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *np, *child; + struct device_node *np; struct npcm7xx_pwm_fan_data *data; struct resource *res; struct device *hwmon; @@ -1004,11 +1004,10 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev) } } - for_each_child_of_node(np, child) { + for_each_child_of_node_scoped(np, child) { ret = npcm7xx_en_pwm_fan(dev, child, data); if (ret) { dev_err(dev, "enable pwm and fan failed\n"); - of_node_put(child); return ret; } } From a6e0a54aa7ad58c6b6ff0d4194b985e15fe1c1b2 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Thu, 22 Aug 2024 14:29:55 +0800 Subject: [PATCH 208/573] hwmon: (tmp421): Simplify with scoped for each OF child loop Use scoped for_each_child_of_node_scoped() when iterating over device nodes to make code a bit simpler. Signed-off-by: Jinjie Ruan Message-ID: <20240822062956.3490387-8-ruanjinjie@huawei.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp421.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/tmp421.c b/drivers/hwmon/tmp421.c index 7a6f9532e594..9537727aad9a 100644 --- a/drivers/hwmon/tmp421.c +++ b/drivers/hwmon/tmp421.c @@ -410,18 +410,15 @@ static int tmp421_probe_from_dt(struct i2c_client *client, struct tmp421_data *d { struct device *dev = &client->dev; const struct device_node *np = dev->of_node; - struct device_node *child; int err; - for_each_child_of_node(np, child) { + for_each_child_of_node_scoped(np, child) { if (strcmp(child->name, "channel")) continue; err = tmp421_probe_child_from_dt(client, child, data); - if (err) { - of_node_put(child); + if (err) return err; - } } return 0; From d5b07232868b69414d1e540e7b96a60fbcab3409 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Thu, 22 Aug 2024 14:29:56 +0800 Subject: [PATCH 209/573] hwmon: (tmp464): Simplify with scoped for each OF child loop Use scoped for_each_child_of_node_scoped() when iterating over device nodes to make code a bit simpler. Signed-off-by: Jinjie Ruan Message-ID: <20240822062956.3490387-9-ruanjinjie@huawei.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp464.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/tmp464.c b/drivers/hwmon/tmp464.c index 0a7c0448835b..0f629c6d7695 100644 --- a/drivers/hwmon/tmp464.c +++ b/drivers/hwmon/tmp464.c @@ -562,18 +562,15 @@ static int tmp464_probe_child_from_dt(struct device *dev, static int tmp464_probe_from_dt(struct device *dev, struct tmp464_data *data) { const struct device_node *np = dev->of_node; - struct device_node *child; int err; - for_each_child_of_node(np, child) { + for_each_child_of_node_scoped(np, child) { if (strcmp(child->name, "channel")) continue; err = tmp464_probe_child_from_dt(dev, child, data); - if (err) { - of_node_put(child); + if (err) return err; - } } return 0; From 87798d9f700b9a9b4afd4193b4874b7d9c36ee1f Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Sat, 17 Aug 2024 10:22:56 +0800 Subject: [PATCH 210/573] dt-bindings: hwmon: Add Sophgo SG2042 external hardware monitor support Due to the design, Sophgo SG2042 use an external MCU to provide hardware information, thermal information and reset control. Add bindings for this monitor device. Signed-off-by: Inochi Amaoto Reviewed-by: Conor Dooley Tested-by: Chen Wang Reviewed-by: Chen Wang Message-ID: Signed-off-by: Guenter Roeck --- .../hwmon/sophgo,sg2042-hwmon-mcu.yaml | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/sophgo,sg2042-hwmon-mcu.yaml diff --git a/Documentation/devicetree/bindings/hwmon/sophgo,sg2042-hwmon-mcu.yaml b/Documentation/devicetree/bindings/hwmon/sophgo,sg2042-hwmon-mcu.yaml new file mode 100644 index 000000000000..f0667ac41d75 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/sophgo,sg2042-hwmon-mcu.yaml @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/sophgo,sg2042-hwmon-mcu.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sophgo SG2042 onboard MCU support + +maintainers: + - Inochi Amaoto + +properties: + compatible: + const: sophgo,sg2042-hwmon-mcu + + reg: + maxItems: 1 + + "#thermal-sensor-cells": + const: 1 + +required: + - compatible + - reg + - "#thermal-sensor-cells" + +allOf: + - $ref: /schemas/thermal/thermal-sensor.yaml# + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + hwmon@17 { + compatible = "sophgo,sg2042-hwmon-mcu"; + reg = <0x17>; + #thermal-sensor-cells = <1>; + }; + }; From 758b62e562f2fdffd26a84dbeafbe6888a7e130c Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Sat, 17 Aug 2024 10:22:57 +0800 Subject: [PATCH 211/573] hwmon: Add sophgo SG2042 external hardware monitor support SG2042 use an external MCU to provide basic hardware information and thermal sensors. Add driver support for the onboard MCU of SG2042. Signed-off-by: Inochi Amaoto Message-ID: Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/sg2042-mcu.rst | 78 ++++++ drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/sg2042-mcu.c | 388 +++++++++++++++++++++++++++++ 5 files changed, 479 insertions(+) create mode 100644 Documentation/hwmon/sg2042-mcu.rst create mode 100644 drivers/hwmon/sg2042-mcu.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 913c11390a45..ea3b5be8fe4f 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -206,6 +206,7 @@ Hardware Monitoring Kernel Drivers sch5636 scpi-hwmon sfctemp + sg2042-mcu sht15 sht21 sht3x diff --git a/Documentation/hwmon/sg2042-mcu.rst b/Documentation/hwmon/sg2042-mcu.rst new file mode 100644 index 000000000000..077e79841d2e --- /dev/null +++ b/Documentation/hwmon/sg2042-mcu.rst @@ -0,0 +1,78 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver sg2042-mcu +======================== + +Supported chips: + + * Onboard MCU for sg2042 + + Addresses scanned: - + + Prefix: 'sg2042-mcu' + +Authors: + + - Inochi Amaoto + +Description +----------- + +This driver supprts hardware monitoring for onboard MCU with +i2c interface. + +Usage Notes +----------- + +This driver does not auto-detect devices. You will have to instantiate +the devices explicitly. +Please see Documentation/i2c/instantiating-devices.rst for details. + +Sysfs Attributes +---------------- + +The following table shows the standard entries support by the driver: + +================= ===================================================== +Name Description +================= ===================================================== +temp1_input Measured temperature of SoC +temp1_crit Critical high temperature +temp1_crit_hyst hysteresis temperature restore from Critical +temp2_input Measured temperature of the base board +================= ===================================================== + +The following table shows the extra entries support by the driver +(the MCU device is in i2c subsystem): + +================= ======= ============================================= +Name Perm Description +================= ======= ============================================= +reset_count RO Reset count of the SoC +uptime RO Seconds after the MCU is powered +reset_reason RO Reset reason for the last reset +repower_policy RW Execution policy when triggering repower +================= ======= ============================================= + +``repower_policy`` + The repower is triggered when the temperature of the SoC falls below + the hysteresis temperature after triggering a shutdown due to + reaching the critical temperature. + The valid values for this entry are "repower" and "keep". "keep" will + leave the SoC down when the triggering repower, and "repower" will + boot the SoC. + +Debugfs Interfaces +------------------ + +If debugfs is available, this driver exposes some hardware specific +data in ``/sys/kernel/debug/sg2042-mcu/*/``. + +================= ======= ============================================= +Name Format Description +================= ======= ============================================= +firmware_version 0x%02x firmware version of the MCU +pcb_version 0x%02x version number of the base board +board_type 0x%02x identifiers for the base board +mcu_type %d type of the MCU: 0 is STM32, 1 is GD32 +================= ======= ============================================= diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 1db349ecb1a4..65ea92529406 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2068,6 +2068,17 @@ config SENSORS_SFCTEMP This driver can also be built as a module. If so, the module will be called sfctemp. +config SENSORS_SG2042_MCU + tristate "Sophgo onboard MCU support" + depends on I2C + depends on ARCH_SOPHGO || COMPILE_TEST + help + Support for onboard MCU of Sophgo SG2042 SoCs. This mcu provides + power control and some basic information. + + This driver can be built as a module. If so, the module + will be called sg2042-mcu. + config SENSORS_SURFACE_FAN tristate "Surface Fan Driver" depends on SURFACE_AGGREGATOR diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 3ce8d6a9202e..9554d2fdcf7b 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -194,6 +194,7 @@ obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o obj-$(CONFIG_SENSORS_SCH5636) += sch5636.o obj-$(CONFIG_SENSORS_SFCTEMP) += sfctemp.o +obj-$(CONFIG_SENSORS_SG2042_MCU) += sg2042-mcu.o obj-$(CONFIG_SENSORS_SL28CPLD) += sl28cpld-hwmon.o obj-$(CONFIG_SENSORS_SHT15) += sht15.o obj-$(CONFIG_SENSORS_SHT21) += sht21.o diff --git a/drivers/hwmon/sg2042-mcu.c b/drivers/hwmon/sg2042-mcu.c new file mode 100644 index 000000000000..141045769354 --- /dev/null +++ b/drivers/hwmon/sg2042-mcu.c @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 Inochi Amaoto + * + * Sophgo power control mcu for SG2042 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* fixed MCU registers */ +#define REG_BOARD_TYPE 0x00 +#define REG_MCU_FIRMWARE_VERSION 0x01 +#define REG_PCB_VERSION 0x02 +#define REG_PWR_CTRL 0x03 +#define REG_SOC_TEMP 0x04 +#define REG_BOARD_TEMP 0x05 +#define REG_RST_COUNT 0x0a +#define REG_UPTIME 0x0b +#define REG_RESET_REASON 0x0d +#define REG_MCU_TYPE 0x18 +#define REG_REPOWER_POLICY 0x65 +#define REG_CRITICAL_TEMP 0x66 +#define REG_REPOWER_TEMP 0x67 + +#define REPOWER_POLICY_REBOOT 1 +#define REPOWER_POLICY_KEEP_OFF 2 + +#define MCU_POWER_MAX 0xff + +#define DEFINE_MCU_DEBUG_ATTR(_name, _reg, _format) \ + static int _name##_show(struct seq_file *seqf, \ + void *unused) \ + { \ + struct sg2042_mcu_data *mcu = seqf->private; \ + int ret; \ + ret = i2c_smbus_read_byte_data(mcu->client, (_reg)); \ + if (ret < 0) \ + return ret; \ + seq_printf(seqf, _format "\n", ret); \ + return 0; \ + } \ + DEFINE_SHOW_ATTRIBUTE(_name) \ + +struct sg2042_mcu_data { + struct i2c_client *client; + struct dentry *debugfs; + struct mutex mutex; +}; + +static struct dentry *sgmcu_debugfs; + +static ssize_t reset_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sg2042_mcu_data *mcu = dev_get_drvdata(dev); + int ret; + + ret = i2c_smbus_read_byte_data(mcu->client, REG_RST_COUNT); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t uptime_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sg2042_mcu_data *mcu = dev_get_drvdata(dev); + u8 time_val[2]; + int ret; + + ret = i2c_smbus_read_i2c_block_data(mcu->client, REG_UPTIME, + sizeof(time_val), time_val); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", + (time_val[0]) | (time_val[1] << 8)); +} + +static ssize_t reset_reason_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sg2042_mcu_data *mcu = dev_get_drvdata(dev); + int ret; + + ret = i2c_smbus_read_byte_data(mcu->client, REG_RESET_REASON); + if (ret < 0) + return ret; + + return sprintf(buf, "0x%02x\n", ret); +} + +static ssize_t repower_policy_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sg2042_mcu_data *mcu = dev_get_drvdata(dev); + int ret; + const char *action; + + ret = i2c_smbus_read_byte_data(mcu->client, REG_REPOWER_POLICY); + if (ret < 0) + return ret; + + if (ret == REPOWER_POLICY_REBOOT) + action = "repower"; + else if (ret == REPOWER_POLICY_KEEP_OFF) + action = "keep"; + else + action = "unknown"; + + return sprintf(buf, "%s\n", action); +} + +static ssize_t repower_policy_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sg2042_mcu_data *mcu = dev_get_drvdata(dev); + u8 value; + int ret; + + if (sysfs_streq("repower", buf)) + value = REPOWER_POLICY_REBOOT; + else if (sysfs_streq("keep", buf)) + value = REPOWER_POLICY_KEEP_OFF; + else + return -EINVAL; + + ret = i2c_smbus_write_byte_data(mcu->client, + REG_REPOWER_POLICY, value); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR_RO(reset_count); +static DEVICE_ATTR_RO(uptime); +static DEVICE_ATTR_RO(reset_reason); +static DEVICE_ATTR_RW(repower_policy); + +DEFINE_MCU_DEBUG_ATTR(firmware_version, REG_MCU_FIRMWARE_VERSION, "0x%02x"); +DEFINE_MCU_DEBUG_ATTR(pcb_version, REG_PCB_VERSION, "0x%02x"); +DEFINE_MCU_DEBUG_ATTR(board_type, REG_BOARD_TYPE, "0x%02x"); +DEFINE_MCU_DEBUG_ATTR(mcu_type, REG_MCU_TYPE, "%d"); + +static struct attribute *sg2042_mcu_attrs[] = { + &dev_attr_reset_count.attr, + &dev_attr_uptime.attr, + &dev_attr_reset_reason.attr, + &dev_attr_repower_policy.attr, + NULL +}; + +static const struct attribute_group sg2042_mcu_attr_group = { + .attrs = sg2042_mcu_attrs, +}; + +static const struct attribute_group *sg2042_mcu_groups[] = { + &sg2042_mcu_attr_group, + NULL +}; + +static const struct hwmon_channel_info * const sg2042_mcu_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_CRIT | + HWMON_T_CRIT_HYST, + HWMON_T_INPUT), + NULL +}; + +static int sg2042_mcu_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct sg2042_mcu_data *mcu = dev_get_drvdata(dev); + int tmp; + u8 reg; + + switch (attr) { + case hwmon_temp_input: + reg = channel ? REG_BOARD_TEMP : REG_SOC_TEMP; + break; + case hwmon_temp_crit: + reg = REG_CRITICAL_TEMP; + break; + case hwmon_temp_crit_hyst: + reg = REG_REPOWER_TEMP; + break; + default: + return -EOPNOTSUPP; + } + + tmp = i2c_smbus_read_byte_data(mcu->client, reg); + if (tmp < 0) + return tmp; + *val = tmp * 1000; + + return 0; +} + +static int sg2042_mcu_write(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct sg2042_mcu_data *mcu = dev_get_drvdata(dev); + int temp = val / 1000; + int hyst_temp, crit_temp; + u8 reg; + + temp = clamp_val(temp, 0, MCU_POWER_MAX); + + guard(mutex)(&mcu->mutex); + + switch (attr) { + case hwmon_temp_crit: + hyst_temp = i2c_smbus_read_byte_data(mcu->client, + REG_REPOWER_TEMP); + if (hyst_temp < 0) + return hyst_temp; + + crit_temp = temp; + reg = REG_CRITICAL_TEMP; + break; + case hwmon_temp_crit_hyst: + crit_temp = i2c_smbus_read_byte_data(mcu->client, + REG_CRITICAL_TEMP); + if (crit_temp < 0) + return crit_temp; + + hyst_temp = temp; + reg = REG_REPOWER_TEMP; + break; + default: + return -EOPNOTSUPP; + } + + /* + * ensure hyst_temp is smaller to avoid MCU from + * keeping triggering repower event. + */ + if (crit_temp < hyst_temp) + return -EINVAL; + + return i2c_smbus_write_byte_data(mcu->client, reg, temp); +} + +static umode_t sg2042_mcu_is_visible(const void *_data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + return 0444; + case hwmon_temp_crit: + case hwmon_temp_crit_hyst: + if (channel == 0) + return 0644; + break; + default: + break; + } + break; + default: + break; + } + return 0; +} + +static const struct hwmon_ops sg2042_mcu_ops = { + .is_visible = sg2042_mcu_is_visible, + .read = sg2042_mcu_read, + .write = sg2042_mcu_write, +}; + +static const struct hwmon_chip_info sg2042_mcu_chip_info = { + .ops = &sg2042_mcu_ops, + .info = sg2042_mcu_info, +}; + +static void sg2042_mcu_debugfs_init(struct sg2042_mcu_data *mcu, + struct device *dev) +{ + mcu->debugfs = debugfs_create_dir(dev_name(dev), sgmcu_debugfs); + + debugfs_create_file("firmware_version", 0444, mcu->debugfs, + mcu, &firmware_version_fops); + debugfs_create_file("pcb_version", 0444, mcu->debugfs, mcu, + &pcb_version_fops); + debugfs_create_file("mcu_type", 0444, mcu->debugfs, mcu, + &mcu_type_fops); + debugfs_create_file("board_type", 0444, mcu->debugfs, mcu, + &board_type_fops); +} + +static int sg2042_mcu_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct sg2042_mcu_data *mcu; + struct device *hwmon_dev; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + mcu = devm_kmalloc(dev, sizeof(*mcu), GFP_KERNEL); + if (!mcu) + return -ENOMEM; + + mutex_init(&mcu->mutex); + mcu->client = client; + + i2c_set_clientdata(client, mcu); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, "sg2042_mcu", + mcu, + &sg2042_mcu_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + sg2042_mcu_debugfs_init(mcu, dev); + + return 0; +} + +static void sg2042_mcu_i2c_remove(struct i2c_client *client) +{ + struct sg2042_mcu_data *mcu = i2c_get_clientdata(client); + + debugfs_remove_recursive(mcu->debugfs); +} + +static const struct i2c_device_id sg2042_mcu_id[] = { + { "sg2042-hwmon-mcu", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, sg2042_mcu_id); + +static const struct of_device_id sg2042_mcu_of_id[] = { + { .compatible = "sophgo,sg2042-hwmon-mcu" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sg2042_mcu_of_id); + +static struct i2c_driver sg2042_mcu_driver = { + .driver = { + .name = "sg2042-mcu", + .of_match_table = sg2042_mcu_of_id, + .dev_groups = sg2042_mcu_groups, + }, + .probe = sg2042_mcu_i2c_probe, + .remove = sg2042_mcu_i2c_remove, + .id_table = sg2042_mcu_id, +}; + +static int __init sg2042_mcu_init(void) +{ + sgmcu_debugfs = debugfs_create_dir("sg2042-mcu", NULL); + return i2c_add_driver(&sg2042_mcu_driver); +} + +static void __exit sg2042_mcu_exit(void) +{ + debugfs_remove_recursive(sgmcu_debugfs); + i2c_del_driver(&sg2042_mcu_driver); +} + +module_init(sg2042_mcu_init); +module_exit(sg2042_mcu_exit); + +MODULE_AUTHOR("Inochi Amaoto "); +MODULE_DESCRIPTION("MCU I2C driver for SG2042 soc platform"); +MODULE_LICENSE("GPL"); From ac9cca7a6a17c6d8bd849be669005fac0420d4b5 Mon Sep 17 00:00:00 2001 From: Shen Lichuan Date: Tue, 27 Aug 2024 15:04:42 +0800 Subject: [PATCH 212/573] hwmon: (pc87360) Use min() macro Use the min() macro to simplify the pc87360_init_device() function and improve its readability. Signed-off-by: Shen Lichuan Message-ID: <20240827070442.40667-1-shenlichuan@vivo.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/pc87360.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pc87360.c b/drivers/hwmon/pc87360.c index 9e9681b2e8c5..788b5d58f77e 100644 --- a/drivers/hwmon/pc87360.c +++ b/drivers/hwmon/pc87360.c @@ -1315,7 +1315,7 @@ static void pc87360_init_device(struct platform_device *pdev, (reg & 0xC0) | 0x11); } - nr = data->innr < 11 ? data->innr : 11; + nr = min(data->innr, 11); for (i = 0; i < nr; i++) { reg = pc87360_read_value(data, LD_IN, i, PC87365_REG_IN_STATUS); From ebb75a3c5265ceeebd2b0708d013735e5eafbb69 Mon Sep 17 00:00:00 2001 From: Johannes Kirchmair Date: Tue, 27 Aug 2024 07:44:54 +0200 Subject: [PATCH 213/573] hwmon: (pwmfan) Do not force disable pwm controller The pwm1_enable attribute of the pwmfan driver influences the mode of operation, especially in case of a requested pwm1 duty cycle of zero. Especially setting pwm1_enable to two, should keep the pwm controller enabled even if the duty cycle is set to zero [1]. This is not the case at the moment, as the pwm controller is disabled always if pwm1 is set to zero. This commit tries to fix this behavior. [1] https://docs.kernel.org/hwmon/pwm-fan.html Signed-off-by: Johannes Kirchmair Message-ID: <20240827054454.521494-1-mailinglist1@johanneskirchmair.de> Signed-off-by: Guenter Roeck --- drivers/hwmon/pwm-fan.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index a1712649b07e..c434db4656e7 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -167,7 +167,7 @@ disable_regulator: return ret; } -static int pwm_fan_power_off(struct pwm_fan_ctx *ctx) +static int pwm_fan_power_off(struct pwm_fan_ctx *ctx, bool force_disable) { struct pwm_state *state = &ctx->pwm_state; bool enable_regulator = false; @@ -180,7 +180,8 @@ static int pwm_fan_power_off(struct pwm_fan_ctx *ctx) state, &enable_regulator); - state->enabled = false; + if (force_disable) + state->enabled = false; state->duty_cycle = 0; ret = pwm_apply_might_sleep(ctx->pwm, state); if (ret) { @@ -213,7 +214,7 @@ static int __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm) return ret; ret = pwm_fan_power_on(ctx); } else { - ret = pwm_fan_power_off(ctx); + ret = pwm_fan_power_off(ctx, false); } if (!ret) ctx->pwm_value = pwm; @@ -468,7 +469,7 @@ static void pwm_fan_cleanup(void *__ctx) del_timer_sync(&ctx->rpm_timer); /* Switch off everything */ ctx->enable_mode = pwm_disable_reg_disable; - pwm_fan_power_off(ctx); + pwm_fan_power_off(ctx, true); } static int pwm_fan_probe(struct platform_device *pdev) @@ -661,7 +662,7 @@ static int pwm_fan_suspend(struct device *dev) { struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); - return pwm_fan_power_off(ctx); + return pwm_fan_power_off(ctx, true); } static int pwm_fan_resume(struct device *dev) From 61978807b00f8a1817b0e5580981af1cd2f428a5 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 21 Aug 2024 16:54:43 -0500 Subject: [PATCH 214/573] power: supply: axp20x_battery: Remove design from min and max voltage The POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN and POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN values should be immutable properties of the battery, but for this driver they are writable values and used as the minimum and maximum values for charging. Remove the DESIGN designation from these values. Fixes: 46c202b5f25f ("power: supply: add battery driver for AXP20X and AXP22X PMICs") Suggested-by: Chen-Yu Tsai Signed-off-by: Chris Morgan Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240821215456.962564-3-macroalpha82@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_battery.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c index 6ac5c80cfda2..7520b599eb3d 100644 --- a/drivers/power/supply/axp20x_battery.c +++ b/drivers/power/supply/axp20x_battery.c @@ -303,11 +303,11 @@ static int axp20x_battery_get_prop(struct power_supply *psy, val->intval = reg & AXP209_FG_PERCENT; break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: return axp20x_batt->data->get_max_voltage(axp20x_batt, &val->intval); - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_MIN: ret = regmap_read(axp20x_batt->regmap, AXP20X_V_OFF, ®); if (ret) return ret; @@ -455,10 +455,10 @@ static int axp20x_battery_set_prop(struct power_supply *psy, struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); switch (psp) { - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_MIN: return axp20x_set_voltage_min_design(axp20x_batt, val->intval); - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: return axp20x_batt->data->set_max_voltage(axp20x_batt, val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: @@ -493,8 +493,8 @@ static enum power_supply_property axp20x_battery_props[] = { POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_HEALTH, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN, POWER_SUPPLY_PROP_CAPACITY, }; @@ -502,8 +502,8 @@ static int axp20x_battery_prop_writeable(struct power_supply *psy, enum power_supply_property psp) { return psp == POWER_SUPPLY_PROP_STATUS || - psp == POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN || - psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN || + psp == POWER_SUPPLY_PROP_VOLTAGE_MIN || + psp == POWER_SUPPLY_PROP_VOLTAGE_MAX || psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT || psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX; } From db97fecb55cee4eed2f8dcdc17c4831719cbfe4d Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 21 Aug 2024 16:54:44 -0500 Subject: [PATCH 215/573] power: supply: axp20x_battery: Make iio and battery config per device Move the configuration of battery specific information and available iio channels from the probe function to a device specific routine, allowing us to use this driver for devices with slightly different configurations (such as the AXP717). Signed-off-by: Chris Morgan Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240821215456.962564-4-macroalpha82@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_battery.c | 137 +++++++++++++++++--------- 1 file changed, 88 insertions(+), 49 deletions(-) diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c index 7520b599eb3d..c903c588b361 100644 --- a/drivers/power/supply/axp20x_battery.c +++ b/drivers/power/supply/axp20x_battery.c @@ -58,11 +58,19 @@ struct axp20x_batt_ps; struct axp_data { - int ccc_scale; - int ccc_offset; - bool has_fg_valid; + int ccc_scale; + int ccc_offset; + unsigned int ccc_reg; + unsigned int ccc_mask; + bool has_fg_valid; + const struct power_supply_desc *bat_ps_desc; int (*get_max_voltage)(struct axp20x_batt_ps *batt, int *val); int (*set_max_voltage)(struct axp20x_batt_ps *batt, int val); + int (*cfg_iio_chan)(struct platform_device *pdev, + struct axp20x_batt_ps *axp_batt); + void (*set_bat_info)(struct platform_device *pdev, + struct axp20x_batt_ps *axp_batt, + struct power_supply_battery_info *info); }; struct axp20x_batt_ps { @@ -508,7 +516,7 @@ static int axp20x_battery_prop_writeable(struct power_supply *psy, psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX; } -static const struct power_supply_desc axp20x_batt_ps_desc = { +static const struct power_supply_desc axp209_batt_ps_desc = { .name = "axp20x-battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = axp20x_battery_props, @@ -518,27 +526,94 @@ static const struct power_supply_desc axp20x_batt_ps_desc = { .set_property = axp20x_battery_set_prop, }; +static int axp209_bat_cfg_iio_channels(struct platform_device *pdev, + struct axp20x_batt_ps *axp_batt) +{ + axp_batt->batt_v = devm_iio_channel_get(&pdev->dev, "batt_v"); + if (IS_ERR(axp_batt->batt_v)) { + if (PTR_ERR(axp_batt->batt_v) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(axp_batt->batt_v); + } + + axp_batt->batt_chrg_i = devm_iio_channel_get(&pdev->dev, + "batt_chrg_i"); + if (IS_ERR(axp_batt->batt_chrg_i)) { + if (PTR_ERR(axp_batt->batt_chrg_i) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(axp_batt->batt_chrg_i); + } + + axp_batt->batt_dischrg_i = devm_iio_channel_get(&pdev->dev, + "batt_dischrg_i"); + if (IS_ERR(axp_batt->batt_dischrg_i)) { + if (PTR_ERR(axp_batt->batt_dischrg_i) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(axp_batt->batt_dischrg_i); + } + + return 0; +} + +static void axp209_set_battery_info(struct platform_device *pdev, + struct axp20x_batt_ps *axp_batt, + struct power_supply_battery_info *info) +{ + int vmin = info->voltage_min_design_uv; + int ccc = info->constant_charge_current_max_ua; + + if (vmin > 0 && axp20x_set_voltage_min_design(axp_batt, vmin)) + dev_err(&pdev->dev, + "couldn't set voltage_min_design\n"); + + /* Set max to unverified value to be able to set CCC */ + axp_batt->max_ccc = ccc; + + if (ccc <= 0 || axp20x_set_constant_charge_current(axp_batt, ccc)) { + dev_err(&pdev->dev, + "couldn't set ccc from DT: fallback to min value\n"); + ccc = 300000; + axp_batt->max_ccc = ccc; + axp20x_set_constant_charge_current(axp_batt, ccc); + } +} + static const struct axp_data axp209_data = { .ccc_scale = 100000, .ccc_offset = 300000, + .ccc_reg = AXP20X_CHRG_CTRL1, + .ccc_mask = AXP20X_CHRG_CTRL1_TGT_CURR, + .bat_ps_desc = &axp209_batt_ps_desc, .get_max_voltage = axp20x_battery_get_max_voltage, .set_max_voltage = axp20x_battery_set_max_voltage, + .cfg_iio_chan = axp209_bat_cfg_iio_channels, + .set_bat_info = axp209_set_battery_info, }; static const struct axp_data axp221_data = { .ccc_scale = 150000, .ccc_offset = 300000, + .ccc_reg = AXP20X_CHRG_CTRL1, + .ccc_mask = AXP20X_CHRG_CTRL1_TGT_CURR, .has_fg_valid = true, + .bat_ps_desc = &axp209_batt_ps_desc, .get_max_voltage = axp22x_battery_get_max_voltage, .set_max_voltage = axp22x_battery_set_max_voltage, + .cfg_iio_chan = axp209_bat_cfg_iio_channels, + .set_bat_info = axp209_set_battery_info, }; static const struct axp_data axp813_data = { .ccc_scale = 200000, .ccc_offset = 200000, + .ccc_reg = AXP20X_CHRG_CTRL1, + .ccc_mask = AXP20X_CHRG_CTRL1_TGT_CURR, .has_fg_valid = true, + .bat_ps_desc = &axp209_batt_ps_desc, .get_max_voltage = axp813_battery_get_max_voltage, .set_max_voltage = axp20x_battery_set_max_voltage, + .cfg_iio_chan = axp209_bat_cfg_iio_channels, + .set_bat_info = axp209_set_battery_info, }; static const struct of_device_id axp20x_battery_ps_id[] = { @@ -561,6 +636,7 @@ static int axp20x_power_probe(struct platform_device *pdev) struct power_supply_config psy_cfg = {}; struct power_supply_battery_info *info; struct device *dev = &pdev->dev; + int ret; if (!of_device_is_available(pdev->dev.of_node)) return -ENODEV; @@ -572,29 +648,6 @@ static int axp20x_power_probe(struct platform_device *pdev) axp20x_batt->dev = &pdev->dev; - axp20x_batt->batt_v = devm_iio_channel_get(&pdev->dev, "batt_v"); - if (IS_ERR(axp20x_batt->batt_v)) { - if (PTR_ERR(axp20x_batt->batt_v) == -ENODEV) - return -EPROBE_DEFER; - return PTR_ERR(axp20x_batt->batt_v); - } - - axp20x_batt->batt_chrg_i = devm_iio_channel_get(&pdev->dev, - "batt_chrg_i"); - if (IS_ERR(axp20x_batt->batt_chrg_i)) { - if (PTR_ERR(axp20x_batt->batt_chrg_i) == -ENODEV) - return -EPROBE_DEFER; - return PTR_ERR(axp20x_batt->batt_chrg_i); - } - - axp20x_batt->batt_dischrg_i = devm_iio_channel_get(&pdev->dev, - "batt_dischrg_i"); - if (IS_ERR(axp20x_batt->batt_dischrg_i)) { - if (PTR_ERR(axp20x_batt->batt_dischrg_i) == -ENODEV) - return -EPROBE_DEFER; - return PTR_ERR(axp20x_batt->batt_dischrg_i); - } - axp20x_batt->regmap = dev_get_regmap(pdev->dev.parent, NULL); platform_set_drvdata(pdev, axp20x_batt); @@ -603,8 +656,12 @@ static int axp20x_power_probe(struct platform_device *pdev) axp20x_batt->data = (struct axp_data *)of_device_get_match_data(dev); + ret = axp20x_batt->data->cfg_iio_chan(pdev, axp20x_batt); + if (ret) + return ret; + axp20x_batt->batt = devm_power_supply_register(&pdev->dev, - &axp20x_batt_ps_desc, + axp20x_batt->data->bat_ps_desc, &psy_cfg); if (IS_ERR(axp20x_batt->batt)) { dev_err(&pdev->dev, "failed to register power supply: %ld\n", @@ -613,33 +670,15 @@ static int axp20x_power_probe(struct platform_device *pdev) } if (!power_supply_get_battery_info(axp20x_batt->batt, &info)) { - int vmin = info->voltage_min_design_uv; - int ccc = info->constant_charge_current_max_ua; - - if (vmin > 0 && axp20x_set_voltage_min_design(axp20x_batt, - vmin)) - dev_err(&pdev->dev, - "couldn't set voltage_min_design\n"); - - /* Set max to unverified value to be able to set CCC */ - axp20x_batt->max_ccc = ccc; - - if (ccc <= 0 || axp20x_set_constant_charge_current(axp20x_batt, - ccc)) { - dev_err(&pdev->dev, - "couldn't set constant charge current from DT: fallback to minimum value\n"); - ccc = 300000; - axp20x_batt->max_ccc = ccc; - axp20x_set_constant_charge_current(axp20x_batt, ccc); - } + axp20x_batt->data->set_bat_info(pdev, axp20x_batt, info); + power_supply_put_battery_info(axp20x_batt->batt, info); } /* * Update max CCC to a valid value if battery info is present or set it * to current register value by default. */ - axp20x_get_constant_charge_current(axp20x_batt, - &axp20x_batt->max_ccc); + axp20x_get_constant_charge_current(axp20x_batt, &axp20x_batt->max_ccc); return 0; } From ae640fc690353f6181740b50a0d6761bc67ebaa9 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 21 Aug 2024 16:54:45 -0500 Subject: [PATCH 216/573] power: supply: axp20x_usb_power: Make VBUS and IIO config per device Make reading of the vbus value and configuring of the iio channels device specific, to allow additional devices (such as the AXP717) to be supported by this driver. Signed-off-by: Chris Morgan Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240821215456.962564-5-macroalpha82@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_usb_power.c | 87 +++++++++++++++---------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index dae7e5cfc54e..cd9e92f2ce71 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -45,6 +45,8 @@ */ #define DEBOUNCE_TIME msecs_to_jiffies(50) +struct axp20x_usb_power; + struct axp_data { const struct power_supply_desc *power_desc; const char * const *irq_names; @@ -58,6 +60,10 @@ struct axp_data { struct reg_field usb_bc_det_fld; struct reg_field vbus_disable_bit; bool vbus_needs_polling: 1; + void (*axp20x_read_vbus)(struct work_struct *work); + int (*axp20x_cfg_iio_chan)(struct platform_device *pdev, + struct axp20x_usb_power *power); + int (*axp20x_cfg_adc_reg)(struct axp20x_usb_power *power); }; struct axp20x_usb_power { @@ -385,6 +391,36 @@ static int axp20x_usb_power_prop_writeable(struct power_supply *psy, psp == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT; } +static int axp20x_configure_iio_channels(struct platform_device *pdev, + struct axp20x_usb_power *power) +{ + power->vbus_v = devm_iio_channel_get(&pdev->dev, "vbus_v"); + if (IS_ERR(power->vbus_v)) { + if (PTR_ERR(power->vbus_v) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(power->vbus_v); + } + + power->vbus_i = devm_iio_channel_get(&pdev->dev, "vbus_i"); + if (IS_ERR(power->vbus_i)) { + if (PTR_ERR(power->vbus_i) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(power->vbus_i); + } + + return 0; +} + +static int axp20x_configure_adc_registers(struct axp20x_usb_power *power) +{ + /* Enable vbus voltage and current measurement */ + return regmap_update_bits(power->regmap, AXP20X_ADC_EN1, + AXP20X_ADC_EN1_VBUS_CURR | + AXP20X_ADC_EN1_VBUS_VOLT, + AXP20X_ADC_EN1_VBUS_CURR | + AXP20X_ADC_EN1_VBUS_VOLT); +} + static enum power_supply_property axp20x_usb_power_properties[] = { POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, @@ -505,6 +541,9 @@ static const struct axp_data axp192_data = { .curr_lim_fld = REG_FIELD(AXP20X_VBUS_IPSOUT_MGMT, 0, 1), .vbus_valid_bit = REG_FIELD(AXP192_USB_OTG_STATUS, 2, 2), .vbus_mon_bit = REG_FIELD(AXP20X_VBUS_MON, 3, 3), + .axp20x_read_vbus = &axp20x_usb_power_poll_vbus, + .axp20x_cfg_iio_chan = axp20x_configure_iio_channels, + .axp20x_cfg_adc_reg = axp20x_configure_adc_registers, }; static const struct axp_data axp202_data = { @@ -516,6 +555,9 @@ static const struct axp_data axp202_data = { .curr_lim_fld = REG_FIELD(AXP20X_VBUS_IPSOUT_MGMT, 0, 1), .vbus_valid_bit = REG_FIELD(AXP20X_USB_OTG_STATUS, 2, 2), .vbus_mon_bit = REG_FIELD(AXP20X_VBUS_MON, 3, 3), + .axp20x_read_vbus = &axp20x_usb_power_poll_vbus, + .axp20x_cfg_iio_chan = axp20x_configure_iio_channels, + .axp20x_cfg_adc_reg = axp20x_configure_adc_registers, }; static const struct axp_data axp221_data = { @@ -526,6 +568,9 @@ static const struct axp_data axp221_data = { .curr_lim_table_size = ARRAY_SIZE(axp221_usb_curr_lim_table), .curr_lim_fld = REG_FIELD(AXP20X_VBUS_IPSOUT_MGMT, 0, 1), .vbus_needs_polling = true, + .axp20x_read_vbus = &axp20x_usb_power_poll_vbus, + .axp20x_cfg_iio_chan = axp20x_configure_iio_channels, + .axp20x_cfg_adc_reg = axp20x_configure_adc_registers, }; static const struct axp_data axp223_data = { @@ -536,6 +581,9 @@ static const struct axp_data axp223_data = { .curr_lim_table_size = ARRAY_SIZE(axp20x_usb_curr_lim_table), .curr_lim_fld = REG_FIELD(AXP20X_VBUS_IPSOUT_MGMT, 0, 1), .vbus_needs_polling = true, + .axp20x_read_vbus = &axp20x_usb_power_poll_vbus, + .axp20x_cfg_iio_chan = axp20x_configure_iio_channels, + .axp20x_cfg_adc_reg = axp20x_configure_adc_registers, }; static const struct axp_data axp813_data = { @@ -549,6 +597,9 @@ static const struct axp_data axp813_data = { .usb_bc_det_fld = REG_FIELD(AXP288_BC_DET_STAT, 5, 7), .vbus_disable_bit = REG_FIELD(AXP20X_VBUS_IPSOUT_MGMT, 7, 7), .vbus_needs_polling = true, + .axp20x_read_vbus = &axp20x_usb_power_poll_vbus, + .axp20x_cfg_iio_chan = axp20x_configure_iio_channels, + .axp20x_cfg_adc_reg = axp20x_configure_adc_registers, }; #ifdef CONFIG_PM_SLEEP @@ -590,36 +641,6 @@ static int axp20x_usb_power_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(axp20x_usb_power_pm_ops, axp20x_usb_power_suspend, axp20x_usb_power_resume); -static int configure_iio_channels(struct platform_device *pdev, - struct axp20x_usb_power *power) -{ - power->vbus_v = devm_iio_channel_get(&pdev->dev, "vbus_v"); - if (IS_ERR(power->vbus_v)) { - if (PTR_ERR(power->vbus_v) == -ENODEV) - return -EPROBE_DEFER; - return PTR_ERR(power->vbus_v); - } - - power->vbus_i = devm_iio_channel_get(&pdev->dev, "vbus_i"); - if (IS_ERR(power->vbus_i)) { - if (PTR_ERR(power->vbus_i) == -ENODEV) - return -EPROBE_DEFER; - return PTR_ERR(power->vbus_i); - } - - return 0; -} - -static int configure_adc_registers(struct axp20x_usb_power *power) -{ - /* Enable vbus voltage and current measurement */ - return regmap_update_bits(power->regmap, AXP20X_ADC_EN1, - AXP20X_ADC_EN1_VBUS_CURR | - AXP20X_ADC_EN1_VBUS_VOLT, - AXP20X_ADC_EN1_VBUS_CURR | - AXP20X_ADC_EN1_VBUS_VOLT); -} - static int axp20x_regmap_field_alloc_optional(struct device *dev, struct regmap *regmap, struct reg_field fdesc, @@ -707,7 +728,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) return ret; ret = devm_delayed_work_autocancel(&pdev->dev, &power->vbus_detect, - axp20x_usb_power_poll_vbus); + axp_data->axp20x_read_vbus); if (ret) return ret; @@ -718,9 +739,9 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) return ret; if (IS_ENABLED(CONFIG_AXP20X_ADC)) - ret = configure_iio_channels(pdev, power); + ret = axp_data->axp20x_cfg_iio_chan(pdev, power); else - ret = configure_adc_registers(power); + ret = axp_data->axp20x_cfg_adc_reg(power); if (ret) return ret; From 6f5cdb7ec8836bb5e5ab221c2f49e2b170d5a978 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 21 Aug 2024 16:54:46 -0500 Subject: [PATCH 217/573] dt-bindings: power: supply: axp20x: Add input-current-limit-microamp Allow specifying a hard limit of the maximum input current. Some PMICs such as the AXP717 can pull up to 3.25A, so allow a value to be specified that clamps this in the event the hardware is not designed for it. Signed-off-by: Chris Morgan Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240821215456.962564-6-macroalpha82@gmail.com Signed-off-by: Sebastian Reichel --- .../x-powers,axp20x-usb-power-supply.yaml | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-usb-power-supply.yaml b/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-usb-power-supply.yaml index 34b7959d6772..ab24ebf2852f 100644 --- a/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-usb-power-supply.yaml +++ b/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-usb-power-supply.yaml @@ -15,9 +15,6 @@ maintainers: - Chen-Yu Tsai - Sebastian Reichel -allOf: - - $ref: power-supply.yaml# - properties: compatible: oneOf: @@ -31,8 +28,63 @@ properties: - const: x-powers,axp803-usb-power-supply - const: x-powers,axp813-usb-power-supply + input-current-limit-microamp: + description: + Optional value to clamp the maximum input current limit to for + the device. If omitted, the programmed value from the EFUSE will + be used. + minimum: 100000 + maximum: 4000000 required: - compatible +allOf: + - $ref: power-supply.yaml# + - if: + properties: + compatible: + contains: + enum: + - x-powers,axp192-usb-power-supply + then: + properties: + input-current-limit-microamp: + enum: [100000, 500000] + + - if: + properties: + compatible: + contains: + enum: + - x-powers,axp202-usb-power-supply + - x-powers,axp223-usb-power-supply + then: + properties: + input-current-limit-microamp: + enum: [100000, 500000, 900000] + + - if: + properties: + compatible: + contains: + enum: + - x-powers,axp221-usb-power-supply + then: + properties: + input-current-limit-microamp: + enum: [500000, 900000] + + - if: + properties: + compatible: + contains: + enum: + - x-powers,axp813-usb-power-supply + then: + properties: + input-current-limit-microamp: + enum: [100000, 500000, 900000, 1500000, 2000000, 2500000, + 3000000, 3500000, 4000000] + additionalProperties: false From 6934da720aac7b0feb99d08ff27fd245a962d8d2 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 21 Aug 2024 16:54:47 -0500 Subject: [PATCH 218/573] power: supply: axp20x_usb_power: add input-current-limit-microamp Allow users to specify a maximum input current for the device. Some devices allow up to 3.25A of input current (such as the AXP717), which may be too much for some implementations. Signed-off-by: Chris Morgan Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240821215456.962564-7-macroalpha82@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_usb_power.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index cd9e92f2ce71..69fbb5861934 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -80,6 +80,7 @@ struct axp20x_usb_power { struct iio_channel *vbus_v; struct iio_channel *vbus_i; struct delayed_work vbus_detect; + int max_input_cur; unsigned int old_status; unsigned int online; unsigned int num_irqs; @@ -323,6 +324,13 @@ static int axp20x_usb_power_set_input_current_limit(struct axp20x_usb_power *pow if (intval == -1) return -EINVAL; + if (power->max_input_cur && (intval > power->max_input_cur)) { + dev_warn(power->dev, + "reqested current %d clamped to max current %d\n", + intval, power->max_input_cur); + intval = power->max_input_cur; + } + /* * BC1.2 detection can cause a race condition if we try to set a current * limit while it's in progress. When it finishes it will overwrite the @@ -661,6 +669,18 @@ static int axp20x_regmap_field_alloc_optional(struct device *dev, return 0; } +/* Optionally allow users to specify a maximum charging current. */ +static void axp20x_usb_power_parse_dt(struct device *dev, + struct axp20x_usb_power *power) +{ + int ret; + + ret = device_property_read_u32(dev, "input-current-limit-microamp", + &power->max_input_cur); + if (ret) + dev_dbg(dev, "%s() no input-current-limit specified\n", __func__); +} + static int axp20x_usb_power_probe(struct platform_device *pdev) { struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); @@ -697,6 +717,8 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) if (IS_ERR(power->curr_lim_fld)) return PTR_ERR(power->curr_lim_fld); + axp20x_usb_power_parse_dt(&pdev->dev, power); + ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap, axp_data->vbus_valid_bit, &power->vbus_valid_bit); From dc123a1a80933b7fba1cf53cec77c2425268ff85 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 21 Aug 2024 16:54:48 -0500 Subject: [PATCH 219/573] dt-bindings: power: supply: axp20x-battery: Add monitored-battery Document the monitored-battery property, which the existing driver can use to set certain properties. Acked-by: Krzysztof Kozlowski Signed-off-by: Chris Morgan Link: https://lore.kernel.org/r/20240821215456.962564-8-macroalpha82@gmail.com Signed-off-by: Sebastian Reichel --- .../power/supply/x-powers,axp20x-battery-power-supply.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-battery-power-supply.yaml b/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-battery-power-supply.yaml index e0b95ecbbebd..f196bf70b248 100644 --- a/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-battery-power-supply.yaml +++ b/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-battery-power-supply.yaml @@ -28,6 +28,12 @@ properties: - const: x-powers,axp813-battery-power-supply - const: x-powers,axp813-battery-power-supply + monitored-battery: + description: + Specifies the phandle of an optional simple-battery connected to + this gauge. + $ref: /schemas/types.yaml#/definitions/phandle + required: - compatible From 3a3acf839b2cedf092bdd1ff65b0e9895df1656b Mon Sep 17 00:00:00 2001 From: Artur Weber Date: Sat, 17 Aug 2024 12:51:14 +0200 Subject: [PATCH 220/573] power: supply: max17042_battery: Fix SOC threshold calc w/ no current sense Commit 223a3b82834f ("power: supply: max17042_battery: use VFSOC for capacity when no rsns") made it so that capacity on systems without current sensing would be read from VFSOC instead of RepSOC. However, the SOC threshold calculation still read RepSOC to get the SOC regardless of the current sensing option state. Fix this by applying the same conditional to determine which register should be read. This also seems to be the intended behavior as per the datasheet - SOC alert config value in MiscCFG on setups without current sensing is set to a value of 0b11, indicating SOC alerts being generated based on VFSOC, instead of 0b00 which indicates SOC alerts being generated based on RepSOC. This fixes an issue on the Galaxy S3/Midas boards, where the alert interrupt would be constantly retriggered, causing high CPU usage on idle (around ~12%-15%). Fixes: e5f3872d2044 ("max17042: Add support for signalling change in SOC") Signed-off-by: Artur Weber Reviewed-by: Henrik Grimler Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240817-max17042-soc-threshold-fix-v1-1-72b45899c3cc@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index e7d37e422c3f..496c3e1f2ee6 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -853,7 +853,10 @@ static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off) /* program interrupt thresholds such that we should * get interrupt for every 'off' perc change in the soc */ - regmap_read(map, MAX17042_RepSOC, &soc); + if (chip->pdata->enable_current_sense) + regmap_read(map, MAX17042_RepSOC, &soc); + else + regmap_read(map, MAX17042_VFSOC, &soc); soc >>= 8; soc_tr = (soc + off) << 8; if (off < soc) From ba7e053ec89f61be9d27bfb244de52849b5138aa Mon Sep 17 00:00:00 2001 From: Artur Weber Date: Fri, 16 Aug 2024 10:19:09 +0200 Subject: [PATCH 221/573] power: supply: max77693: Expose input current limit and CC current properties There are two charger current limit registers: - Fast charge current limit (which controls current going from the charger to the battery); - CHGIN input current limit (which controls current going into the charger through the cable). Add the necessary functions to retrieve the CHGIN input limit (from CHARGER regulator) and maximum fast charge current values, and expose them as power supply properties. Tested-by: Henrik Grimler Signed-off-by: Artur Weber Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240816-max77693-charger-extcon-v4-3-050a0a9bfea0@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max77693_charger.c | 52 +++++++++++++++++++++++++ include/linux/mfd/max77693-private.h | 5 +++ 2 files changed, 57 insertions(+) diff --git a/drivers/power/supply/max77693_charger.c b/drivers/power/supply/max77693_charger.c index 2001e12c9f7d..4caac142c428 100644 --- a/drivers/power/supply/max77693_charger.c +++ b/drivers/power/supply/max77693_charger.c @@ -197,12 +197,58 @@ static int max77693_get_online(struct regmap *regmap, int *val) return 0; } +/* + * There are *two* current limit registers: + * - CHGIN limit, which limits the input current from the external charger; + * - Fast charge current limit, which limits the current going to the battery. + */ + +static int max77693_get_input_current_limit(struct regmap *regmap, int *val) +{ + unsigned int data; + int ret; + + ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_CNFG_09, &data); + if (ret < 0) + return ret; + + data &= CHG_CNFG_09_CHGIN_ILIM_MASK; + data >>= CHG_CNFG_09_CHGIN_ILIM_SHIFT; + + if (data <= 0x03) + /* The first four values (0x00..0x03) are 60mA */ + *val = 60000; + else + *val = data * 20000; /* 20mA steps */ + + return 0; +} + +static int max77693_get_fast_charge_current(struct regmap *regmap, int *val) +{ + unsigned int data; + int ret; + + ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_CNFG_02, &data); + if (ret < 0) + return ret; + + data &= CHG_CNFG_02_CC_MASK; + data >>= CHG_CNFG_02_CC_SHIFT; + + *val = data * 33300; /* 33.3mA steps */ + + return 0; +} + static enum power_supply_property max77693_charger_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, }; @@ -231,6 +277,12 @@ static int max77693_charger_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_ONLINE: ret = max77693_get_online(regmap, &val->intval); break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = max77693_get_input_current_limit(regmap, &val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + ret = max77693_get_fast_charge_current(regmap, &val->intval); + break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = max77693_charger_model; break; diff --git a/include/linux/mfd/max77693-private.h b/include/linux/mfd/max77693-private.h index 54444ff2a5de..20c5e02ed9da 100644 --- a/include/linux/mfd/max77693-private.h +++ b/include/linux/mfd/max77693-private.h @@ -217,6 +217,10 @@ enum max77693_charger_battery_state { #define CHG_CNFG_01_CHGRSTRT_MASK (0x3 << CHG_CNFG_01_CHGRSTRT_SHIFT) #define CHG_CNFG_01_PQEN_MAKS BIT(CHG_CNFG_01_PQEN_SHIFT) +/* MAX77693_CHG_REG_CHG_CNFG_02 register */ +#define CHG_CNFG_02_CC_SHIFT 0 +#define CHG_CNFG_02_CC_MASK 0x3F + /* MAX77693_CHG_REG_CHG_CNFG_03 register */ #define CHG_CNFG_03_TOITH_SHIFT 0 #define CHG_CNFG_03_TOTIME_SHIFT 3 @@ -244,6 +248,7 @@ enum max77693_charger_battery_state { #define CHG_CNFG_12_VCHGINREG_MASK (0x3 << CHG_CNFG_12_VCHGINREG_SHIFT) /* MAX77693 CHG_CNFG_09 Register */ +#define CHG_CNFG_09_CHGIN_ILIM_SHIFT 0 #define CHG_CNFG_09_CHGIN_ILIM_MASK 0x7F /* MAX77693 CHG_CTRL Register */ From 50f74b785059453b4f10fe53241c2f612ebf9028 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Wed, 10 Jul 2024 11:20:23 +0800 Subject: [PATCH 222/573] power: supply: cpcap-charger: Convert comma to semicolon Replace a comma between expression statements by a semicolon. Signed-off-by: Chen Ni Link: https://lore.kernel.org/r/20240710032023.2003742-1-nichen@iscas.ac.cn Signed-off-by: Sebastian Reichel --- drivers/power/supply/cpcap-charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index cebca34ff872..91e7292d86bb 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -904,7 +904,7 @@ static int cpcap_charger_probe(struct platform_device *pdev) psy_cfg.of_node = pdev->dev.of_node; psy_cfg.drv_data = ddata; psy_cfg.supplied_to = cpcap_charger_supplied_to; - psy_cfg.num_supplicants = ARRAY_SIZE(cpcap_charger_supplied_to), + psy_cfg.num_supplicants = ARRAY_SIZE(cpcap_charger_supplied_to); ddata->usb = devm_power_supply_register(ddata->dev, &cpcap_charger_usb_desc, From 292fe42c34a9e9eea07f58a691813e077e9ca86f Mon Sep 17 00:00:00 2001 From: Asmaa Mnebhi Date: Tue, 11 Jun 2024 09:43:27 -0400 Subject: [PATCH 223/573] power: reset: pwr-mlxbf: support graceful shutdown The OCP board used a BlueField's GPIO pin for entering low power mode. That board was not commercialized and has been dropped from production so all its code is unused. The new hardware requirement is to trigger a graceful shutdown when that GPIO pin is toggled. So replace the unused low power mode with a graceful shutdown. Signed-off-by: Asmaa Mnebhi Reviewed-by: David Thompson Link: https://lore.kernel.org/r/20240611134327.30975-1-asmaa@nvidia.com Signed-off-by: Sebastian Reichel --- drivers/power/reset/pwr-mlxbf.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/drivers/power/reset/pwr-mlxbf.c b/drivers/power/reset/pwr-mlxbf.c index 1775b318d0ef..4f1cd1c0018c 100644 --- a/drivers/power/reset/pwr-mlxbf.c +++ b/drivers/power/reset/pwr-mlxbf.c @@ -18,7 +18,6 @@ struct pwr_mlxbf { struct work_struct reboot_work; - struct work_struct shutdown_work; const char *hid; }; @@ -27,22 +26,17 @@ static void pwr_mlxbf_reboot_work(struct work_struct *work) acpi_bus_generate_netlink_event("button/reboot.*", "Reboot Button", 0x80, 1); } -static void pwr_mlxbf_shutdown_work(struct work_struct *work) -{ - acpi_bus_generate_netlink_event("button/power.*", "Power Button", 0x80, 1); -} - static irqreturn_t pwr_mlxbf_irq(int irq, void *ptr) { const char *rst_pwr_hid = "MLNXBF24"; - const char *low_pwr_hid = "MLNXBF29"; + const char *shutdown_hid = "MLNXBF29"; struct pwr_mlxbf *priv = ptr; if (!strncmp(priv->hid, rst_pwr_hid, 8)) schedule_work(&priv->reboot_work); - if (!strncmp(priv->hid, low_pwr_hid, 8)) - schedule_work(&priv->shutdown_work); + if (!strncmp(priv->hid, shutdown_hid, 8)) + orderly_poweroff(true); return IRQ_HANDLED; } @@ -70,10 +64,6 @@ static int pwr_mlxbf_probe(struct platform_device *pdev) if (irq < 0) return dev_err_probe(dev, irq, "Error getting %s irq.\n", priv->hid); - err = devm_work_autocancel(dev, &priv->shutdown_work, pwr_mlxbf_shutdown_work); - if (err) - return err; - err = devm_work_autocancel(dev, &priv->reboot_work, pwr_mlxbf_reboot_work); if (err) return err; From 0174d12f9b7ebc83f1f2b6c25f05304104de5a91 Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Mon, 10 Jun 2024 09:28:32 -0500 Subject: [PATCH 224/573] power: reset: brcmstb: Use normal driver register function The platform_driver_probe() helper is useful when the probe function is in the _init section, that is not the case here. Use the normal platform_driver_register() function. Signed-off-by: Andrew Davis Reviewed-by: Dhruva Gole Acked-by: Florian Fainelli Link: https://lore.kernel.org/r/20240610142836.168603-1-afd@ti.com Signed-off-by: Sebastian Reichel --- drivers/power/reset/brcmstb-reboot.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/reset/brcmstb-reboot.c b/drivers/power/reset/brcmstb-reboot.c index 0f2944dc9355..797f0079bb59 100644 --- a/drivers/power/reset/brcmstb-reboot.c +++ b/drivers/power/reset/brcmstb-reboot.c @@ -140,7 +140,6 @@ static struct platform_driver brcmstb_reboot_driver = { static int __init brcmstb_reboot_init(void) { - return platform_driver_probe(&brcmstb_reboot_driver, - brcmstb_reboot_probe); + return platform_driver_register(&brcmstb_reboot_driver); } subsys_initcall(brcmstb_reboot_init); From cf37f16a60f332fad21fe0a45893ac4c6a825d9d Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Mon, 10 Jun 2024 09:28:33 -0500 Subject: [PATCH 225/573] power: reset: brcmstb: Use device_get_match_data() for matching Use device_get_match_data() for finding the matching node and fetching the match data all in one. Signed-off-by: Andrew Davis Acked-by: Florian Fainelli Link: https://lore.kernel.org/r/20240610142836.168603-2-afd@ti.com Signed-off-by: Sebastian Reichel --- drivers/power/reset/brcmstb-reboot.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/power/reset/brcmstb-reboot.c b/drivers/power/reset/brcmstb-reboot.c index 797f0079bb59..db5b7120eadd 100644 --- a/drivers/power/reset/brcmstb-reboot.c +++ b/drivers/power/reset/brcmstb-reboot.c @@ -83,24 +83,16 @@ static const struct reset_reg_mask reset_bits_65nm = { .sw_mstr_rst_mask = BIT(31), }; -static const struct of_device_id of_match[] = { - { .compatible = "brcm,brcmstb-reboot", .data = &reset_bits_40nm }, - { .compatible = "brcm,bcm7038-reboot", .data = &reset_bits_65nm }, - {}, -}; - static int brcmstb_reboot_probe(struct platform_device *pdev) { int rc; struct device_node *np = pdev->dev.of_node; - const struct of_device_id *of_id; - of_id = of_match_node(of_match, np); - if (!of_id) { - pr_err("failed to look up compatible string\n"); + reset_masks = device_get_match_data(&pdev->dev); + if (!reset_masks) { + pr_err("failed to get match data\n"); return -EINVAL; } - reset_masks = of_id->data; regmap = syscon_regmap_lookup_by_phandle(np, "syscon"); if (IS_ERR(regmap)) { @@ -130,6 +122,12 @@ static int brcmstb_reboot_probe(struct platform_device *pdev) return rc; } +static const struct of_device_id of_match[] = { + { .compatible = "brcm,brcmstb-reboot", .data = &reset_bits_40nm }, + { .compatible = "brcm,bcm7038-reboot", .data = &reset_bits_65nm }, + {}, +}; + static struct platform_driver brcmstb_reboot_driver = { .probe = brcmstb_reboot_probe, .driver = { From a4ceaab660cabdc9ac2d2514a809174443209b3b Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Mon, 10 Jun 2024 09:28:34 -0500 Subject: [PATCH 226/573] power: reset: brcmstb: Use syscon_regmap_lookup_by_phandle_args() helper Simplify probe by fetching the regmap and its arguments in one call. Signed-off-by: Andrew Davis Reviewed-by: Dhruva Gole Acked-by: Florian Fainelli Link: https://lore.kernel.org/r/20240610142836.168603-3-afd@ti.com Signed-off-by: Sebastian Reichel --- drivers/power/reset/brcmstb-reboot.c | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/drivers/power/reset/brcmstb-reboot.c b/drivers/power/reset/brcmstb-reboot.c index db5b7120eadd..fd583bd30956 100644 --- a/drivers/power/reset/brcmstb-reboot.c +++ b/drivers/power/reset/brcmstb-reboot.c @@ -18,9 +18,6 @@ #include #include -#define RESET_SOURCE_ENABLE_REG 1 -#define SW_MASTER_RESET_REG 2 - static struct regmap *regmap; static u32 rst_src_en; static u32 sw_mstr_rst; @@ -87,6 +84,7 @@ static int brcmstb_reboot_probe(struct platform_device *pdev) { int rc; struct device_node *np = pdev->dev.of_node; + unsigned int args[2]; reset_masks = device_get_match_data(&pdev->dev); if (!reset_masks) { @@ -94,25 +92,13 @@ static int brcmstb_reboot_probe(struct platform_device *pdev) return -EINVAL; } - regmap = syscon_regmap_lookup_by_phandle(np, "syscon"); + regmap = syscon_regmap_lookup_by_phandle_args(np, "syscon", ARRAY_SIZE(args), args); if (IS_ERR(regmap)) { pr_err("failed to get syscon phandle\n"); return -EINVAL; } - - rc = of_property_read_u32_index(np, "syscon", RESET_SOURCE_ENABLE_REG, - &rst_src_en); - if (rc) { - pr_err("can't get rst_src_en offset (%d)\n", rc); - return -EINVAL; - } - - rc = of_property_read_u32_index(np, "syscon", SW_MASTER_RESET_REG, - &sw_mstr_rst); - if (rc) { - pr_err("can't get sw_mstr_rst offset (%d)\n", rc); - return -EINVAL; - } + rst_src_en = args[0]; + sw_mstr_rst = args[1]; rc = register_restart_handler(&brcmstb_restart_nb); if (rc) From ad87aee5cba821066be99c76efd818368ff5bb4a Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Mon, 10 Jun 2024 09:28:35 -0500 Subject: [PATCH 227/573] power: reset: brcmstb: Use devm_register_sys_off_handler() Function register_restart_handler() is deprecated. Using this new API removes our need to keep and manage a struct notifier_block. Signed-off-by: Andrew Davis Reviewed-by: Dhruva Gole Acked-by: Florian Fainelli Link: https://lore.kernel.org/r/20240610142836.168603-4-afd@ti.com Signed-off-by: Sebastian Reichel --- drivers/power/reset/brcmstb-reboot.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/power/reset/brcmstb-reboot.c b/drivers/power/reset/brcmstb-reboot.c index fd583bd30956..0524ee53d6ba 100644 --- a/drivers/power/reset/brcmstb-reboot.c +++ b/drivers/power/reset/brcmstb-reboot.c @@ -29,8 +29,7 @@ struct reset_reg_mask { static const struct reset_reg_mask *reset_masks; -static int brcmstb_restart_handler(struct notifier_block *this, - unsigned long mode, void *cmd) +static int brcmstb_restart_handler(struct sys_off_data *data) { int rc; u32 tmp; @@ -65,11 +64,6 @@ static int brcmstb_restart_handler(struct notifier_block *this, return NOTIFY_DONE; } -static struct notifier_block brcmstb_restart_nb = { - .notifier_call = brcmstb_restart_handler, - .priority = 128, -}; - static const struct reset_reg_mask reset_bits_40nm = { .rst_src_en_mask = BIT(0), .sw_mstr_rst_mask = BIT(0), @@ -100,7 +94,8 @@ static int brcmstb_reboot_probe(struct platform_device *pdev) rst_src_en = args[0]; sw_mstr_rst = args[1]; - rc = register_restart_handler(&brcmstb_restart_nb); + rc = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, + 128, brcmstb_restart_handler, NULL); if (rc) dev_err(&pdev->dev, "cannot register restart handler (err=%d)\n", rc); From cf8c39b00e982fa506b16f9d76657838c09150cb Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Mon, 10 Jun 2024 09:28:36 -0500 Subject: [PATCH 228/573] power: reset: brcmstb: Do not go into infinite loop if reset fails There may be other backup reset methods available, do not halt here so that other reset methods can be tried. Signed-off-by: Andrew Davis Reviewed-by: Dhruva Gole Acked-by: Florian Fainelli Link: https://lore.kernel.org/r/20240610142836.168603-5-afd@ti.com Signed-off-by: Sebastian Reichel --- drivers/power/reset/brcmstb-reboot.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/power/reset/brcmstb-reboot.c b/drivers/power/reset/brcmstb-reboot.c index 0524ee53d6ba..b9c093f6064c 100644 --- a/drivers/power/reset/brcmstb-reboot.c +++ b/drivers/power/reset/brcmstb-reboot.c @@ -58,9 +58,6 @@ static int brcmstb_restart_handler(struct sys_off_data *data) return NOTIFY_DONE; } - while (1) - ; - return NOTIFY_DONE; } From 7eb42da6ab4a963677e3f692c4debdcc781cf4c9 Mon Sep 17 00:00:00 2001 From: Liming Sun Date: Tue, 27 Aug 2024 12:40:16 -0400 Subject: [PATCH 229/573] mmc: sdhci-of-dwcmshc: Add hw_reset() support for BlueField-3 SoC The eMMC RST_N register is implemented as secure register on the BlueField-3 SoC and controlled by TF-A. This commit adds the hw_reset() support which sends an SMC call to TF-A for the eMMC HW reset. Reviewed-by: David Thompson Signed-off-by: Liming Sun Link: https://lore.kernel.org/r/20240827164016.237617-1-limings@nvidia.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-of-dwcmshc.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index ba8960d8b2d4..8999b97263af 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -201,6 +202,9 @@ SDHCI_TRNS_BLK_CNT_EN | \ SDHCI_TRNS_DMA) +/* SMC call for BlueField-3 eMMC RST_N */ +#define BLUEFIELD_SMC_SET_EMMC_RST_N 0x82000007 + enum dwcmshc_rk_type { DWCMSHC_RK3568, DWCMSHC_RK3588, @@ -1111,6 +1115,29 @@ static const struct sdhci_ops sdhci_dwcmshc_ops = { .irq = dwcmshc_cqe_irq_handler, }; +#ifdef CONFIG_ACPI +static void dwcmshc_bf3_hw_reset(struct sdhci_host *host) +{ + struct arm_smccc_res res = { 0 }; + + arm_smccc_smc(BLUEFIELD_SMC_SET_EMMC_RST_N, 0, 0, 0, 0, 0, 0, 0, &res); + + if (res.a0) + pr_err("%s: RST_N failed.\n", mmc_hostname(host->mmc)); +} + +static const struct sdhci_ops sdhci_dwcmshc_bf3_ops = { + .set_clock = sdhci_set_clock, + .set_bus_width = sdhci_set_bus_width, + .set_uhs_signaling = dwcmshc_set_uhs_signaling, + .get_max_clock = dwcmshc_get_max_clock, + .reset = sdhci_reset, + .adma_write_desc = dwcmshc_adma_write_desc, + .irq = dwcmshc_cqe_irq_handler, + .hw_reset = dwcmshc_bf3_hw_reset, +}; +#endif + static const struct sdhci_ops sdhci_dwcmshc_rk35xx_ops = { .set_clock = dwcmshc_rk3568_set_clock, .set_bus_width = sdhci_set_bus_width, @@ -1163,7 +1190,7 @@ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_pdata = { #ifdef CONFIG_ACPI static const struct dwcmshc_pltfm_data sdhci_dwcmshc_bf3_pdata = { .pdata = { - .ops = &sdhci_dwcmshc_ops, + .ops = &sdhci_dwcmshc_bf3_ops, .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | SDHCI_QUIRK2_ACMD23_BROKEN, From 98845e7753902937da24b9053537b7936c916abd Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 28 Aug 2024 11:05:35 -0700 Subject: [PATCH 230/573] hwmon: (oxp-sensors) Add missing breaks to fix -Wimplicit-fallthrough with clang clang warns (or errors due to CONFIG_WERROR): drivers/hwmon/oxp-sensors.c:481:3: error: unannotated fall-through between switch labels [-Werror,-Wimplicit-fallthrough] drivers/hwmon/oxp-sensors.c:553:3: error: unannotated fall-through between switch labels [-Werror,-Wimplicit-fallthrough] drivers/hwmon/oxp-sensors.c:556:2: error: unannotated fall-through between switch labels [-Werror,-Wimplicit-fallthrough] drivers/hwmon/oxp-sensors.c:607:3: error: unannotated fall-through between switch labels [-Werror,-Wimplicit-fallthrough] Clang is a little more pedantic than GCC, which does not warn when falling through to a case that is just break or return. Clang's version is more in line with the kernel's own stance in deprecated.rst, which states that all switch/case blocks must end in either break, fallthrough, continue, goto, or return. Add the missing breaks to silence the warnings. Fixes: b82b38a49926 ("hwmon: (oxp-sensors) Add support for multiple new devices.") Signed-off-by: Nathan Chancellor Message-ID: <20240828-hwmon-oxp-sensors-fix-clang-implicit-fallthrough-v1-1-dc48496ac67a@kernel.org> Signed-off-by: Guenter Roeck --- drivers/hwmon/oxp-sensors.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/hwmon/oxp-sensors.c b/drivers/hwmon/oxp-sensors.c index b6d06370469d..83730d931824 100644 --- a/drivers/hwmon/oxp-sensors.c +++ b/drivers/hwmon/oxp-sensors.c @@ -478,6 +478,7 @@ static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type, default: break; } + break; default: break; } @@ -550,9 +551,11 @@ static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type, default: break; } + break; default: break; } + break; default: break; } @@ -604,6 +607,7 @@ static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type, default: break; } + break; default: break; } From 9dad0127ad732f756d056ea152e0b084f321c765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 28 Aug 2024 20:04:29 +0200 Subject: [PATCH 231/573] power: supply: core: constify psy_tzd_ops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This struct is never modified, so mark it const. Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20240828-power-supply-const-psy_tzd_ops-v1-1-dc27176fda5b@weissschuh.net Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 2b845ac51157..3614d263ddad 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1295,7 +1295,7 @@ static int power_supply_read_temp(struct thermal_zone_device *tzd, return ret; } -static struct thermal_zone_device_ops psy_tzd_ops = { +static const struct thermal_zone_device_ops psy_tzd_ops = { .get_temp = power_supply_read_temp, }; From 2e1a57d5b0adbb5bd1d85245ec29b6d3cc7edc69 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 21 Aug 2024 16:54:52 -0500 Subject: [PATCH 232/573] mfd: axp20x: Add ADC, BAT, and USB cells for AXP717 Add support for the AXP717 PMIC to utilize the ADC (for reading voltage, current, and temperature information from the PMIC) as well as the USB charger and battery. Signed-off-by: Chris Morgan Link: https://lore.kernel.org/r/20240821215456.962564-12-macroalpha82@gmail.com Signed-off-by: Lee Jones --- drivers/mfd/axp20x.c | 25 ++++++++++++++++++++++++- include/linux/mfd/axp20x.h | 26 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c index dacd3c96c9f5..4051551757f2 100644 --- a/drivers/mfd/axp20x.c +++ b/drivers/mfd/axp20x.c @@ -209,13 +209,23 @@ static const struct regmap_access_table axp313a_volatile_table = { }; static const struct regmap_range axp717_writeable_ranges[] = { + regmap_reg_range(AXP717_PMU_FAULT, AXP717_MODULE_EN_CONTROL_1), + regmap_reg_range(AXP717_MIN_SYS_V_CONTROL, AXP717_BOOST_CONTROL), + regmap_reg_range(AXP717_VSYS_V_POWEROFF, AXP717_VSYS_V_POWEROFF), regmap_reg_range(AXP717_IRQ0_EN, AXP717_IRQ4_EN), regmap_reg_range(AXP717_IRQ0_STATE, AXP717_IRQ4_STATE), + regmap_reg_range(AXP717_ICC_CHG_SET, AXP717_CV_CHG_SET), regmap_reg_range(AXP717_DCDC_OUTPUT_CONTROL, AXP717_CPUSLDO_CONTROL), + regmap_reg_range(AXP717_ADC_CH_EN_CONTROL, AXP717_ADC_CH_EN_CONTROL), + regmap_reg_range(AXP717_ADC_DATA_SEL, AXP717_ADC_DATA_SEL), }; static const struct regmap_range axp717_volatile_ranges[] = { + regmap_reg_range(AXP717_ON_INDICATE, AXP717_PMU_FAULT), regmap_reg_range(AXP717_IRQ0_STATE, AXP717_IRQ4_STATE), + regmap_reg_range(AXP717_BATT_PERCENT_DATA, AXP717_BATT_PERCENT_DATA), + regmap_reg_range(AXP717_BATT_V_H, AXP717_BATT_CHRG_I_L), + regmap_reg_range(AXP717_ADC_DATA_H, AXP717_ADC_DATA_L), }; static const struct regmap_access_table axp717_writeable_table = { @@ -308,6 +318,12 @@ static const struct resource axp22x_usb_power_supply_resources[] = { DEFINE_RES_IRQ_NAMED(AXP22X_IRQ_VBUS_REMOVAL, "VBUS_REMOVAL"), }; +static const struct resource axp717_usb_power_supply_resources[] = { + DEFINE_RES_IRQ_NAMED(AXP717_IRQ_VBUS_OVER_V, "VBUS_OVER_V"), + DEFINE_RES_IRQ_NAMED(AXP717_IRQ_VBUS_PLUGIN, "VBUS_PLUGIN"), + DEFINE_RES_IRQ_NAMED(AXP717_IRQ_VBUS_REMOVAL, "VBUS_REMOVAL"), +}; + /* AXP803 and AXP813/AXP818 share the same interrupts */ static const struct resource axp803_usb_power_supply_resources[] = { DEFINE_RES_IRQ_NAMED(AXP803_IRQ_VBUS_PLUGIN, "VBUS_PLUGIN"), @@ -422,7 +438,7 @@ static const struct regmap_config axp717_regmap_config = { .val_bits = 8, .wr_table = &axp717_writeable_table, .volatile_table = &axp717_volatile_table, - .max_register = AXP717_CPUSLDO_CONTROL, + .max_register = AXP717_ADC_DATA_L, .cache_type = REGCACHE_MAPLE, }; @@ -1024,6 +1040,13 @@ static struct mfd_cell axp313a_cells[] = { static struct mfd_cell axp717_cells[] = { MFD_CELL_NAME("axp20x-regulator"), MFD_CELL_RES("axp20x-pek", axp717_pek_resources), + MFD_CELL_OF("axp717-adc", + NULL, NULL, 0, 0, "x-powers,axp717-adc"), + MFD_CELL_OF("axp20x-usb-power-supply", + axp717_usb_power_supply_resources, NULL, 0, 0, + "x-powers,axp717-usb-power-supply"), + MFD_CELL_OF("axp20x-battery-power-supply", + NULL, NULL, 0, 0, "x-powers,axp717-battery-power-supply"), }; static const struct resource axp288_adc_resources[] = { diff --git a/include/linux/mfd/axp20x.h b/include/linux/mfd/axp20x.h index 8c0a33a2e9ce..9b7ef473adcb 100644 --- a/include/linux/mfd/axp20x.h +++ b/include/linux/mfd/axp20x.h @@ -115,6 +115,16 @@ enum axp20x_variants { #define AXP313A_IRQ_STATE 0x21 #define AXP717_ON_INDICATE 0x00 +#define AXP717_PMU_STATUS_2 0x01 +#define AXP717_BC_DETECT 0x05 +#define AXP717_PMU_FAULT 0x08 +#define AXP717_MODULE_EN_CONTROL_1 0x0b +#define AXP717_MIN_SYS_V_CONTROL 0x15 +#define AXP717_INPUT_VOL_LIMIT_CTRL 0x16 +#define AXP717_INPUT_CUR_LIMIT_CTRL 0x17 +#define AXP717_MODULE_EN_CONTROL_2 0x19 +#define AXP717_BOOST_CONTROL 0x1e +#define AXP717_VSYS_V_POWEROFF 0x24 #define AXP717_IRQ0_EN 0x40 #define AXP717_IRQ1_EN 0x41 #define AXP717_IRQ2_EN 0x42 @@ -125,6 +135,9 @@ enum axp20x_variants { #define AXP717_IRQ2_STATE 0x4a #define AXP717_IRQ3_STATE 0x4b #define AXP717_IRQ4_STATE 0x4c +#define AXP717_ICC_CHG_SET 0x62 +#define AXP717_ITERM_CHG_SET 0x63 +#define AXP717_CV_CHG_SET 0x64 #define AXP717_DCDC_OUTPUT_CONTROL 0x80 #define AXP717_DCDC1_CONTROL 0x83 #define AXP717_DCDC2_CONTROL 0x84 @@ -145,6 +158,19 @@ enum axp20x_variants { #define AXP717_CLDO3_CONTROL 0x9d #define AXP717_CLDO4_CONTROL 0x9e #define AXP717_CPUSLDO_CONTROL 0x9f +#define AXP717_BATT_PERCENT_DATA 0xa4 +#define AXP717_ADC_CH_EN_CONTROL 0xc0 +#define AXP717_BATT_V_H 0xc4 +#define AXP717_BATT_V_L 0xc5 +#define AXP717_VBUS_V_H 0xc6 +#define AXP717_VBUS_V_L 0xc7 +#define AXP717_VSYS_V_H 0xc8 +#define AXP717_VSYS_V_L 0xc9 +#define AXP717_BATT_CHRG_I_H 0xca +#define AXP717_BATT_CHRG_I_L 0xcb +#define AXP717_ADC_DATA_SEL 0xcd +#define AXP717_ADC_DATA_H 0xce +#define AXP717_ADC_DATA_L 0xcf #define AXP806_STARTUP_SRC 0x00 #define AXP806_CHIP_ID 0x03 From 2f0d9872beca2def687de76a7c052db36d6ba78d Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 29 Aug 2024 11:06:41 -0400 Subject: [PATCH 233/573] dt-bindings: hwmon: Convert ltc2978.txt to yaml Convert binding doc ltc2978.txt to yaml format. Additional change: - add i2c node. Fix below warning: arch/arm64/boot/dts/freescale/fsl-lx2160a-clearfog-cx.dtb: /soc/i2c@2000000/i2c-mux@77/i2c@2/regulator@5c: failed to match any schema with compatible: ['lltc,ltc3882'] Signed-off-by: Frank Li Reviewed-by: Rob Herring (Arm) Message-ID: <20240829150641.1307906-1-Frank.Li@nxp.com> Signed-off-by: Guenter Roeck --- .../bindings/hwmon/lltc,ltc2978.yaml | 94 +++++++++++++++++++ .../devicetree/bindings/hwmon/ltc2978.txt | 62 ------------ 2 files changed, 94 insertions(+), 62 deletions(-) create mode 100644 Documentation/devicetree/bindings/hwmon/lltc,ltc2978.yaml delete mode 100644 Documentation/devicetree/bindings/hwmon/ltc2978.txt diff --git a/Documentation/devicetree/bindings/hwmon/lltc,ltc2978.yaml b/Documentation/devicetree/bindings/hwmon/lltc,ltc2978.yaml new file mode 100644 index 000000000000..1f98da32f3fe --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/lltc,ltc2978.yaml @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/lltc,ltc2978.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Octal Digital Power-supply monitor/supervisor/sequencer/margin controller. + +maintainers: + - Frank Li + +properties: + compatible: + enum: + - lltc,ltc2972 + - lltc,ltc2974 + - lltc,ltc2975 + - lltc,ltc2977 + - lltc,ltc2978 + - lltc,ltc2979 + - lltc,ltc2980 + - lltc,ltc3880 + - lltc,ltc3882 + - lltc,ltc3883 + - lltc,ltc3884 + - lltc,ltc3886 + - lltc,ltc3887 + - lltc,ltc3889 + - lltc,ltc7880 + - lltc,ltm2987 + - lltc,ltm4664 + - lltc,ltm4675 + - lltc,ltm4676 + - lltc,ltm4677 + - lltc,ltm4678 + - lltc,ltm4680 + - lltc,ltm4686 + - lltc,ltm4700 + + reg: + maxItems: 1 + + regulators: + type: object + description: | + list of regulators provided by this controller. + Valid names of regulators depend on number of supplies supported per device: + * ltc2972 vout0 - vout1 + * ltc2974, ltc2975 : vout0 - vout3 + * ltc2977, ltc2979, ltc2980, ltm2987 : vout0 - vout7 + * ltc2978 : vout0 - vout7 + * ltc3880, ltc3882, ltc3884, ltc3886, ltc3887, ltc3889 : vout0 - vout1 + * ltc7880 : vout0 - vout1 + * ltc3883 : vout0 + * ltm4664 : vout0 - vout1 + * ltm4675, ltm4676, ltm4677, ltm4678 : vout0 - vout1 + * ltm4680, ltm4686 : vout0 - vout1 + * ltm4700 : vout0 - vout1 + + patternProperties: + "^vout[0-7]$": + $ref: /schemas/regulator/regulator.yaml# + type: object + unevaluatedProperties: false + + additionalProperties: false + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + regulator@5e { + compatible = "lltc,ltc2978"; + reg = <0x5e>; + + regulators { + vout0 { + regulator-name = "FPGA-2.5V"; + }; + vout2 { + regulator-name = "FPGA-1.5V"; + }; + }; + }; + }; + diff --git a/Documentation/devicetree/bindings/hwmon/ltc2978.txt b/Documentation/devicetree/bindings/hwmon/ltc2978.txt deleted file mode 100644 index 4e7f6215a453..000000000000 --- a/Documentation/devicetree/bindings/hwmon/ltc2978.txt +++ /dev/null @@ -1,62 +0,0 @@ -ltc2978 - -Required properties: -- compatible: should contain one of: - * "lltc,ltc2972" - * "lltc,ltc2974" - * "lltc,ltc2975" - * "lltc,ltc2977" - * "lltc,ltc2978" - * "lltc,ltc2979" - * "lltc,ltc2980" - * "lltc,ltc3880" - * "lltc,ltc3882" - * "lltc,ltc3883" - * "lltc,ltc3884" - * "lltc,ltc3886" - * "lltc,ltc3887" - * "lltc,ltc3889" - * "lltc,ltc7880" - * "lltc,ltm2987" - * "lltc,ltm4664" - * "lltc,ltm4675" - * "lltc,ltm4676" - * "lltc,ltm4677" - * "lltc,ltm4678" - * "lltc,ltm4680" - * "lltc,ltm4686" - * "lltc,ltm4700" -- reg: I2C slave address - -Optional properties: -- regulators: A node that houses a sub-node for each regulator controlled by - the device. Each sub-node is identified using the node's name, with valid - values listed below. The content of each sub-node is defined by the - standard binding for regulators; see regulator.txt. - -Valid names of regulators depend on number of supplies supported per device: - * ltc2972 vout0 - vout1 - * ltc2974, ltc2975 : vout0 - vout3 - * ltc2977, ltc2979, ltc2980, ltm2987 : vout0 - vout7 - * ltc2978 : vout0 - vout7 - * ltc3880, ltc3882, ltc3884, ltc3886, ltc3887, ltc3889 : vout0 - vout1 - * ltc7880 : vout0 - vout1 - * ltc3883 : vout0 - * ltm4664 : vout0 - vout1 - * ltm4675, ltm4676, ltm4677, ltm4678 : vout0 - vout1 - * ltm4680, ltm4686 : vout0 - vout1 - * ltm4700 : vout0 - vout1 - -Example: -ltc2978@5e { - compatible = "lltc,ltc2978"; - reg = <0x5e>; - regulators { - vout0 { - regulator-name = "FPGA-2.5V"; - }; - vout2 { - regulator-name = "FPGA-1.5V"; - }; - }; -}; From 6c17c7d5936e6af6a5bda9f9de98a5e2ee6e8a6f Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Wed, 7 Aug 2024 15:19:20 -0300 Subject: [PATCH 234/573] iommu: Allow ATS to work on VFs when the PF uses IDENTITY PCI ATS has a global Smallest Translation Unit field that is located in the PF but shared by all of the VFs. The expectation is that the STU will be set to the root port's global STU capability which is driven by the IO page table configuration of the iommu HW. Today it becomes set when the iommu driver first enables ATS. Thus, to enable ATS on the VF, the PF must have already had the correct STU programmed, even if ATS is off on the PF. Unfortunately the PF only programs the STU when the PF enables ATS. The iommu drivers tend to leave ATS disabled when IDENTITY translation is being used. Thus we can get into a state where the PF is setup to use IDENTITY with the DMA API while the VF would like to use VFIO with a PAGING domain and have ATS turned on. This fails because the PF never loaded a PAGING domain and so it never setup the STU, and the VF can't do it. The simplest solution is to have the iommu driver set the ATS STU when it probes the device. This way the ATS STU is loaded immediately at boot time to all PFs and there is no issue when a VF comes to use it. Add a new call pci_prepare_ats() which should be called by iommu drivers in their probe_device() op for every PCI device if the iommu driver supports ATS. This will setup the STU based on whatever page size capability the iommu HW has. Signed-off-by: Jason Gunthorpe Acked-by: Bjorn Helgaas Reviewed-by: Kevin Tian Reviewed-by: Lu Baolu Link: https://lore.kernel.org/r/0-v1-0fb4d2ab6770+7e706-ats_vf_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/iommu.c | 3 ++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 6 ++++ drivers/iommu/intel/iommu.c | 1 + drivers/pci/ats.c | 33 +++++++++++++++++++++ include/linux/pci-ats.h | 3 ++ 5 files changed, 46 insertions(+) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index b19e8c0f48fa..98054497d343 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -2203,6 +2203,9 @@ static struct iommu_device *amd_iommu_probe_device(struct device *dev) iommu_completion_wait(iommu); + if (dev_is_pci(dev)) + pci_prepare_ats(to_pci_dev(dev), PAGE_SHIFT); + return iommu_dev; } diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index a31460f9f3d4..9bc50bded5af 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -3295,6 +3295,12 @@ static struct iommu_device *arm_smmu_probe_device(struct device *dev) smmu->features & ARM_SMMU_FEAT_STALL_FORCE) master->stall_enabled = true; + if (dev_is_pci(dev)) { + unsigned int stu = __ffs(smmu->pgsize_bitmap); + + pci_prepare_ats(to_pci_dev(dev), stu); + } + return &smmu->iommu; err_free_master: diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index 9ff8b83c19a3..ad81db026ab2 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -4091,6 +4091,7 @@ static struct iommu_device *intel_iommu_probe_device(struct device *dev) dev_iommu_priv_set(dev, info); if (pdev && pci_ats_supported(pdev)) { + pci_prepare_ats(pdev, VTD_PAGE_SHIFT); ret = device_rbtree_insert(iommu, info); if (ret) goto free; diff --git a/drivers/pci/ats.c b/drivers/pci/ats.c index c570892b2090..87fa03540b8a 100644 --- a/drivers/pci/ats.c +++ b/drivers/pci/ats.c @@ -47,6 +47,39 @@ bool pci_ats_supported(struct pci_dev *dev) } EXPORT_SYMBOL_GPL(pci_ats_supported); +/** + * pci_prepare_ats - Setup the PS for ATS + * @dev: the PCI device + * @ps: the IOMMU page shift + * + * This must be done by the IOMMU driver on the PF before any VFs are created to + * ensure that the VF can have ATS enabled. + * + * Returns 0 on success, or negative on failure. + */ +int pci_prepare_ats(struct pci_dev *dev, int ps) +{ + u16 ctrl; + + if (!pci_ats_supported(dev)) + return -EINVAL; + + if (WARN_ON(dev->ats_enabled)) + return -EBUSY; + + if (ps < PCI_ATS_MIN_STU) + return -EINVAL; + + if (dev->is_virtfn) + return 0; + + dev->ats_stu = ps; + ctrl = PCI_ATS_CTRL_STU(dev->ats_stu - PCI_ATS_MIN_STU); + pci_write_config_word(dev, dev->ats_cap + PCI_ATS_CTRL, ctrl); + return 0; +} +EXPORT_SYMBOL_GPL(pci_prepare_ats); + /** * pci_enable_ats - enable the ATS capability * @dev: the PCI device diff --git a/include/linux/pci-ats.h b/include/linux/pci-ats.h index df54cd5b15db..0e8b74e63767 100644 --- a/include/linux/pci-ats.h +++ b/include/linux/pci-ats.h @@ -8,6 +8,7 @@ /* Address Translation Service */ bool pci_ats_supported(struct pci_dev *dev); int pci_enable_ats(struct pci_dev *dev, int ps); +int pci_prepare_ats(struct pci_dev *dev, int ps); void pci_disable_ats(struct pci_dev *dev); int pci_ats_queue_depth(struct pci_dev *dev); int pci_ats_page_aligned(struct pci_dev *dev); @@ -16,6 +17,8 @@ static inline bool pci_ats_supported(struct pci_dev *d) { return false; } static inline int pci_enable_ats(struct pci_dev *d, int ps) { return -ENODEV; } +static inline int pci_prepare_ats(struct pci_dev *dev, int ps) +{ return -ENODEV; } static inline void pci_disable_ats(struct pci_dev *d) { } static inline int pci_ats_queue_depth(struct pci_dev *d) { return -ENODEV; } From 84b2baf427968c1b2e3ae3b7afcb0118cdee0915 Mon Sep 17 00:00:00 2001 From: Ashish Mhetre Date: Tue, 6 Aug 2024 10:51:35 +0000 Subject: [PATCH 235/573] iommu/io-pgtable-arm: Optimise non-coherent unmap The current __arm_lpae_unmap() function calls dma_sync() on individual PTEs after clearing them. Overall unmap performance can be improved by around 25% for large buffer sizes by combining the syncs for adjacent leaf entries. Optimize the unmap time by clearing all the leaf entries and issuing a single dma_sync() for them. Below is detailed analysis of average unmap latency(in us) with and without this optimization obtained by running dma_map_benchmark for different buffer sizes. UnMap Latency(us) Size Without With % gain with optimiztion optimization optimization 4KB 3 3 0 8KB 4 3.8 5 16KB 6.1 5.4 11.48 32KB 10.2 8.5 16.67 64KB 18.5 14.9 19.46 128KB 35 27.5 21.43 256KB 67.5 52.2 22.67 512KB 127.9 97.2 24.00 1MB 248.6 187.4 24.62 2MB 65.5 65.5 0 4MB 119.2 119 0.17 Reviewed-by: Robin Murphy Signed-off-by: Ashish Mhetre Acked-by: Will Deacon Link: https://lore.kernel.org/r/20240806105135.218089-1-amhetre@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/io-pgtable-arm.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c index f5d9fd1f45bf..6fecf3d9fe67 100644 --- a/drivers/iommu/io-pgtable-arm.c +++ b/drivers/iommu/io-pgtable-arm.c @@ -274,13 +274,13 @@ static void __arm_lpae_sync_pte(arm_lpae_iopte *ptep, int num_entries, sizeof(*ptep) * num_entries, DMA_TO_DEVICE); } -static void __arm_lpae_clear_pte(arm_lpae_iopte *ptep, struct io_pgtable_cfg *cfg) +static void __arm_lpae_clear_pte(arm_lpae_iopte *ptep, struct io_pgtable_cfg *cfg, int num_entries) { + for (int i = 0; i < num_entries; i++) + ptep[i] = 0; - *ptep = 0; - - if (!cfg->coherent_walk) - __arm_lpae_sync_pte(ptep, 1, cfg); + if (!cfg->coherent_walk && num_entries) + __arm_lpae_sync_pte(ptep, num_entries, cfg); } static size_t __arm_lpae_unmap(struct arm_lpae_io_pgtable *data, @@ -654,26 +654,29 @@ static size_t __arm_lpae_unmap(struct arm_lpae_io_pgtable *data, max_entries = ARM_LPAE_PTES_PER_TABLE(data) - unmap_idx_start; num_entries = min_t(int, pgcount, max_entries); - while (i < num_entries) { - pte = READ_ONCE(*ptep); + /* Find and handle non-leaf entries */ + for (i = 0; i < num_entries; i++) { + pte = READ_ONCE(ptep[i]); if (WARN_ON(!pte)) break; - __arm_lpae_clear_pte(ptep, &iop->cfg); - if (!iopte_leaf(pte, lvl, iop->fmt)) { + __arm_lpae_clear_pte(&ptep[i], &iop->cfg, 1); + /* Also flush any partial walks */ io_pgtable_tlb_flush_walk(iop, iova + i * size, size, ARM_LPAE_GRANULE(data)); __arm_lpae_free_pgtable(data, lvl + 1, iopte_deref(pte, data)); - } else if (!iommu_iotlb_gather_queued(gather)) { - io_pgtable_tlb_add_page(iop, gather, iova + i * size, size); } - - ptep++; - i++; } + /* Clear the remaining entries */ + __arm_lpae_clear_pte(ptep, &iop->cfg, i); + + if (gather && !iommu_iotlb_gather_queued(gather)) + for (int j = 0; j < i; j++) + io_pgtable_tlb_add_page(iop, gather, iova + j * size, size); + return i * size; } else if (iopte_leaf(pte, lvl, iop->fmt)) { /* From 01b52f01c5a6bdc3b3e4229dccc84ed667e6867b Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Mon, 29 Jul 2024 18:02:11 +0200 Subject: [PATCH 236/573] selftests: vDSO: simplify getrandom thread local storage and structs Rather than using pthread_get/set_specific, just use gcc's __thread annotation, which is noticeably faster and makes the code more obvious. Also, just have one simplified struct called vgrnd, instead of trying to split things up semantically. Those divisions were useful when this code was split across several commit *messages*, but doesn't make as much sense within a single file. This should make the code more clear and provide a better example for implementers. Signed-off-by: Jason A. Donenfeld --- .../selftests/vDSO/vdso_test_getrandom.c | 67 ++++++++----------- 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/tools/testing/selftests/vDSO/vdso_test_getrandom.c b/tools/testing/selftests/vDSO/vdso_test_getrandom.c index 05122425a873..89c961175956 100644 --- a/tools/testing/selftests/vDSO/vdso_test_getrandom.c +++ b/tools/testing/selftests/vDSO/vdso_test_getrandom.c @@ -38,50 +38,43 @@ static struct { pthread_mutex_t lock; void **states; size_t len, cap; -} grnd_allocator = { - .lock = PTHREAD_MUTEX_INITIALIZER -}; - -static struct { ssize_t(*fn)(void *, size_t, unsigned long, void *, size_t); - pthread_key_t key; - pthread_once_t initialized; struct vgetrandom_opaque_params params; -} grnd_ctx = { - .initialized = PTHREAD_ONCE_INIT +} vgrnd = { + .lock = PTHREAD_MUTEX_INITIALIZER }; static void *vgetrandom_get_state(void) { void *state = NULL; - pthread_mutex_lock(&grnd_allocator.lock); - if (!grnd_allocator.len) { + pthread_mutex_lock(&vgrnd.lock); + if (!vgrnd.len) { size_t page_size = getpagesize(); size_t new_cap; size_t alloc_size, num = sysconf(_SC_NPROCESSORS_ONLN); /* Just a decent heuristic. */ void *new_block, *new_states; - alloc_size = (num * grnd_ctx.params.size_of_opaque_state + page_size - 1) & (~(page_size - 1)); - num = (page_size / grnd_ctx.params.size_of_opaque_state) * (alloc_size / page_size); - new_block = mmap(0, alloc_size, grnd_ctx.params.mmap_prot, grnd_ctx.params.mmap_flags, -1, 0); + alloc_size = (num * vgrnd.params.size_of_opaque_state + page_size - 1) & (~(page_size - 1)); + num = (page_size / vgrnd.params.size_of_opaque_state) * (alloc_size / page_size); + new_block = mmap(0, alloc_size, vgrnd.params.mmap_prot, vgrnd.params.mmap_flags, -1, 0); if (new_block == MAP_FAILED) goto out; - new_cap = grnd_allocator.cap + num; - new_states = reallocarray(grnd_allocator.states, new_cap, sizeof(*grnd_allocator.states)); + new_cap = vgrnd.cap + num; + new_states = reallocarray(vgrnd.states, new_cap, sizeof(*vgrnd.states)); if (!new_states) goto unmap; - grnd_allocator.cap = new_cap; - grnd_allocator.states = new_states; + vgrnd.cap = new_cap; + vgrnd.states = new_states; for (size_t i = 0; i < num; ++i) { - if (((uintptr_t)new_block & (page_size - 1)) + grnd_ctx.params.size_of_opaque_state > page_size) + if (((uintptr_t)new_block & (page_size - 1)) + vgrnd.params.size_of_opaque_state > page_size) new_block = (void *)(((uintptr_t)new_block + page_size - 1) & (~(page_size - 1))); - grnd_allocator.states[i] = new_block; - new_block += grnd_ctx.params.size_of_opaque_state; + vgrnd.states[i] = new_block; + new_block += vgrnd.params.size_of_opaque_state; } - grnd_allocator.len = num; + vgrnd.len = num; goto success; unmap: @@ -89,10 +82,10 @@ static void *vgetrandom_get_state(void) goto out; } success: - state = grnd_allocator.states[--grnd_allocator.len]; + state = vgrnd.states[--vgrnd.len]; out: - pthread_mutex_unlock(&grnd_allocator.lock); + pthread_mutex_unlock(&vgrnd.lock); return state; } @@ -100,27 +93,25 @@ static void vgetrandom_put_state(void *state) { if (!state) return; - pthread_mutex_lock(&grnd_allocator.lock); - grnd_allocator.states[grnd_allocator.len++] = state; - pthread_mutex_unlock(&grnd_allocator.lock); + pthread_mutex_lock(&vgrnd.lock); + vgrnd.states[vgrnd.len++] = state; + pthread_mutex_unlock(&vgrnd.lock); } static void vgetrandom_init(void) { - if (pthread_key_create(&grnd_ctx.key, vgetrandom_put_state) != 0) - return; unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR); if (!sysinfo_ehdr) { printf("AT_SYSINFO_EHDR is not present!\n"); exit(KSFT_SKIP); } vdso_init_from_sysinfo_ehdr(sysinfo_ehdr); - grnd_ctx.fn = (__typeof__(grnd_ctx.fn))vdso_sym("LINUX_2.6", "__vdso_getrandom"); - if (!grnd_ctx.fn) { + vgrnd.fn = (__typeof__(vgrnd.fn))vdso_sym("LINUX_2.6", "__vdso_getrandom"); + if (!vgrnd.fn) { printf("__vdso_getrandom is missing!\n"); exit(KSFT_FAIL); } - if (grnd_ctx.fn(NULL, 0, 0, &grnd_ctx.params, ~0UL) != 0) { + if (vgrnd.fn(NULL, 0, 0, &vgrnd.params, ~0UL) != 0) { printf("failed to fetch vgetrandom params!\n"); exit(KSFT_FAIL); } @@ -128,22 +119,16 @@ static void vgetrandom_init(void) static ssize_t vgetrandom(void *buf, size_t len, unsigned long flags) { - void *state; + static __thread void *state; - pthread_once(&grnd_ctx.initialized, vgetrandom_init); - state = pthread_getspecific(grnd_ctx.key); if (!state) { state = vgetrandom_get_state(); - if (pthread_setspecific(grnd_ctx.key, state) != 0) { - vgetrandom_put_state(state); - state = NULL; - } if (!state) { printf("vgetrandom_get_state failed!\n"); exit(KSFT_FAIL); } } - return grnd_ctx.fn(buf, len, flags, state, grnd_ctx.params.size_of_opaque_state); + return vgrnd.fn(buf, len, flags, state, vgrnd.params.size_of_opaque_state); } enum { TRIALS = 25000000, THREADS = 256 }; @@ -265,6 +250,8 @@ static void usage(const char *argv0) int main(int argc, char *argv[]) { + vgetrandom_init(); + if (argc == 1) { kselftest(); return 0; From 20a9af057cd7c4057c72c803dcf83c97eb5deb95 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Thu, 22 Aug 2024 09:13:21 +0200 Subject: [PATCH 237/573] selftests: vDSO: don't hard-code location of vDSO sources Architectures use different location for vDSO sources: arch/mips/vdso arch/sparc/vdso arch/arm64/kernel/vdso arch/riscv/kernel/vdso arch/csky/kernel/vdso arch/x86/um/vdso arch/x86/entry/vdso arch/powerpc/kernel/vdso arch/arm/vdso arch/loongarch/vdso Don't hard-code vdso sources location in selftest Makefile. Instead create a vdso/ symbolic link in tools/arch/$arch/ and update Makefile accordingly. Signed-off-by: Christophe Leroy Signed-off-by: Jason A. Donenfeld --- tools/arch/x86/vdso | 1 + tools/testing/selftests/vDSO/Makefile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 120000 tools/arch/x86/vdso diff --git a/tools/arch/x86/vdso b/tools/arch/x86/vdso new file mode 120000 index 000000000000..7eb962fd3454 --- /dev/null +++ b/tools/arch/x86/vdso @@ -0,0 +1 @@ +../../../arch/x86/entry/vdso/ \ No newline at end of file diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index 3de8e7e052ae..c9a819cacbf2 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -40,7 +40,7 @@ $(OUTPUT)/vdso_test_getrandom: parse_vdso.c $(OUTPUT)/vdso_test_getrandom: CFLAGS += -isystem $(top_srcdir)/tools/include \ -isystem $(top_srcdir)/include/uapi -$(OUTPUT)/vdso_test_chacha: $(top_srcdir)/arch/$(ARCH)/entry/vdso/vgetrandom-chacha.S +$(OUTPUT)/vdso_test_chacha: $(top_srcdir)/tools/arch/$(ARCH)/vdso/vgetrandom-chacha.S $(OUTPUT)/vdso_test_chacha: CFLAGS += -idirafter $(top_srcdir)/tools/include \ -isystem $(top_srcdir)/arch/$(ARCH)/include \ -isystem $(top_srcdir)/include \ From f8d92fc527ff0388b2e94c101afb0e5c7496199f Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Thu, 22 Aug 2024 09:13:23 +0200 Subject: [PATCH 238/573] selftests: vDSO: fix include order in build of test_vdso_chacha Building test_vdso_chacha currently leads to following issue: In file included from /home/chleroy/linux-powerpc/include/linux/limits.h:7, from /opt/powerpc64-e5500--glibc--stable-2024.02-1/powerpc64-buildroot-linux-gnu/sysroot/usr/include/bits/local_lim.h:38, from /opt/powerpc64-e5500--glibc--stable-2024.02-1/powerpc64-buildroot-linux-gnu/sysroot/usr/include/bits/posix1_lim.h:161, from /opt/powerpc64-e5500--glibc--stable-2024.02-1/powerpc64-buildroot-linux-gnu/sysroot/usr/include/limits.h:195, from /opt/powerpc64-e5500--glibc--stable-2024.02-1/lib/gcc/powerpc64-buildroot-linux-gnu/12.3.0/include-fixed/limits.h:203, from /opt/powerpc64-e5500--glibc--stable-2024.02-1/lib/gcc/powerpc64-buildroot-linux-gnu/12.3.0/include-fixed/syslimits.h:7, from /opt/powerpc64-e5500--glibc--stable-2024.02-1/lib/gcc/powerpc64-buildroot-linux-gnu/12.3.0/include-fixed/limits.h:34, from /tmp/sodium/usr/local/include/sodium/export.h:7, from /tmp/sodium/usr/local/include/sodium/crypto_stream_chacha20.h:14, from vdso_test_chacha.c:6: /opt/powerpc64-e5500--glibc--stable-2024.02-1/powerpc64-buildroot-linux-gnu/sysroot/usr/include/bits/xopen_lim.h:99:6: error: missing binary operator before token "(" 99 | # if INT_MAX == 32767 | ^~~~~~~ /opt/powerpc64-e5500--glibc--stable-2024.02-1/powerpc64-buildroot-linux-gnu/sysroot/usr/include/bits/xopen_lim.h:102:7: error: missing binary operator before token "(" 102 | # if INT_MAX == 2147483647 | ^~~~~~~ /opt/powerpc64-e5500--glibc--stable-2024.02-1/powerpc64-buildroot-linux-gnu/sysroot/usr/include/bits/xopen_lim.h:126:6: error: missing binary operator before token "(" 126 | # if LONG_MAX == 2147483647 | ^~~~~~~~ This is due to kernel include/linux/limits.h being included instead of libc's limits.h. This is because directory include/ is added through option -isystem so it goes prior to glibc's include directory. Replace -isystem by -idirafter. But this implies that now tools/include/linux/linkage.h is included instead of include/linux/linkage.h, so define a stub for SYM_FUNC_START() and SYM_FUNC_END(). Signed-off-by: Christophe Leroy Signed-off-by: Jason A. Donenfeld --- tools/include/linux/linkage.h | 4 ++++ tools/testing/selftests/vDSO/Makefile | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/include/linux/linkage.h b/tools/include/linux/linkage.h index bc763d500262..a48ff086899c 100644 --- a/tools/include/linux/linkage.h +++ b/tools/include/linux/linkage.h @@ -1,4 +1,8 @@ #ifndef _TOOLS_INCLUDE_LINUX_LINKAGE_H #define _TOOLS_INCLUDE_LINUX_LINKAGE_H +#define SYM_FUNC_START(x) .globl x; x: + +#define SYM_FUNC_END(x) + #endif /* _TOOLS_INCLUDE_LINUX_LINKAGE_H */ diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index c9a819cacbf2..10ffdda3f2fa 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -42,7 +42,7 @@ $(OUTPUT)/vdso_test_getrandom: CFLAGS += -isystem $(top_srcdir)/tools/include \ $(OUTPUT)/vdso_test_chacha: $(top_srcdir)/tools/arch/$(ARCH)/vdso/vgetrandom-chacha.S $(OUTPUT)/vdso_test_chacha: CFLAGS += -idirafter $(top_srcdir)/tools/include \ - -isystem $(top_srcdir)/arch/$(ARCH)/include \ - -isystem $(top_srcdir)/include \ + -idirafter $(top_srcdir)/arch/$(ARCH)/include \ + -idirafter $(top_srcdir)/include \ -D__ASSEMBLY__ -DBULID_VDSO -DCONFIG_FUNCTION_ALIGNMENT=0 \ -Wa,--noexecstack $(SODIUM) From e1bbcab496f745d963e43a6e0f669359e82c4934 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Thu, 22 Aug 2024 09:13:22 +0200 Subject: [PATCH 239/573] selftests: vDSO: look for arch-specific function name in getrandom test Don't hard-code x86 specific names. Rather, use vdso_config definitions to find the correct function matching the architecture. Add random VDSO function names in names[][]. Remove the #ifdef CONFIG_VDSO32, as having the name there all the time is harmless and guaranties a steady index for following strings. Signed-off-by: Christophe Leroy [Jason: add [6] to variable declaration rather than each usage site.] Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/vdso_config.h | 8 +++----- tools/testing/selftests/vDSO/vdso_test_getrandom.c | 8 ++++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/vDSO/vdso_config.h b/tools/testing/selftests/vDSO/vdso_config.h index 7b543e7f04d7..b5ba5ded3a61 100644 --- a/tools/testing/selftests/vDSO/vdso_config.h +++ b/tools/testing/selftests/vDSO/vdso_config.h @@ -68,16 +68,15 @@ static const char *versions[7] = { "LINUX_5.10" }; -static const char *names[2][6] = { +static const char *names[2][7] = { { "__kernel_gettimeofday", "__kernel_clock_gettime", "__kernel_time", "__kernel_clock_getres", "__kernel_getcpu", -#if defined(VDSO_32BIT) "__kernel_clock_gettime64", -#endif + "__kernel_getrandom", }, { "__vdso_gettimeofday", @@ -85,9 +84,8 @@ static const char *names[2][6] = { "__vdso_time", "__vdso_clock_getres", "__vdso_getcpu", -#if defined(VDSO_32BIT) "__vdso_clock_gettime64", -#endif + "__vdso_getrandom", }, }; diff --git a/tools/testing/selftests/vDSO/vdso_test_getrandom.c b/tools/testing/selftests/vDSO/vdso_test_getrandom.c index 89c961175956..20bbef992c48 100644 --- a/tools/testing/selftests/vDSO/vdso_test_getrandom.c +++ b/tools/testing/selftests/vDSO/vdso_test_getrandom.c @@ -21,6 +21,7 @@ #include "../kselftest.h" #include "parse_vdso.h" +#include "vdso_config.h" #ifndef timespecsub #define timespecsub(tsp, usp, vsp) \ @@ -100,15 +101,18 @@ static void vgetrandom_put_state(void *state) static void vgetrandom_init(void) { + const char *version = versions[VDSO_VERSION]; + const char *name = names[VDSO_NAMES][6]; unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR); + if (!sysinfo_ehdr) { printf("AT_SYSINFO_EHDR is not present!\n"); exit(KSFT_SKIP); } vdso_init_from_sysinfo_ehdr(sysinfo_ehdr); - vgrnd.fn = (__typeof__(vgrnd.fn))vdso_sym("LINUX_2.6", "__vdso_getrandom"); + vgrnd.fn = (__typeof__(vgrnd.fn))vdso_sym(version, name); if (!vgrnd.fn) { - printf("__vdso_getrandom is missing!\n"); + printf("%s is missing!\n", name); exit(KSFT_FAIL); } if (vgrnd.fn(NULL, 0, 0, &vgrnd.params, ~0UL) != 0) { From 1e661b349041c3d5026d9e95930b1d396c0c6e6d Mon Sep 17 00:00:00 2001 From: Xi Ruoyao Date: Tue, 27 Aug 2024 21:20:15 +0800 Subject: [PATCH 240/573] selftests: vDSO: add --cflags for pkg-config command querying libsodium When libsodium is installed into its own prefix, the --cflags output is needed for the compiler to find libsodium headers. Signed-off-by: Xi Ruoyao Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index 10ffdda3f2fa..180854eb9fec 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 uname_M := $(shell uname -m 2>/dev/null || echo not) ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/x86/ -e s/x86_64/x86/) -SODIUM := $(shell pkg-config --libs libsodium 2>/dev/null) +SODIUM := $(shell pkg-config --libs --cflags libsodium 2>/dev/null) TEST_GEN_PROGS := vdso_test_gettimeofday TEST_GEN_PROGS += vdso_test_getcpu From a5330eb3bcd87f3ce7738a2e898671a0ff561e85 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Tue, 27 Aug 2024 16:27:13 +0200 Subject: [PATCH 241/573] selftests: vDSO: separate LDLIBS from CFLAGS for libsodium On systems that set -Wl,--as-needed, putting the -lsodium in the wrong place on the command line means we get a linker error: CC vdso_test_chacha /usr/bin/ld: /tmp/ccKpjnSM.o: in function `main': vdso_test_chacha.c:(.text+0x276): undefined reference to `crypto_stream_chacha20' collect2: error: ld returned 1 exit status Fix this by passing pkg-config's --libs output to the LDFLAGS field instead of the CFLAGS field, as is customary. Reported-by: Adhemerval Zanella Netto Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index 180854eb9fec..834aa862ba2c 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -1,7 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 uname_M := $(shell uname -m 2>/dev/null || echo not) ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/x86/ -e s/x86_64/x86/) -SODIUM := $(shell pkg-config --libs --cflags libsodium 2>/dev/null) +SODIUM_LIBS := $(shell pkg-config --libs libsodium 2>/dev/null) +SODIUM_CFLAGS := $(shell pkg-config --cflags libsodium 2>/dev/null) TEST_GEN_PROGS := vdso_test_gettimeofday TEST_GEN_PROGS += vdso_test_getcpu @@ -13,7 +14,7 @@ endif TEST_GEN_PROGS += vdso_test_correctness ifeq ($(uname_M),x86_64) TEST_GEN_PROGS += vdso_test_getrandom -ifneq ($(SODIUM),) +ifneq ($(SODIUM_LIBS),) TEST_GEN_PROGS += vdso_test_chacha endif endif @@ -41,8 +42,9 @@ $(OUTPUT)/vdso_test_getrandom: CFLAGS += -isystem $(top_srcdir)/tools/include \ -isystem $(top_srcdir)/include/uapi $(OUTPUT)/vdso_test_chacha: $(top_srcdir)/tools/arch/$(ARCH)/vdso/vgetrandom-chacha.S +$(OUTPUT)/vdso_test_chacha: LDLIBS += $(SODIUM_LIBS) $(OUTPUT)/vdso_test_chacha: CFLAGS += -idirafter $(top_srcdir)/tools/include \ -idirafter $(top_srcdir)/arch/$(ARCH)/include \ -idirafter $(top_srcdir)/include \ -D__ASSEMBLY__ -DBULID_VDSO -DCONFIG_FUNCTION_ALIGNMENT=0 \ - -Wa,--noexecstack $(SODIUM) + -Wa,--noexecstack $(SODIUM_CFLAGS) From be9155154bc39e910ba8e56f32be299cf83e21d0 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Tue, 27 Aug 2024 16:52:36 +0200 Subject: [PATCH 242/573] selftests: vDSO: remove unnecessary command line defs from chacha test CONFIG_FUNCTION_ALIGNMENT=0 is no longer necessary and BULID_VDSO wasn't spelled right while BUILD_VDSO isn't necessary, so just remove these. Reported-by: Adhemerval Zanella Netto Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index 834aa862ba2c..d1452c7d6d4f 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -46,5 +46,4 @@ $(OUTPUT)/vdso_test_chacha: LDLIBS += $(SODIUM_LIBS) $(OUTPUT)/vdso_test_chacha: CFLAGS += -idirafter $(top_srcdir)/tools/include \ -idirafter $(top_srcdir)/arch/$(ARCH)/include \ -idirafter $(top_srcdir)/include \ - -D__ASSEMBLY__ -DBULID_VDSO -DCONFIG_FUNCTION_ALIGNMENT=0 \ - -Wa,--noexecstack $(SODIUM_CFLAGS) + -D__ASSEMBLY__ -Wa,--noexecstack $(SODIUM_CFLAGS) From b90eeff1badd9c3d12123e2721ae08dfb689cda7 Mon Sep 17 00:00:00 2001 From: Xi Ruoyao Date: Wed, 28 Aug 2024 13:29:57 +0200 Subject: [PATCH 243/573] selftests: vDSO: use KHDR_INCLUDES for UAPI headers for getrandom test Building test_vdso_getrandom currently leads to following issue: In file included from /home/xry111/git-repos/linux/tools/include/linux/compiler_types.h:36, from /home/xry111/git-repos/linux/include/uapi/linux/stddef.h:5, from /home/xry111/git-repos/linux/include/uapi/linux/posix_types.h:5, from /usr/include/asm/sigcontext.h:12, from /usr/include/bits/sigcontext.h:30, from /usr/include/signal.h:301, from vdso_test_getrandom.c:14: /home/xry111/git-repos/linux/tools/include/linux/compiler-gcc.h:3:2: error: #error "Please don't include directly, include instead." 3 | #error "Please don't include directly, include instead." | ^~~~~ It's because the compiler_types.h inclusion in include/uapi/linux/stddef.h is expected to be removed by the header_install.sh script, as compiler_types.h shouldn't be used from user space. Add KHDR_INCLUDES before the existing include/uapi inclusion so that usr/include takes precedence if it's populated. Signed-off-by: Xi Ruoyao Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index d1452c7d6d4f..32ea4c2a9a23 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -39,6 +39,7 @@ $(OUTPUT)/vdso_test_correctness: LDFLAGS += -ldl $(OUTPUT)/vdso_test_getrandom: parse_vdso.c $(OUTPUT)/vdso_test_getrandom: CFLAGS += -isystem $(top_srcdir)/tools/include \ + $(KHDR_INCLUDES) \ -isystem $(top_srcdir)/include/uapi $(OUTPUT)/vdso_test_chacha: $(top_srcdir)/tools/arch/$(ARCH)/vdso/vgetrandom-chacha.S From f78280b1a3cedd9f68d5f596179675514a15bd1d Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Thu, 29 Aug 2024 20:23:23 +0200 Subject: [PATCH 244/573] selftests: vDSO: skip getrandom test if architecture is unsupported If the getrandom test compiles for an arch, don't exit fatally if the actual cpu it's running on is unsupported. Suggested-by: Adhemerval Zanella Netto Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/vdso_test_getrandom.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/vDSO/vdso_test_getrandom.c b/tools/testing/selftests/vDSO/vdso_test_getrandom.c index 20bbef992c48..5db8ac8999cd 100644 --- a/tools/testing/selftests/vDSO/vdso_test_getrandom.c +++ b/tools/testing/selftests/vDSO/vdso_test_getrandom.c @@ -104,6 +104,7 @@ static void vgetrandom_init(void) const char *version = versions[VDSO_VERSION]; const char *name = names[VDSO_NAMES][6]; unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR); + size_t ret; if (!sysinfo_ehdr) { printf("AT_SYSINFO_EHDR is not present!\n"); @@ -115,7 +116,11 @@ static void vgetrandom_init(void) printf("%s is missing!\n", name); exit(KSFT_FAIL); } - if (vgrnd.fn(NULL, 0, 0, &vgrnd.params, ~0UL) != 0) { + ret = vgrnd.fn(NULL, 0, 0, &vgrnd.params, ~0UL); + if (ret == -ENOSYS) { + printf("unsupported architecture\n"); + exit(KSFT_SKIP); + } else if (ret) { printf("failed to fetch vgetrandom params!\n"); exit(KSFT_FAIL); } From 59eb856c3ed9b3552befd240c0c339f22eed3fa1 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Fri, 30 Aug 2024 14:28:35 +0200 Subject: [PATCH 245/573] selftests: vDSO: fix vDSO name for powerpc Following error occurs when running vdso_test_correctness on powerpc: ~ # ./vdso_test_correctness [WARN] failed to find vDSO [SKIP] No vDSO, so skipping clock_gettime() tests [SKIP] No vDSO, so skipping clock_gettime64() tests [RUN] Testing getcpu... [OK] CPU 0: syscall: cpu 0, node 0 On powerpc, vDSO is neither called linux-vdso.so.1 nor linux-gate.so.1 but linux-vdso32.so.1 or linux-vdso64.so.1. Also search those two names before giving up. Fixes: c7e5789b24d3 ("kselftest: Move test_vdso to the vDSO test suite") Signed-off-by: Christophe Leroy Acked-by: Shuah Khan Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/vdso_test_correctness.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/testing/selftests/vDSO/vdso_test_correctness.c b/tools/testing/selftests/vDSO/vdso_test_correctness.c index e691a3cf1491..cdb697ae8343 100644 --- a/tools/testing/selftests/vDSO/vdso_test_correctness.c +++ b/tools/testing/selftests/vDSO/vdso_test_correctness.c @@ -114,6 +114,12 @@ static void fill_function_pointers() if (!vdso) vdso = dlopen("linux-gate.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + if (!vdso) + vdso = dlopen("linux-vdso32.so.1", + RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + if (!vdso) + vdso = dlopen("linux-vdso64.so.1", + RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); if (!vdso) { printf("[WARN]\tfailed to find vDSO\n"); return; From 7d297c419b08eafa69ce27243ee9bbecab4fcaa4 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Fri, 30 Aug 2024 14:28:36 +0200 Subject: [PATCH 246/573] selftests: vDSO: fix vdso_config for powerpc Running vdso_test_correctness on powerpc64 gives the following warning: ~ # ./vdso_test_correctness Warning: failed to find clock_gettime64 in vDSO This is because vdso_test_correctness was built with VDSO_32BIT defined. __powerpc__ macro is defined on both powerpc32 and powerpc64 so __powerpc64__ needs to be checked first in vdso_config.h Fixes: 693f5ca08ca0 ("kselftest: Extend vDSO selftest") Signed-off-by: Christophe Leroy Acked-by: Shuah Khan Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/vdso_config.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/vDSO/vdso_config.h b/tools/testing/selftests/vDSO/vdso_config.h index b5ba5ded3a61..740ce8c98d2e 100644 --- a/tools/testing/selftests/vDSO/vdso_config.h +++ b/tools/testing/selftests/vDSO/vdso_config.h @@ -18,13 +18,13 @@ #elif defined(__aarch64__) #define VDSO_VERSION 3 #define VDSO_NAMES 0 +#elif defined(__powerpc64__) +#define VDSO_VERSION 1 +#define VDSO_NAMES 0 #elif defined(__powerpc__) #define VDSO_VERSION 1 #define VDSO_NAMES 0 #define VDSO_32BIT 1 -#elif defined(__powerpc64__) -#define VDSO_VERSION 1 -#define VDSO_NAMES 0 #elif defined (__s390__) #define VDSO_VERSION 2 #define VDSO_NAMES 0 From ba83b3239e657469709d15dcea5f9b65bf9dbf34 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Fri, 30 Aug 2024 14:28:37 +0200 Subject: [PATCH 247/573] selftests: vDSO: fix vDSO symbols lookup for powerpc64 On powerpc64, following tests fail locating vDSO functions: ~ # ./vdso_test_abi TAP version 13 1..16 # [vDSO kselftest] VDSO_VERSION: LINUX_2.6.15 # Couldn't find __kernel_gettimeofday ok 1 # SKIP __kernel_gettimeofday # clock_id: CLOCK_REALTIME # Couldn't find __kernel_clock_gettime ok 2 # SKIP __kernel_clock_gettime CLOCK_REALTIME # Couldn't find __kernel_clock_getres ok 3 # SKIP __kernel_clock_getres CLOCK_REALTIME ... # Couldn't find __kernel_time ok 16 # SKIP __kernel_time # Totals: pass:0 fail:0 xfail:0 xpass:0 skip:16 error:0 ~ # ./vdso_test_getrandom __kernel_getrandom is missing! ~ # ./vdso_test_gettimeofday Could not find __kernel_gettimeofday ~ # ./vdso_test_getcpu Could not find __kernel_getcpu On powerpc64, as shown below by readelf, vDSO functions symbols have type NOTYPE, so also accept that type when looking for symbols. $ powerpc64-linux-gnu-readelf -a arch/powerpc/kernel/vdso/vdso64.so.dbg ELF Header: Magic: 7f 45 4c 46 02 02 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, big endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: PowerPC64 Version: 0x1 ... Symbol table '.dynsym' contains 12 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000524 84 NOTYPE GLOBAL DEFAULT 8 __[...]@@LINUX_2.6.15 2: 00000000000005f0 36 NOTYPE GLOBAL DEFAULT 8 __[...]@@LINUX_2.6.15 3: 0000000000000578 68 NOTYPE GLOBAL DEFAULT 8 __[...]@@LINUX_2.6.15 4: 0000000000000000 0 OBJECT GLOBAL DEFAULT ABS LINUX_2.6.15 5: 00000000000006c0 48 NOTYPE GLOBAL DEFAULT 8 __[...]@@LINUX_2.6.15 6: 0000000000000614 172 NOTYPE GLOBAL DEFAULT 8 __[...]@@LINUX_2.6.15 7: 00000000000006f0 84 NOTYPE GLOBAL DEFAULT 8 __[...]@@LINUX_2.6.15 8: 000000000000047c 84 NOTYPE GLOBAL DEFAULT 8 __[...]@@LINUX_2.6.15 9: 0000000000000454 12 NOTYPE GLOBAL DEFAULT 8 __[...]@@LINUX_2.6.15 10: 00000000000004d0 84 NOTYPE GLOBAL DEFAULT 8 __[...]@@LINUX_2.6.15 11: 00000000000005bc 52 NOTYPE GLOBAL DEFAULT 8 __[...]@@LINUX_2.6.15 Symbol table '.symtab' contains 56 entries: Num: Value Size Type Bind Vis Ndx Name ... 45: 0000000000000000 0 OBJECT GLOBAL DEFAULT ABS LINUX_2.6.15 46: 00000000000006c0 48 NOTYPE GLOBAL DEFAULT 8 __kernel_getcpu 47: 0000000000000524 84 NOTYPE GLOBAL DEFAULT 8 __kernel_clock_getres 48: 00000000000005f0 36 NOTYPE GLOBAL DEFAULT 8 __kernel_get_tbfreq 49: 000000000000047c 84 NOTYPE GLOBAL DEFAULT 8 __kernel_gettimeofday 50: 0000000000000614 172 NOTYPE GLOBAL DEFAULT 8 __kernel_sync_dicache 51: 00000000000006f0 84 NOTYPE GLOBAL DEFAULT 8 __kernel_getrandom 52: 0000000000000454 12 NOTYPE GLOBAL DEFAULT 8 __kernel_sigtram[...] 53: 0000000000000578 68 NOTYPE GLOBAL DEFAULT 8 __kernel_time 54: 00000000000004d0 84 NOTYPE GLOBAL DEFAULT 8 __kernel_clock_g[...] 55: 00000000000005bc 52 NOTYPE GLOBAL DEFAULT 8 __kernel_get_sys[...] Fixes: 98eedc3a9dbf ("Document the vDSO and add a reference parser") Signed-off-by: Christophe Leroy Acked-by: Shuah Khan Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/parse_vdso.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/vDSO/parse_vdso.c b/tools/testing/selftests/vDSO/parse_vdso.c index 4ae417372e9e..d9ccc5acac18 100644 --- a/tools/testing/selftests/vDSO/parse_vdso.c +++ b/tools/testing/selftests/vDSO/parse_vdso.c @@ -216,7 +216,8 @@ void *vdso_sym(const char *version, const char *name) ELF(Sym) *sym = &vdso_info.symtab[chain]; /* Check for a defined global or weak function w/ right name. */ - if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC) + if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC && + ELF64_ST_TYPE(sym->st_info) != STT_NOTYPE) continue; if (ELF64_ST_BIND(sym->st_info) != STB_GLOBAL && ELF64_ST_BIND(sym->st_info) != STB_WEAK) From 6eda706a535c3d0119eaefaad5fc119609639ed2 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Fri, 30 Aug 2024 14:28:38 +0200 Subject: [PATCH 248/573] selftests: vDSO: fix the way vDSO functions are called for powerpc vdso_test_correctness test fails on powerpc: ~ # ./vdso_test_correctness ... [RUN] Testing clock_gettime for clock CLOCK_REALTIME_ALARM (8)... [FAIL] No such clock, but __vdso_clock_gettime returned 22 [RUN] Testing clock_gettime for clock CLOCK_BOOTTIME_ALARM (9)... [FAIL] No such clock, but __vdso_clock_gettime returned 22 [RUN] Testing clock_gettime for clock CLOCK_SGI_CYCLE (10)... [FAIL] No such clock, but __vdso_clock_gettime returned 22 ... [RUN] Testing clock_gettime for clock invalid (-1)... [FAIL] No such clock, but __vdso_clock_gettime returned 22 [RUN] Testing clock_gettime for clock invalid (-2147483648)... [FAIL] No such clock, but __vdso_clock_gettime returned 22 [RUN] Testing clock_gettime for clock invalid (2147483647)... [FAIL] No such clock, but __vdso_clock_gettime returned 22 On powerpc, a call to a VDSO function is not an ordinary C function call. Unlike several architectures which returns a negative error code in case of an error, powerpc sets CR[SO] and returns the error code as a positive value. Define and use a macro called VDSO_CALL() which takes a pointer to the function to call, the number of arguments and the arguments. Also update ABI vdso documentation to reflect this subtlety. Provide a specific version of VDSO_CALL() for powerpc that negates the error code on return when CR[SO] is set. Fixes: c7e5789b24d3 ("kselftest: Move test_vdso to the vDSO test suite") Fixes: 2e9a97256616 ("selftests: vdso: Add a selftest for vDSO getcpu()") Fixes: 693f5ca08ca0 ("kselftest: Extend vDSO selftest") Fixes: b2f1c3db2887 ("kselftest: Extend vdso correctness test to clock_gettime64") Fixes: 4920a2590e91 ("selftests/vDSO: add tests for vgetrandom") Signed-off-by: Christophe Leroy Acked-by: Shuah Khan Signed-off-by: Jason A. Donenfeld --- Documentation/ABI/stable/vdso | 8 ++- tools/testing/selftests/vDSO/vdso_call.h | 70 +++++++++++++++++++ tools/testing/selftests/vDSO/vdso_test_abi.c | 9 +-- .../selftests/vDSO/vdso_test_correctness.c | 15 ++-- .../testing/selftests/vDSO/vdso_test_getcpu.c | 3 +- .../selftests/vDSO/vdso_test_getrandom.c | 5 +- .../selftests/vDSO/vdso_test_gettimeofday.c | 3 +- 7 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 tools/testing/selftests/vDSO/vdso_call.h diff --git a/Documentation/ABI/stable/vdso b/Documentation/ABI/stable/vdso index 951838d42781..85dbb6a160df 100644 --- a/Documentation/ABI/stable/vdso +++ b/Documentation/ABI/stable/vdso @@ -9,9 +9,11 @@ maps an ELF DSO into that program's address space. This DSO is called the vDSO and it often contains useful and highly-optimized alternatives to real syscalls. -These functions are called just like ordinary C function according to -your platform's ABI. Call them from a sensible context. (For example, -if you set CS on x86 to something strange, the vDSO functions are +These functions are called according to your platform's ABI. On many +platforms they are called just like ordinary C function. On other platforms +(ex: powerpc) they are called with the same convention as system calls which +is different from ordinary C functions. Call them from a sensible context. +(For example, if you set CS on x86 to something strange, the vDSO functions are within their rights to crash.) In addition, if you pass a bad pointer to a vDSO function, you might get SIGSEGV instead of -EFAULT. diff --git a/tools/testing/selftests/vDSO/vdso_call.h b/tools/testing/selftests/vDSO/vdso_call.h new file mode 100644 index 000000000000..bb237d771051 --- /dev/null +++ b/tools/testing/selftests/vDSO/vdso_call.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Macro to call vDSO functions + * + * Copyright (C) 2024 Christophe Leroy , CS GROUP France + */ +#ifndef __VDSO_CALL_H__ +#define __VDSO_CALL_H__ + +#ifdef __powerpc__ + +#define LOADARGS_1(fn, __arg1) do { \ + _r0 = fn; \ + _r3 = (long)__arg1; \ +} while (0) + +#define LOADARGS_2(fn, __arg1, __arg2) do { \ + _r0 = fn; \ + _r3 = (long)__arg1; \ + _r4 = (long)__arg2; \ +} while (0) + +#define LOADARGS_3(fn, __arg1, __arg2, __arg3) do { \ + _r0 = fn; \ + _r3 = (long)__arg1; \ + _r4 = (long)__arg2; \ + _r5 = (long)__arg3; \ +} while (0) + +#define LOADARGS_5(fn, __arg1, __arg2, __arg3, __arg4, __arg5) do { \ + _r0 = fn; \ + _r3 = (long)__arg1; \ + _r4 = (long)__arg2; \ + _r5 = (long)__arg3; \ + _r6 = (long)__arg4; \ + _r7 = (long)__arg5; \ +} while (0) + +#define VDSO_CALL(fn, nr, args...) ({ \ + register void *_r0 asm ("r0"); \ + register long _r3 asm ("r3"); \ + register long _r4 asm ("r4"); \ + register long _r5 asm ("r5"); \ + register long _r6 asm ("r6"); \ + register long _r7 asm ("r7"); \ + register long _r8 asm ("r8"); \ + register long _rval asm ("r3"); \ + \ + LOADARGS_##nr(fn, args); \ + \ + asm volatile( \ + " mtctr %0\n" \ + " bctrl\n" \ + " bns+ 1f\n" \ + " neg 3, 3\n" \ + "1:" \ + : "+r" (_r0), "=r" (_r3), "+r" (_r4), "+r" (_r5), \ + "+r" (_r6), "+r" (_r7), "+r" (_r8) \ + : "r" (_rval) \ + : "r9", "r10", "r11", "r12", "cr0", "cr1", "cr5", \ + "cr6", "cr7", "xer", "lr", "ctr", "memory" \ + ); \ + _rval; \ +}) + +#else +#define VDSO_CALL(fn, nr, args...) fn(args) +#endif + +#endif diff --git a/tools/testing/selftests/vDSO/vdso_test_abi.c b/tools/testing/selftests/vDSO/vdso_test_abi.c index 96d32fd65b42..00034208c4c6 100644 --- a/tools/testing/selftests/vDSO/vdso_test_abi.c +++ b/tools/testing/selftests/vDSO/vdso_test_abi.c @@ -20,6 +20,7 @@ #include "../kselftest.h" #include "vdso_config.h" +#include "vdso_call.h" extern void *vdso_sym(const char *version, const char *name); extern void vdso_init_from_sysinfo_ehdr(uintptr_t base); @@ -61,7 +62,7 @@ static void vdso_test_gettimeofday(void) } struct timeval tv; - long ret = vdso_gettimeofday(&tv, 0); + long ret = VDSO_CALL(vdso_gettimeofday, 2, &tv, 0); if (ret == 0) { ksft_print_msg("The time is %lld.%06lld\n", @@ -86,7 +87,7 @@ static void vdso_test_clock_gettime(clockid_t clk_id) } struct timespec ts; - long ret = vdso_clock_gettime(clk_id, &ts); + long ret = VDSO_CALL(vdso_clock_gettime, 2, clk_id, &ts); if (ret == 0) { ksft_print_msg("The time is %lld.%06lld\n", @@ -111,7 +112,7 @@ static void vdso_test_time(void) return; } - long ret = vdso_time(NULL); + long ret = VDSO_CALL(vdso_time, 1, NULL); if (ret > 0) { ksft_print_msg("The time in hours since January 1, 1970 is %lld\n", @@ -138,7 +139,7 @@ static void vdso_test_clock_getres(clockid_t clk_id) } struct timespec ts, sys_ts; - long ret = vdso_clock_getres(clk_id, &ts); + long ret = VDSO_CALL(vdso_clock_getres, 2, clk_id, &ts); if (ret == 0) { ksft_print_msg("The vdso resolution is %lld %lld\n", diff --git a/tools/testing/selftests/vDSO/vdso_test_correctness.c b/tools/testing/selftests/vDSO/vdso_test_correctness.c index cdb697ae8343..5fb97ad67eea 100644 --- a/tools/testing/selftests/vDSO/vdso_test_correctness.c +++ b/tools/testing/selftests/vDSO/vdso_test_correctness.c @@ -20,6 +20,7 @@ #include #include "vdso_config.h" +#include "vdso_call.h" #include "../kselftest.h" static const char **name; @@ -186,7 +187,7 @@ static void test_getcpu(void) ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0); if (vdso_getcpu) - ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0); + ret_vdso = VDSO_CALL(vdso_getcpu, 3, &cpu_vdso, &node_vdso, 0); if (vgetcpu) ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0); @@ -269,7 +270,7 @@ static void test_one_clock_gettime(int clock, const char *name) if (sys_clock_gettime(clock, &start) < 0) { if (errno == EINVAL) { - vdso_ret = vdso_clock_gettime(clock, &vdso); + vdso_ret = VDSO_CALL(vdso_clock_gettime, 2, clock, &vdso); if (vdso_ret == -EINVAL) { printf("[OK]\tNo such clock.\n"); } else { @@ -282,7 +283,7 @@ static void test_one_clock_gettime(int clock, const char *name) return; } - vdso_ret = vdso_clock_gettime(clock, &vdso); + vdso_ret = VDSO_CALL(vdso_clock_gettime, 2, clock, &vdso); end_ret = sys_clock_gettime(clock, &end); if (vdso_ret != 0 || end_ret != 0) { @@ -331,7 +332,7 @@ static void test_one_clock_gettime64(int clock, const char *name) if (sys_clock_gettime64(clock, &start) < 0) { if (errno == EINVAL) { - vdso_ret = vdso_clock_gettime64(clock, &vdso); + vdso_ret = VDSO_CALL(vdso_clock_gettime64, 2, clock, &vdso); if (vdso_ret == -EINVAL) { printf("[OK]\tNo such clock.\n"); } else { @@ -344,7 +345,7 @@ static void test_one_clock_gettime64(int clock, const char *name) return; } - vdso_ret = vdso_clock_gettime64(clock, &vdso); + vdso_ret = VDSO_CALL(vdso_clock_gettime64, 2, clock, &vdso); end_ret = sys_clock_gettime64(clock, &end); if (vdso_ret != 0 || end_ret != 0) { @@ -401,7 +402,7 @@ static void test_gettimeofday(void) return; } - vdso_ret = vdso_gettimeofday(&vdso, &vdso_tz); + vdso_ret = VDSO_CALL(vdso_gettimeofday, 2, &vdso, &vdso_tz); end_ret = sys_gettimeofday(&end, NULL); if (vdso_ret != 0 || end_ret != 0) { @@ -431,7 +432,7 @@ static void test_gettimeofday(void) } /* And make sure that passing NULL for tz doesn't crash. */ - vdso_gettimeofday(&vdso, NULL); + VDSO_CALL(vdso_gettimeofday, 2, &vdso, NULL); } int main(int argc, char **argv) diff --git a/tools/testing/selftests/vDSO/vdso_test_getcpu.c b/tools/testing/selftests/vDSO/vdso_test_getcpu.c index b758f68c6c9c..cdeaed45fb26 100644 --- a/tools/testing/selftests/vDSO/vdso_test_getcpu.c +++ b/tools/testing/selftests/vDSO/vdso_test_getcpu.c @@ -14,6 +14,7 @@ #include "../kselftest.h" #include "parse_vdso.h" #include "vdso_config.h" +#include "vdso_call.h" struct getcpu_cache; typedef long (*getcpu_t)(unsigned int *, unsigned int *, @@ -42,7 +43,7 @@ int main(int argc, char **argv) return KSFT_SKIP; } - ret = get_cpu(&cpu, &node, 0); + ret = VDSO_CALL(get_cpu, 3, &cpu, &node, 0); if (ret == 0) { printf("Running on CPU %u node %u\n", cpu, node); } else { diff --git a/tools/testing/selftests/vDSO/vdso_test_getrandom.c b/tools/testing/selftests/vDSO/vdso_test_getrandom.c index 5db8ac8999cd..351daeb649c8 100644 --- a/tools/testing/selftests/vDSO/vdso_test_getrandom.c +++ b/tools/testing/selftests/vDSO/vdso_test_getrandom.c @@ -22,6 +22,7 @@ #include "../kselftest.h" #include "parse_vdso.h" #include "vdso_config.h" +#include "vdso_call.h" #ifndef timespecsub #define timespecsub(tsp, usp, vsp) \ @@ -116,7 +117,7 @@ static void vgetrandom_init(void) printf("%s is missing!\n", name); exit(KSFT_FAIL); } - ret = vgrnd.fn(NULL, 0, 0, &vgrnd.params, ~0UL); + ret = VDSO_CALL(vgrnd.fn, 5, NULL, 0, 0, &vgrnd.params, ~0UL); if (ret == -ENOSYS) { printf("unsupported architecture\n"); exit(KSFT_SKIP); @@ -137,7 +138,7 @@ static ssize_t vgetrandom(void *buf, size_t len, unsigned long flags) exit(KSFT_FAIL); } } - return vgrnd.fn(buf, len, flags, state, vgrnd.params.size_of_opaque_state); + return VDSO_CALL(vgrnd.fn, 5, buf, len, flags, state, vgrnd.params.size_of_opaque_state); } enum { TRIALS = 25000000, THREADS = 256 }; diff --git a/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c b/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c index ee4f1ca56a71..e31b18ffae33 100644 --- a/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c +++ b/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c @@ -19,6 +19,7 @@ #include "../kselftest.h" #include "parse_vdso.h" #include "vdso_config.h" +#include "vdso_call.h" int main(int argc, char **argv) { @@ -43,7 +44,7 @@ int main(int argc, char **argv) } struct timeval tv; - long ret = gtod(&tv, 0); + long ret = VDSO_CALL(gtod, 2, &tv, 0); if (ret == 0) { printf("The time is %lld.%06lld\n", From f0d0dbbc101a5ed2cd844eae0c2cc0546327ef89 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Fri, 30 Aug 2024 14:28:39 +0200 Subject: [PATCH 249/573] selftests: vDSO: use parse_vdso.h in vdso_test_abi Don't duplicate parse_vdso function prototypes, include the header instead. Fixes: 693f5ca08ca0 ("kselftest: Extend vDSO selftest") Signed-off-by: Christophe Leroy Acked-by: Shuah Khan Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/vdso_test_abi.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tools/testing/selftests/vDSO/vdso_test_abi.c b/tools/testing/selftests/vDSO/vdso_test_abi.c index 00034208c4c6..a54424e2336f 100644 --- a/tools/testing/selftests/vDSO/vdso_test_abi.c +++ b/tools/testing/selftests/vDSO/vdso_test_abi.c @@ -21,10 +21,7 @@ #include "../kselftest.h" #include "vdso_config.h" #include "vdso_call.h" - -extern void *vdso_sym(const char *version, const char *name); -extern void vdso_init_from_sysinfo_ehdr(uintptr_t base); -extern void vdso_init_from_auxv(void *auxv); +#include "parse_vdso.h" static const char *version; static const char **name; From 56ae8866f3b408836c5f6cafbe6102f6e97911ba Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Thu, 29 Aug 2024 15:34:30 -0700 Subject: [PATCH 250/573] iommu/arm-smmu-v3: Issue a batch of commands to the same cmdq The driver calls in different places the arm_smmu_get_cmdq() helper, and it's fine to do so since the helper always returns the single SMMU CMDQ. However, with NVIDIA CMDQV extension or SMMU ECMDQ, there can be multiple cmdqs in the system to select one from. And either case requires a batch of commands to be issued to the same cmdq. Thus, a cmdq has to be decided in the higher-level callers. Add a cmdq pointer in arm_smmu_cmdq_batch structure, and decide the cmdq when initializing the batch. Pass its pointer down to the bottom function. Update __arm_smmu_cmdq_issue_cmd() accordingly for single command issuers. Suggested-by: Jason Gunthorpe Reviewed-by: Jason Gunthorpe Signed-off-by: Nicolin Chen Link: https://lore.kernel.org/r/2cbf5ddefb6ea611e48d67c642271bd24421eb21.1724970714.git.nicolinc@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 48 +++++++++++++-------- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 + 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index ac6c1597fb2d..7213ec538887 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -592,11 +592,11 @@ static void arm_smmu_cmdq_poll_valid_map(struct arm_smmu_cmdq *cmdq, /* Wait for the command queue to become non-full */ static int arm_smmu_cmdq_poll_until_not_full(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq *cmdq, struct arm_smmu_ll_queue *llq) { unsigned long flags; struct arm_smmu_queue_poll qp; - struct arm_smmu_cmdq *cmdq = arm_smmu_get_cmdq(smmu); int ret = 0; /* @@ -627,11 +627,11 @@ static int arm_smmu_cmdq_poll_until_not_full(struct arm_smmu_device *smmu, * Must be called with the cmdq lock held in some capacity. */ static int __arm_smmu_cmdq_poll_until_msi(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq *cmdq, struct arm_smmu_ll_queue *llq) { int ret = 0; struct arm_smmu_queue_poll qp; - struct arm_smmu_cmdq *cmdq = arm_smmu_get_cmdq(smmu); u32 *cmd = (u32 *)(Q_ENT(&cmdq->q, llq->prod)); queue_poll_init(smmu, &qp); @@ -651,10 +651,10 @@ static int __arm_smmu_cmdq_poll_until_msi(struct arm_smmu_device *smmu, * Must be called with the cmdq lock held in some capacity. */ static int __arm_smmu_cmdq_poll_until_consumed(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq *cmdq, struct arm_smmu_ll_queue *llq) { struct arm_smmu_queue_poll qp; - struct arm_smmu_cmdq *cmdq = arm_smmu_get_cmdq(smmu); u32 prod = llq->prod; int ret = 0; @@ -701,12 +701,13 @@ static int __arm_smmu_cmdq_poll_until_consumed(struct arm_smmu_device *smmu, } static int arm_smmu_cmdq_poll_until_sync(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq *cmdq, struct arm_smmu_ll_queue *llq) { if (smmu->options & ARM_SMMU_OPT_MSIPOLL) - return __arm_smmu_cmdq_poll_until_msi(smmu, llq); + return __arm_smmu_cmdq_poll_until_msi(smmu, cmdq, llq); - return __arm_smmu_cmdq_poll_until_consumed(smmu, llq); + return __arm_smmu_cmdq_poll_until_consumed(smmu, cmdq, llq); } static void arm_smmu_cmdq_write_entries(struct arm_smmu_cmdq *cmdq, u64 *cmds, @@ -743,13 +744,13 @@ static void arm_smmu_cmdq_write_entries(struct arm_smmu_cmdq *cmdq, u64 *cmds, * CPU will appear before any of the commands from the other CPU. */ static int arm_smmu_cmdq_issue_cmdlist(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq *cmdq, u64 *cmds, int n, bool sync) { u64 cmd_sync[CMDQ_ENT_DWORDS]; u32 prod; unsigned long flags; bool owner; - struct arm_smmu_cmdq *cmdq = arm_smmu_get_cmdq(smmu); struct arm_smmu_ll_queue llq, head; int ret = 0; @@ -763,7 +764,7 @@ static int arm_smmu_cmdq_issue_cmdlist(struct arm_smmu_device *smmu, while (!queue_has_space(&llq, n + sync)) { local_irq_restore(flags); - if (arm_smmu_cmdq_poll_until_not_full(smmu, &llq)) + if (arm_smmu_cmdq_poll_until_not_full(smmu, cmdq, &llq)) dev_err_ratelimited(smmu->dev, "CMDQ timeout\n"); local_irq_save(flags); } @@ -839,7 +840,7 @@ static int arm_smmu_cmdq_issue_cmdlist(struct arm_smmu_device *smmu, /* 5. If we are inserting a CMD_SYNC, we must wait for it to complete */ if (sync) { llq.prod = queue_inc_prod_n(&llq, n); - ret = arm_smmu_cmdq_poll_until_sync(smmu, &llq); + ret = arm_smmu_cmdq_poll_until_sync(smmu, cmdq, &llq); if (ret) { dev_err_ratelimited(smmu->dev, "CMD_SYNC timeout at 0x%08x [hwprod 0x%08x, hwcons 0x%08x]\n", @@ -874,7 +875,8 @@ static int __arm_smmu_cmdq_issue_cmd(struct arm_smmu_device *smmu, return -EINVAL; } - return arm_smmu_cmdq_issue_cmdlist(smmu, cmd, 1, sync); + return arm_smmu_cmdq_issue_cmdlist( + smmu, arm_smmu_get_cmdq(smmu), cmd, 1, sync); } static int arm_smmu_cmdq_issue_cmd(struct arm_smmu_device *smmu, @@ -889,6 +891,13 @@ static int arm_smmu_cmdq_issue_cmd_with_sync(struct arm_smmu_device *smmu, return __arm_smmu_cmdq_issue_cmd(smmu, ent, true); } +static void arm_smmu_cmdq_batch_init(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq_batch *cmds) +{ + cmds->num = 0; + cmds->cmdq = arm_smmu_get_cmdq(smmu); +} + static void arm_smmu_cmdq_batch_add(struct arm_smmu_device *smmu, struct arm_smmu_cmdq_batch *cmds, struct arm_smmu_cmdq_ent *cmd) @@ -897,13 +906,15 @@ static void arm_smmu_cmdq_batch_add(struct arm_smmu_device *smmu, if (cmds->num == CMDQ_BATCH_ENTRIES - 1 && (smmu->options & ARM_SMMU_OPT_CMDQ_FORCE_SYNC)) { - arm_smmu_cmdq_issue_cmdlist(smmu, cmds->cmds, cmds->num, true); - cmds->num = 0; + arm_smmu_cmdq_issue_cmdlist(smmu, cmds->cmdq, cmds->cmds, + cmds->num, true); + arm_smmu_cmdq_batch_init(smmu, cmds); } if (cmds->num == CMDQ_BATCH_ENTRIES) { - arm_smmu_cmdq_issue_cmdlist(smmu, cmds->cmds, cmds->num, false); - cmds->num = 0; + arm_smmu_cmdq_issue_cmdlist(smmu, cmds->cmdq, cmds->cmds, + cmds->num, false); + arm_smmu_cmdq_batch_init(smmu, cmds); } index = cmds->num * CMDQ_ENT_DWORDS; @@ -919,7 +930,8 @@ static void arm_smmu_cmdq_batch_add(struct arm_smmu_device *smmu, static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu, struct arm_smmu_cmdq_batch *cmds) { - return arm_smmu_cmdq_issue_cmdlist(smmu, cmds->cmds, cmds->num, true); + return arm_smmu_cmdq_issue_cmdlist(smmu, cmds->cmdq, cmds->cmds, + cmds->num, true); } static void arm_smmu_page_response(struct device *dev, struct iopf_fault *unused, @@ -1170,7 +1182,7 @@ static void arm_smmu_sync_cd(struct arm_smmu_master *master, }, }; - cmds.num = 0; + arm_smmu_cmdq_batch_init(smmu, &cmds); for (i = 0; i < master->num_streams; i++) { cmd.cfgi.sid = master->streams[i].id; arm_smmu_cmdq_batch_add(smmu, &cmds, &cmd); @@ -2021,7 +2033,7 @@ static int arm_smmu_atc_inv_master(struct arm_smmu_master *master, arm_smmu_atc_inv_to_cmd(ssid, 0, 0, &cmd); - cmds.num = 0; + arm_smmu_cmdq_batch_init(master->smmu, &cmds); for (i = 0; i < master->num_streams; i++) { cmd.atc.sid = master->streams[i].id; arm_smmu_cmdq_batch_add(master->smmu, &cmds, &cmd); @@ -2059,7 +2071,7 @@ int arm_smmu_atc_inv_domain(struct arm_smmu_domain *smmu_domain, if (!atomic_read(&smmu_domain->nr_ats_masters)) return 0; - cmds.num = 0; + arm_smmu_cmdq_batch_init(smmu_domain->smmu, &cmds); spin_lock_irqsave(&smmu_domain->devices_lock, flags); list_for_each_entry(master_domain, &smmu_domain->devices, @@ -2141,7 +2153,7 @@ static void __arm_smmu_tlb_inv_range(struct arm_smmu_cmdq_ent *cmd, num_pages++; } - cmds.num = 0; + arm_smmu_cmdq_batch_init(smmu, &cmds); while (iova < end) { if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) { diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index c425c878bbd4..ba24f9efc5c9 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -570,6 +570,7 @@ struct arm_smmu_cmdq { struct arm_smmu_cmdq_batch { u64 cmds[CMDQ_BATCH_ENTRIES * CMDQ_ENT_DWORDS]; + struct arm_smmu_cmdq *cmdq; int num; }; From 2ea1f0120f900b2643afc71cc6bf5bab52df27d8 Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Thu, 29 Aug 2024 15:34:31 -0700 Subject: [PATCH 251/573] iommu/arm-smmu-v3: Pass in cmdq pointer to arm_smmu_cmdq_build_sync_cmd The CMDQV extension on NVIDIA Tegra241 SoC only supports CS_NONE in the CS field of CMD_SYNC, v.s. standard SMMU CMDQ. Pass in the cmdq pointer directly, so the function can identify a different cmdq implementation. Reviewed-by: Jason Gunthorpe Signed-off-by: Nicolin Chen Link: https://lore.kernel.org/r/723288287997b6dfbcd2a904d2c11e9b23f82250.1724970714.git.nicolinc@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 7213ec538887..4a28cd2dc47a 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -352,8 +352,9 @@ static struct arm_smmu_cmdq *arm_smmu_get_cmdq(struct arm_smmu_device *smmu) } static void arm_smmu_cmdq_build_sync_cmd(u64 *cmd, struct arm_smmu_device *smmu, - struct arm_smmu_queue *q, u32 prod) + struct arm_smmu_cmdq *cmdq, u32 prod) { + struct arm_smmu_queue *q = &cmdq->q; struct arm_smmu_cmdq_ent ent = { .opcode = CMDQ_OP_CMD_SYNC, }; @@ -371,7 +372,7 @@ static void arm_smmu_cmdq_build_sync_cmd(u64 *cmd, struct arm_smmu_device *smmu, } static void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu, - struct arm_smmu_queue *q) + struct arm_smmu_cmdq *cmdq) { static const char * const cerror_str[] = { [CMDQ_ERR_CERROR_NONE_IDX] = "No error", @@ -379,6 +380,7 @@ static void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu, [CMDQ_ERR_CERROR_ABT_IDX] = "Abort on command fetch", [CMDQ_ERR_CERROR_ATC_INV_IDX] = "ATC invalidate timeout", }; + struct arm_smmu_queue *q = &cmdq->q; int i; u64 cmd[CMDQ_ENT_DWORDS]; @@ -427,7 +429,7 @@ static void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu, static void arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu) { - __arm_smmu_cmdq_skip_err(smmu, &smmu->cmdq.q); + __arm_smmu_cmdq_skip_err(smmu, &smmu->cmdq); } /* @@ -790,7 +792,7 @@ static int arm_smmu_cmdq_issue_cmdlist(struct arm_smmu_device *smmu, arm_smmu_cmdq_write_entries(cmdq, cmds, llq.prod, n); if (sync) { prod = queue_inc_prod_n(&llq, n); - arm_smmu_cmdq_build_sync_cmd(cmd_sync, smmu, &cmdq->q, prod); + arm_smmu_cmdq_build_sync_cmd(cmd_sync, smmu, cmdq, prod); queue_write(Q_ENT(&cmdq->q, prod), cmd_sync, CMDQ_ENT_DWORDS); /* From e736c895c45bfcf9a9c675022e51fcabbb33e748 Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Thu, 29 Aug 2024 15:34:32 -0700 Subject: [PATCH 252/573] iommu/arm-smmu-v3: Pass in cmdq pointer to arm_smmu_cmdq_init So that this function can be used by other cmdqs than &smmu->cmdq only. Reviewed-by: Jason Gunthorpe Signed-off-by: Nicolin Chen Link: https://lore.kernel.org/r/e11a3c0bde172c9652c2946f12bc2ceed4c3a355.1724970714.git.nicolinc@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 4a28cd2dc47a..22578d1aa268 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -3564,9 +3564,9 @@ static int arm_smmu_init_one_queue(struct arm_smmu_device *smmu, return 0; } -static int arm_smmu_cmdq_init(struct arm_smmu_device *smmu) +static int arm_smmu_cmdq_init(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq *cmdq) { - struct arm_smmu_cmdq *cmdq = &smmu->cmdq; unsigned int nents = 1 << cmdq->q.llq.max_n_shift; atomic_set(&cmdq->owner_prod, 0); @@ -3591,7 +3591,7 @@ static int arm_smmu_init_queues(struct arm_smmu_device *smmu) if (ret) return ret; - ret = arm_smmu_cmdq_init(smmu); + ret = arm_smmu_cmdq_init(smmu, &smmu->cmdq); if (ret) return ret; From a7a08b857a32d2f17fb9aba42e2c30d816ce5f1c Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Thu, 29 Aug 2024 15:34:33 -0700 Subject: [PATCH 253/573] iommu/arm-smmu-v3: Make symbols public for CONFIG_TEGRA241_CMDQV The symbols __arm_smmu_cmdq_skip_err(), arm_smmu_init_one_queue(), and arm_smmu_cmdq_init() need to be used by the tegra241-cmdqv compilation unit in a following patch. Remove the static and put prototypes in the header. Reviewed-by: Jason Gunthorpe Signed-off-by: Nicolin Chen Link: https://lore.kernel.org/r/c4f2aa5f5f40a2e7c68b132c6d3171d6403de57a.1724970714.git.nicolinc@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 18 ++++++++---------- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 9 +++++++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 22578d1aa268..061a61f4ff0a 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -371,8 +371,8 @@ static void arm_smmu_cmdq_build_sync_cmd(u64 *cmd, struct arm_smmu_device *smmu, arm_smmu_cmdq_build_cmd(cmd, &ent); } -static void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu, - struct arm_smmu_cmdq *cmdq) +void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq *cmdq) { static const char * const cerror_str[] = { [CMDQ_ERR_CERROR_NONE_IDX] = "No error", @@ -3521,12 +3521,10 @@ static struct iommu_dirty_ops arm_smmu_dirty_ops = { }; /* Probing and initialisation functions */ -static int arm_smmu_init_one_queue(struct arm_smmu_device *smmu, - struct arm_smmu_queue *q, - void __iomem *page, - unsigned long prod_off, - unsigned long cons_off, - size_t dwords, const char *name) +int arm_smmu_init_one_queue(struct arm_smmu_device *smmu, + struct arm_smmu_queue *q, void __iomem *page, + unsigned long prod_off, unsigned long cons_off, + size_t dwords, const char *name) { size_t qsz; @@ -3564,8 +3562,8 @@ static int arm_smmu_init_one_queue(struct arm_smmu_device *smmu, return 0; } -static int arm_smmu_cmdq_init(struct arm_smmu_device *smmu, - struct arm_smmu_cmdq *cmdq) +int arm_smmu_cmdq_init(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq *cmdq) { unsigned int nents = 1 << cmdq->q.llq.max_n_shift; diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index ba24f9efc5c9..50efc804f91c 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -816,6 +816,15 @@ void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid, int arm_smmu_atc_inv_domain(struct arm_smmu_domain *smmu_domain, unsigned long iova, size_t size); +void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq *cmdq); +int arm_smmu_init_one_queue(struct arm_smmu_device *smmu, + struct arm_smmu_queue *q, void __iomem *page, + unsigned long prod_off, unsigned long cons_off, + size_t dwords, const char *name); +int arm_smmu_cmdq_init(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq *cmdq); + #ifdef CONFIG_ARM_SMMU_V3_SVA bool arm_smmu_sva_supported(struct arm_smmu_device *smmu); bool arm_smmu_master_sva_supported(struct arm_smmu_master *master); From b935a5b1c670c0a167f1263df5647b1b5b06e806 Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Thu, 29 Aug 2024 15:34:34 -0700 Subject: [PATCH 254/573] iommu/arm-smmu-v3: Add ARM_SMMU_OPT_TEGRA241_CMDQV The CMDQV extension in NVIDIA Tegra241 SoC only supports CS_NONE in the CS field of CMD_SYNC. Add a new SMMU option to accommodate that. Suggested-by: Will Deacon Reviewed-by: Jason Gunthorpe Signed-off-by: Nicolin Chen Link: https://lore.kernel.org/r/a3cb9bb2429fbae4a59f7ef517614d226763d717.1724970714.git.nicolinc@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 16 +++++++++++++++- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 061a61f4ff0a..816f5937345a 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -351,6 +351,15 @@ static struct arm_smmu_cmdq *arm_smmu_get_cmdq(struct arm_smmu_device *smmu) return &smmu->cmdq; } +static bool arm_smmu_cmdq_needs_busy_polling(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq *cmdq) +{ + if (cmdq == &smmu->cmdq) + return false; + + return smmu->options & ARM_SMMU_OPT_TEGRA241_CMDQV; +} + static void arm_smmu_cmdq_build_sync_cmd(u64 *cmd, struct arm_smmu_device *smmu, struct arm_smmu_cmdq *cmdq, u32 prod) { @@ -369,6 +378,8 @@ static void arm_smmu_cmdq_build_sync_cmd(u64 *cmd, struct arm_smmu_device *smmu, } arm_smmu_cmdq_build_cmd(cmd, &ent); + if (arm_smmu_cmdq_needs_busy_polling(smmu, cmdq)) + u64p_replace_bits(cmd, CMDQ_SYNC_0_CS_NONE, CMDQ_SYNC_0_CS); } void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu, @@ -423,6 +434,8 @@ void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu, /* Convert the erroneous command into a CMD_SYNC */ arm_smmu_cmdq_build_cmd(cmd, &cmd_sync); + if (arm_smmu_cmdq_needs_busy_polling(smmu, cmdq)) + u64p_replace_bits(cmd, CMDQ_SYNC_0_CS_NONE, CMDQ_SYNC_0_CS); queue_write(Q_ENT(q, cons), cmd, q->ent_dwords); } @@ -706,7 +719,8 @@ static int arm_smmu_cmdq_poll_until_sync(struct arm_smmu_device *smmu, struct arm_smmu_cmdq *cmdq, struct arm_smmu_ll_queue *llq) { - if (smmu->options & ARM_SMMU_OPT_MSIPOLL) + if (smmu->options & ARM_SMMU_OPT_MSIPOLL && + !arm_smmu_cmdq_needs_busy_polling(smmu, cmdq)) return __arm_smmu_cmdq_poll_until_msi(smmu, cmdq, llq); return __arm_smmu_cmdq_poll_until_consumed(smmu, cmdq, llq); diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 50efc804f91c..21f034f0ff4c 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -665,6 +665,7 @@ struct arm_smmu_device { #define ARM_SMMU_OPT_PAGE0_REGS_ONLY (1 << 1) #define ARM_SMMU_OPT_MSIPOLL (1 << 2) #define ARM_SMMU_OPT_CMDQ_FORCE_SYNC (1 << 3) +#define ARM_SMMU_OPT_TEGRA241_CMDQV (1 << 4) u32 options; struct arm_smmu_cmdq cmdq; From 6f3f9ff43d005571a8d70d4a562ed7c4150e324c Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Thu, 29 Aug 2024 15:34:35 -0700 Subject: [PATCH 255/573] iommu/arm-smmu-v3: Add acpi_smmu_iort_probe_model for impl For model-specific implementation, repurpose the acpi_smmu_get_options() to a wider acpi_smmu_acpi_probe_model(). A new model can add to the list in this new function. Suggested-by: Will Deacon Signed-off-by: Nicolin Chen Link: https://lore.kernel.org/r/79716299829aeab2e55b8c7932f2634b209bb4d5.1724970714.git.nicolinc@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 816f5937345a..f23245012681 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -4341,18 +4341,25 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu) } #ifdef CONFIG_ACPI -static void acpi_smmu_get_options(u32 model, struct arm_smmu_device *smmu) +static int acpi_smmu_iort_probe_model(struct acpi_iort_node *node, + struct arm_smmu_device *smmu) { - switch (model) { + struct acpi_iort_smmu_v3 *iort_smmu = + (struct acpi_iort_smmu_v3 *)node->node_data; + + switch (iort_smmu->model) { case ACPI_IORT_SMMU_V3_CAVIUM_CN99XX: smmu->options |= ARM_SMMU_OPT_PAGE0_REGS_ONLY; break; case ACPI_IORT_SMMU_V3_HISILICON_HI161X: smmu->options |= ARM_SMMU_OPT_SKIP_PREFETCH; break; + case ACPI_IORT_SMMU_V3_GENERIC: + break; } dev_notice(smmu->dev, "option mask 0x%x\n", smmu->options); + return 0; } static int arm_smmu_device_acpi_probe(struct platform_device *pdev, @@ -4367,8 +4374,6 @@ static int arm_smmu_device_acpi_probe(struct platform_device *pdev, /* Retrieve SMMUv3 specific data */ iort_smmu = (struct acpi_iort_smmu_v3 *)node->node_data; - acpi_smmu_get_options(iort_smmu->model, smmu); - if (iort_smmu->flags & ACPI_IORT_SMMU_V3_COHACC_OVERRIDE) smmu->features |= ARM_SMMU_FEAT_COHERENCY; @@ -4380,7 +4385,7 @@ static int arm_smmu_device_acpi_probe(struct platform_device *pdev, smmu->features |= ARM_SMMU_FEAT_HA; } - return 0; + return acpi_smmu_iort_probe_model(node, smmu); } #else static inline int arm_smmu_device_acpi_probe(struct platform_device *pdev, From 6de80d619203c672e5c011e8715bd965d27b69cf Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 15:34:36 -0700 Subject: [PATCH 256/573] iommu/arm-smmu-v3: Add struct arm_smmu_impl_ops Mimicing the arm-smmu (v2) driver, introduce a struct arm_smmu_impl_ops to accommodate impl routines. Suggested-by: Will Deacon Signed-off-by: Jason Gunthorpe Signed-off-by: Nicolin Chen Link: https://lore.kernel.org/r/8fe9f3805568aabf771fc6706c116459016bf62d.1724970714.git.nicolinc@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 51 ++++++++++++++++++++- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 10 ++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index f23245012681..01dc752e9338 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -348,7 +348,12 @@ static int arm_smmu_cmdq_build_cmd(u64 *cmd, struct arm_smmu_cmdq_ent *ent) static struct arm_smmu_cmdq *arm_smmu_get_cmdq(struct arm_smmu_device *smmu) { - return &smmu->cmdq; + struct arm_smmu_cmdq *cmdq = NULL; + + if (smmu->impl_ops && smmu->impl_ops->get_secondary_cmdq) + cmdq = smmu->impl_ops->get_secondary_cmdq(smmu); + + return cmdq ?: &smmu->cmdq; } static bool arm_smmu_cmdq_needs_busy_polling(struct arm_smmu_device *smmu, @@ -4052,6 +4057,14 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu) return ret; } + if (smmu->impl_ops && smmu->impl_ops->device_reset) { + ret = smmu->impl_ops->device_reset(smmu); + if (ret) { + dev_err(smmu->dev, "failed to reset impl\n"); + return ret; + } + } + return 0; } @@ -4466,6 +4479,38 @@ static void arm_smmu_rmr_install_bypass_ste(struct arm_smmu_device *smmu) iort_put_rmr_sids(dev_fwnode(smmu->dev), &rmr_list); } +static void arm_smmu_impl_remove(void *data) +{ + struct arm_smmu_device *smmu = data; + + if (smmu->impl_ops && smmu->impl_ops->device_remove) + smmu->impl_ops->device_remove(smmu); +} + +/* + * Probe all the compiled in implementations. Each one checks to see if it + * matches this HW and if so returns a devm_krealloc'd arm_smmu_device which + * replaces the callers. Otherwise the original is returned or ERR_PTR. + */ +static struct arm_smmu_device *arm_smmu_impl_probe(struct arm_smmu_device *smmu) +{ + struct arm_smmu_device *new_smmu = ERR_PTR(-ENODEV); + int ret; + + /* Add impl probe */ + + if (new_smmu == ERR_PTR(-ENODEV)) + return smmu; + if (IS_ERR(new_smmu)) + return new_smmu; + + ret = devm_add_action_or_reset(new_smmu->dev, arm_smmu_impl_remove, + new_smmu); + if (ret) + return ERR_PTR(ret); + return new_smmu; +} + static int arm_smmu_device_probe(struct platform_device *pdev) { int irq, ret; @@ -4487,6 +4532,10 @@ static int arm_smmu_device_probe(struct platform_device *pdev) if (ret) return ret; + smmu = arm_smmu_impl_probe(smmu); + if (IS_ERR(smmu)) + return PTR_ERR(smmu); + /* Base address */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 21f034f0ff4c..8d7a95b0dbd9 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -14,6 +14,8 @@ #include #include +struct arm_smmu_device; + /* MMIO registers */ #define ARM_SMMU_IDR0 0x0 #define IDR0_ST_LVL GENMASK(28, 27) @@ -630,9 +632,17 @@ struct arm_smmu_strtab_cfg { u32 strtab_base_cfg; }; +struct arm_smmu_impl_ops { + int (*device_reset)(struct arm_smmu_device *smmu); + void (*device_remove)(struct arm_smmu_device *smmu); + struct arm_smmu_cmdq *(*get_secondary_cmdq)(struct arm_smmu_device *smmu); +}; + /* An SMMUv3 instance */ struct arm_smmu_device { struct device *dev; + const struct arm_smmu_impl_ops *impl_ops; + void __iomem *base; void __iomem *page1; From 918eb5c856f6ce4cf93b4b38e4b5e156905c5943 Mon Sep 17 00:00:00 2001 From: Nate Watterson Date: Thu, 29 Aug 2024 15:34:37 -0700 Subject: [PATCH 257/573] iommu/arm-smmu-v3: Add in-kernel support for NVIDIA Tegra241 (Grace) CMDQV NVIDIA's Tegra241 Soc has a CMDQ-Virtualization (CMDQV) hardware, extending the standard ARM SMMU v3 IP to support multiple VCMDQs with virtualization capabilities. In terms of command queue, they are very like a standard SMMU CMDQ (or ECMDQs), but only support CS_NONE in the CS field of CMD_SYNC. Add a new tegra241-cmdqv driver, and insert its structure pointer into the existing arm_smmu_device, and then add related function calls in the SMMUv3 driver to interact with the CMDQV driver. In the CMDQV driver, add a minimal part for the in-kernel support: reserve VINTF0 for in-kernel use, and assign some of the VCMDQs to the VINTF0, and select one VCMDQ based on the current CPU ID to execute supported commands. This multi-queue design for in-kernel use gives some limited improvements: up to 20% reduction of invalidation time was measured by a multi-threaded DMA unmap benchmark, compared to a single queue. The other part of the CMDQV driver will be user-space support that gives a hypervisor running on the host OS to talk to the driver for virtualization use cases, allowing VMs to use VCMDQs without trappings, i.e. no VM Exits. This is designed based on IOMMUFD, and its RFC series is also under review. It will provide a guest OS a bigger improvement: 70% to 90% reductions of TLB invalidation time were measured by DMA unmap tests running in a guest, compared to nested SMMU CMDQ (with trappings). As the initial version, the CMDQV driver only supports ACPI configurations. Signed-off-by: Nate Watterson Reviewed-by: Jason Gunthorpe Co-developed-by: Nicolin Chen Signed-off-by: Nicolin Chen Link: https://lore.kernel.org/r/dce50490b2c10b7254fb36aa73ed7ffd812b283a.1724970714.git.nicolinc@nvidia.com Signed-off-by: Will Deacon --- MAINTAINERS | 1 + drivers/iommu/Kconfig | 11 + drivers/iommu/arm/arm-smmu-v3/Makefile | 1 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 33 +- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 11 + .../iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 858 ++++++++++++++++++ 6 files changed, 914 insertions(+), 1 deletion(-) create mode 100644 drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c diff --git a/MAINTAINERS b/MAINTAINERS index f328373463b0..dad28b828143 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22474,6 +22474,7 @@ M: Thierry Reding R: Krishna Reddy L: linux-tegra@vger.kernel.org S: Supported +F: drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c F: drivers/iommu/arm/arm-smmu/arm-smmu-nvidia.c F: drivers/iommu/tegra* diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index a82f10054aec..22addaedf64d 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -424,6 +424,17 @@ config ARM_SMMU_V3_KUNIT_TEST Enable this option to unit-test arm-smmu-v3 driver functions. If unsure, say N. + +config TEGRA241_CMDQV + bool "NVIDIA Tegra241 CMDQ-V extension support for ARM SMMUv3" + depends on ACPI + help + Support for NVIDIA CMDQ-Virtualization extension for ARM SMMUv3. The + CMDQ-V extension is similar to v3.3 ECMDQ for multi command queues + support, except with virtualization capabilities. + + Say Y here if your system is NVIDIA Tegra241 (Grace) or it has the same + CMDQ-V extension. endif config S390_IOMMU diff --git a/drivers/iommu/arm/arm-smmu-v3/Makefile b/drivers/iommu/arm/arm-smmu-v3/Makefile index 355173d1441d..dc98c88b48c8 100644 --- a/drivers/iommu/arm/arm-smmu-v3/Makefile +++ b/drivers/iommu/arm/arm-smmu-v3/Makefile @@ -2,5 +2,6 @@ obj-$(CONFIG_ARM_SMMU_V3) += arm_smmu_v3.o arm_smmu_v3-y := arm-smmu-v3.o arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_SVA) += arm-smmu-v3-sva.o +arm_smmu_v3-$(CONFIG_TEGRA241_CMDQV) += tegra241-cmdqv.o obj-$(CONFIG_ARM_SMMU_V3_KUNIT_TEST) += arm-smmu-v3-test.o diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 01dc752e9338..dee23ec352e7 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -4354,6 +4354,31 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu) } #ifdef CONFIG_ACPI +#ifdef CONFIG_TEGRA241_CMDQV +static void acpi_smmu_dsdt_probe_tegra241_cmdqv(struct acpi_iort_node *node, + struct arm_smmu_device *smmu) +{ + const char *uid = kasprintf(GFP_KERNEL, "%u", node->identifier); + struct acpi_device *adev; + + /* Look for an NVDA200C node whose _UID matches the SMMU node ID */ + adev = acpi_dev_get_first_match_dev("NVDA200C", uid, -1); + if (adev) { + /* Tegra241 CMDQV driver is responsible for put_device() */ + smmu->impl_dev = &adev->dev; + smmu->options |= ARM_SMMU_OPT_TEGRA241_CMDQV; + dev_info(smmu->dev, "found companion CMDQV device: %s\n", + dev_name(smmu->impl_dev)); + } + kfree(uid); +} +#else +static void acpi_smmu_dsdt_probe_tegra241_cmdqv(struct acpi_iort_node *node, + struct arm_smmu_device *smmu) +{ +} +#endif + static int acpi_smmu_iort_probe_model(struct acpi_iort_node *node, struct arm_smmu_device *smmu) { @@ -4368,6 +4393,11 @@ static int acpi_smmu_iort_probe_model(struct acpi_iort_node *node, smmu->options |= ARM_SMMU_OPT_SKIP_PREFETCH; break; case ACPI_IORT_SMMU_V3_GENERIC: + /* + * Tegra241 implementation stores its SMMU options and impl_dev + * in DSDT. Thus, go through the ACPI tables unconditionally. + */ + acpi_smmu_dsdt_probe_tegra241_cmdqv(node, smmu); break; } @@ -4497,7 +4527,8 @@ static struct arm_smmu_device *arm_smmu_impl_probe(struct arm_smmu_device *smmu) struct arm_smmu_device *new_smmu = ERR_PTR(-ENODEV); int ret; - /* Add impl probe */ + if (smmu->impl_dev && (smmu->options & ARM_SMMU_OPT_TEGRA241_CMDQV)) + new_smmu = tegra241_cmdqv_probe(smmu); if (new_smmu == ERR_PTR(-ENODEV)) return smmu; diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 8d7a95b0dbd9..9fa22ee2383d 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -641,6 +641,7 @@ struct arm_smmu_impl_ops { /* An SMMUv3 instance */ struct arm_smmu_device { struct device *dev; + struct device *impl_dev; const struct arm_smmu_impl_ops *impl_ops; void __iomem *base; @@ -882,4 +883,14 @@ static inline void arm_smmu_sva_notifier_synchronize(void) {} #define arm_smmu_sva_domain_alloc NULL #endif /* CONFIG_ARM_SMMU_V3_SVA */ + +#ifdef CONFIG_TEGRA241_CMDQV +struct arm_smmu_device *tegra241_cmdqv_probe(struct arm_smmu_device *smmu); +#else /* CONFIG_TEGRA241_CMDQV */ +static inline struct arm_smmu_device * +tegra241_cmdqv_probe(struct arm_smmu_device *smmu) +{ + return ERR_PTR(-ENODEV); +} +#endif /* CONFIG_TEGRA241_CMDQV */ #endif /* _ARM_SMMU_V3_H */ diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c new file mode 100644 index 000000000000..5ac3032ee6dd --- /dev/null +++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c @@ -0,0 +1,858 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2021-2024 NVIDIA CORPORATION & AFFILIATES. */ + +#define dev_fmt(fmt) "tegra241_cmdqv: " fmt + +#include +#include +#include +#include +#include +#include + +#include + +#include "arm-smmu-v3.h" + +/* CMDQV register page base and size defines */ +#define TEGRA241_CMDQV_CONFIG_BASE (0) +#define TEGRA241_CMDQV_CONFIG_SIZE (SZ_64K) +#define TEGRA241_VCMDQ_PAGE0_BASE (TEGRA241_CMDQV_CONFIG_BASE + SZ_64K) +#define TEGRA241_VCMDQ_PAGE1_BASE (TEGRA241_VCMDQ_PAGE0_BASE + SZ_64K) +#define TEGRA241_VINTF_PAGE_BASE (TEGRA241_VCMDQ_PAGE1_BASE + SZ_64K) + +/* CMDQV global base regs */ +#define TEGRA241_CMDQV_CONFIG 0x0000 +#define CMDQV_EN BIT(0) + +#define TEGRA241_CMDQV_PARAM 0x0004 +#define CMDQV_NUM_VINTF_LOG2 GENMASK(11, 8) +#define CMDQV_NUM_VCMDQ_LOG2 GENMASK(7, 4) + +#define TEGRA241_CMDQV_STATUS 0x0008 +#define CMDQV_ENABLED BIT(0) + +#define TEGRA241_CMDQV_VINTF_ERR_MAP 0x0014 +#define TEGRA241_CMDQV_VINTF_INT_MASK 0x001C +#define TEGRA241_CMDQV_CMDQ_ERR_MAP(m) (0x0024 + 0x4*(m)) + +#define TEGRA241_CMDQV_CMDQ_ALLOC(q) (0x0200 + 0x4*(q)) +#define CMDQV_CMDQ_ALLOC_VINTF GENMASK(20, 15) +#define CMDQV_CMDQ_ALLOC_LVCMDQ GENMASK(7, 1) +#define CMDQV_CMDQ_ALLOCATED BIT(0) + +/* VINTF base regs */ +#define TEGRA241_VINTF(v) (0x1000 + 0x100*(v)) + +#define TEGRA241_VINTF_CONFIG 0x0000 +#define VINTF_HYP_OWN BIT(17) +#define VINTF_VMID GENMASK(16, 1) +#define VINTF_EN BIT(0) + +#define TEGRA241_VINTF_STATUS 0x0004 +#define VINTF_STATUS GENMASK(3, 1) +#define VINTF_ENABLED BIT(0) + +#define TEGRA241_VINTF_LVCMDQ_ERR_MAP_64(m) \ + (0x00C0 + 0x8*(m)) +#define LVCMDQ_ERR_MAP_NUM_64 2 + +/* VCMDQ base regs */ +/* -- PAGE0 -- */ +#define TEGRA241_VCMDQ_PAGE0(q) (TEGRA241_VCMDQ_PAGE0_BASE + 0x80*(q)) + +#define TEGRA241_VCMDQ_CONS 0x00000 +#define VCMDQ_CONS_ERR GENMASK(30, 24) + +#define TEGRA241_VCMDQ_PROD 0x00004 + +#define TEGRA241_VCMDQ_CONFIG 0x00008 +#define VCMDQ_EN BIT(0) + +#define TEGRA241_VCMDQ_STATUS 0x0000C +#define VCMDQ_ENABLED BIT(0) + +#define TEGRA241_VCMDQ_GERROR 0x00010 +#define TEGRA241_VCMDQ_GERRORN 0x00014 + +/* -- PAGE1 -- */ +#define TEGRA241_VCMDQ_PAGE1(q) (TEGRA241_VCMDQ_PAGE1_BASE + 0x80*(q)) +#define VCMDQ_ADDR GENMASK(47, 5) +#define VCMDQ_LOG2SIZE GENMASK(4, 0) +#define VCMDQ_LOG2SIZE_MAX 19 + +#define TEGRA241_VCMDQ_BASE 0x00000 +#define TEGRA241_VCMDQ_CONS_INDX_BASE 0x00008 + +/* VINTF logical-VCMDQ pages */ +#define TEGRA241_VINTFi_PAGE0(i) (TEGRA241_VINTF_PAGE_BASE + SZ_128K*(i)) +#define TEGRA241_VINTFi_PAGE1(i) (TEGRA241_VINTFi_PAGE0(i) + SZ_64K) +#define TEGRA241_VINTFi_LVCMDQ_PAGE0(i, q) \ + (TEGRA241_VINTFi_PAGE0(i) + 0x80*(q)) +#define TEGRA241_VINTFi_LVCMDQ_PAGE1(i, q) \ + (TEGRA241_VINTFi_PAGE1(i) + 0x80*(q)) + +/* MMIO helpers */ +#define REG_CMDQV(_cmdqv, _regname) \ + ((_cmdqv)->base + TEGRA241_CMDQV_##_regname) +#define REG_VINTF(_vintf, _regname) \ + ((_vintf)->base + TEGRA241_VINTF_##_regname) +#define REG_VCMDQ_PAGE0(_vcmdq, _regname) \ + ((_vcmdq)->page0 + TEGRA241_VCMDQ_##_regname) +#define REG_VCMDQ_PAGE1(_vcmdq, _regname) \ + ((_vcmdq)->page1 + TEGRA241_VCMDQ_##_regname) + + +static bool disable_cmdqv; +module_param(disable_cmdqv, bool, 0444); +MODULE_PARM_DESC(disable_cmdqv, + "This allows to disable CMDQV HW and use default SMMU internal CMDQ."); + +static bool bypass_vcmdq; +module_param(bypass_vcmdq, bool, 0444); +MODULE_PARM_DESC(bypass_vcmdq, + "This allows to bypass VCMDQ for debugging use or perf comparison."); + +/** + * struct tegra241_vcmdq - Virtual Command Queue + * @idx: Global index in the CMDQV + * @lidx: Local index in the VINTF + * @enabled: Enable status + * @cmdqv: Parent CMDQV pointer + * @vintf: Parent VINTF pointer + * @cmdq: Command Queue struct + * @page0: MMIO Page0 base address + * @page1: MMIO Page1 base address + */ +struct tegra241_vcmdq { + u16 idx; + u16 lidx; + + bool enabled; + + struct tegra241_cmdqv *cmdqv; + struct tegra241_vintf *vintf; + struct arm_smmu_cmdq cmdq; + + void __iomem *page0; + void __iomem *page1; +}; + +/** + * struct tegra241_vintf - Virtual Interface + * @idx: Global index in the CMDQV + * @enabled: Enable status + * @cmdqv: Parent CMDQV pointer + * @lvcmdqs: List of logical VCMDQ pointers + * @base: MMIO base address + */ +struct tegra241_vintf { + u16 idx; + + bool enabled; + + struct tegra241_cmdqv *cmdqv; + struct tegra241_vcmdq **lvcmdqs; + + void __iomem *base; +}; + +/** + * struct tegra241_cmdqv - CMDQ-V for SMMUv3 + * @smmu: SMMUv3 device + * @dev: CMDQV device + * @base: MMIO base address + * @irq: IRQ number + * @num_vintfs: Total number of VINTFs + * @num_vcmdqs: Total number of VCMDQs + * @num_lvcmdqs_per_vintf: Number of logical VCMDQs per VINTF + * @vintf_ids: VINTF id allocator + * @vintfs: List of VINTFs + */ +struct tegra241_cmdqv { + struct arm_smmu_device smmu; + struct device *dev; + + void __iomem *base; + int irq; + + /* CMDQV Hardware Params */ + u16 num_vintfs; + u16 num_vcmdqs; + u16 num_lvcmdqs_per_vintf; + + struct ida vintf_ids; + + struct tegra241_vintf **vintfs; +}; + +/* Config and Polling Helpers */ + +static inline int tegra241_cmdqv_write_config(struct tegra241_cmdqv *cmdqv, + void __iomem *addr_config, + void __iomem *addr_status, + u32 regval, const char *header, + bool *out_enabled) +{ + bool en = regval & BIT(0); + int ret; + + writel(regval, addr_config); + ret = readl_poll_timeout(addr_status, regval, + en ? regval & BIT(0) : !(regval & BIT(0)), + 1, ARM_SMMU_POLL_TIMEOUT_US); + if (ret) + dev_err(cmdqv->dev, "%sfailed to %sable, STATUS=0x%08X\n", + header, en ? "en" : "dis", regval); + if (out_enabled) + WRITE_ONCE(*out_enabled, regval & BIT(0)); + return ret; +} + +static inline int cmdqv_write_config(struct tegra241_cmdqv *cmdqv, u32 regval) +{ + return tegra241_cmdqv_write_config(cmdqv, + REG_CMDQV(cmdqv, CONFIG), + REG_CMDQV(cmdqv, STATUS), + regval, "CMDQV: ", NULL); +} + +static inline int vintf_write_config(struct tegra241_vintf *vintf, u32 regval) +{ + char header[16]; + + snprintf(header, 16, "VINTF%u: ", vintf->idx); + return tegra241_cmdqv_write_config(vintf->cmdqv, + REG_VINTF(vintf, CONFIG), + REG_VINTF(vintf, STATUS), + regval, header, &vintf->enabled); +} + +static inline char *lvcmdq_error_header(struct tegra241_vcmdq *vcmdq, + char *header, int hlen) +{ + WARN_ON(hlen < 32); + if (WARN_ON(!vcmdq->vintf)) + return ""; + snprintf(header, hlen, "VINTF%u: VCMDQ%u/LVCMDQ%u: ", + vcmdq->vintf->idx, vcmdq->idx, vcmdq->lidx); + return header; +} + +static inline int vcmdq_write_config(struct tegra241_vcmdq *vcmdq, u32 regval) +{ + char header[32], *h = lvcmdq_error_header(vcmdq, header, 32); + + return tegra241_cmdqv_write_config(vcmdq->cmdqv, + REG_VCMDQ_PAGE0(vcmdq, CONFIG), + REG_VCMDQ_PAGE0(vcmdq, STATUS), + regval, h, &vcmdq->enabled); +} + +/* ISR Functions */ + +static void tegra241_vintf0_handle_error(struct tegra241_vintf *vintf) +{ + int i; + + for (i = 0; i < LVCMDQ_ERR_MAP_NUM_64; i++) { + u64 map = readq_relaxed(REG_VINTF(vintf, LVCMDQ_ERR_MAP_64(i))); + + while (map) { + unsigned long lidx = __ffs64(map); + struct tegra241_vcmdq *vcmdq = vintf->lvcmdqs[lidx]; + u32 gerror = readl_relaxed(REG_VCMDQ_PAGE0(vcmdq, GERROR)); + + __arm_smmu_cmdq_skip_err(&vintf->cmdqv->smmu, &vcmdq->cmdq); + writel(gerror, REG_VCMDQ_PAGE0(vcmdq, GERRORN)); + map &= ~BIT_ULL(lidx); + } + } +} + +static irqreturn_t tegra241_cmdqv_isr(int irq, void *devid) +{ + struct tegra241_cmdqv *cmdqv = (struct tegra241_cmdqv *)devid; + void __iomem *reg_vintf_map = REG_CMDQV(cmdqv, VINTF_ERR_MAP); + char err_str[256]; + u64 vintf_map; + + /* Use readl_relaxed() as register addresses are not 64-bit aligned */ + vintf_map = (u64)readl_relaxed(reg_vintf_map + 0x4) << 32 | + (u64)readl_relaxed(reg_vintf_map); + + snprintf(err_str, sizeof(err_str), + "vintf_map: %016llx, vcmdq_map %08x:%08x:%08x:%08x", vintf_map, + readl_relaxed(REG_CMDQV(cmdqv, CMDQ_ERR_MAP(3))), + readl_relaxed(REG_CMDQV(cmdqv, CMDQ_ERR_MAP(2))), + readl_relaxed(REG_CMDQV(cmdqv, CMDQ_ERR_MAP(1))), + readl_relaxed(REG_CMDQV(cmdqv, CMDQ_ERR_MAP(0)))); + + dev_warn(cmdqv->dev, "unexpected error reported. %s\n", err_str); + + /* Handle VINTF0 and its LVCMDQs */ + if (vintf_map & BIT_ULL(0)) { + tegra241_vintf0_handle_error(cmdqv->vintfs[0]); + vintf_map &= ~BIT_ULL(0); + } + + return IRQ_HANDLED; +} + +/* Command Queue Function */ + +static struct arm_smmu_cmdq * +tegra241_cmdqv_get_cmdq(struct arm_smmu_device *smmu) +{ + struct tegra241_cmdqv *cmdqv = + container_of(smmu, struct tegra241_cmdqv, smmu); + struct tegra241_vintf *vintf = cmdqv->vintfs[0]; + struct tegra241_vcmdq *vcmdq; + u16 lidx; + + if (READ_ONCE(bypass_vcmdq)) + return NULL; + + /* Use SMMU CMDQ if VINTF0 is uninitialized */ + if (!READ_ONCE(vintf->enabled)) + return NULL; + + /* + * Select a LVCMDQ to use. Here we use a temporal solution to + * balance out traffic on cmdq issuing: each cmdq has its own + * lock, if all cpus issue cmdlist using the same cmdq, only + * one CPU at a time can enter the process, while the others + * will be spinning at the same lock. + */ + lidx = smp_processor_id() % cmdqv->num_lvcmdqs_per_vintf; + vcmdq = vintf->lvcmdqs[lidx]; + if (!vcmdq || !READ_ONCE(vcmdq->enabled)) + return NULL; + return &vcmdq->cmdq; +} + +/* HW Reset Functions */ + +static void tegra241_vcmdq_hw_deinit(struct tegra241_vcmdq *vcmdq) +{ + char header[32], *h = lvcmdq_error_header(vcmdq, header, 32); + u32 gerrorn, gerror; + + if (vcmdq_write_config(vcmdq, 0)) { + dev_err(vcmdq->cmdqv->dev, + "%sGERRORN=0x%X, GERROR=0x%X, CONS=0x%X\n", h, + readl_relaxed(REG_VCMDQ_PAGE0(vcmdq, GERRORN)), + readl_relaxed(REG_VCMDQ_PAGE0(vcmdq, GERROR)), + readl_relaxed(REG_VCMDQ_PAGE0(vcmdq, CONS))); + } + writel_relaxed(0, REG_VCMDQ_PAGE0(vcmdq, PROD)); + writel_relaxed(0, REG_VCMDQ_PAGE0(vcmdq, CONS)); + writeq_relaxed(0, REG_VCMDQ_PAGE1(vcmdq, BASE)); + writeq_relaxed(0, REG_VCMDQ_PAGE1(vcmdq, CONS_INDX_BASE)); + + gerrorn = readl_relaxed(REG_VCMDQ_PAGE0(vcmdq, GERRORN)); + gerror = readl_relaxed(REG_VCMDQ_PAGE0(vcmdq, GERROR)); + if (gerror != gerrorn) { + dev_warn(vcmdq->cmdqv->dev, + "%suncleared error detected, resetting\n", h); + writel(gerror, REG_VCMDQ_PAGE0(vcmdq, GERRORN)); + } + + dev_dbg(vcmdq->cmdqv->dev, "%sdeinited\n", h); +} + +static int tegra241_vcmdq_hw_init(struct tegra241_vcmdq *vcmdq) +{ + char header[32], *h = lvcmdq_error_header(vcmdq, header, 32); + int ret; + + /* Reset VCMDQ */ + tegra241_vcmdq_hw_deinit(vcmdq); + + /* Configure and enable VCMDQ */ + writeq_relaxed(vcmdq->cmdq.q.q_base, REG_VCMDQ_PAGE1(vcmdq, BASE)); + + ret = vcmdq_write_config(vcmdq, VCMDQ_EN); + if (ret) { + dev_err(vcmdq->cmdqv->dev, + "%sGERRORN=0x%X, GERROR=0x%X, CONS=0x%X\n", h, + readl_relaxed(REG_VCMDQ_PAGE0(vcmdq, GERRORN)), + readl_relaxed(REG_VCMDQ_PAGE0(vcmdq, GERROR)), + readl_relaxed(REG_VCMDQ_PAGE0(vcmdq, CONS))); + return ret; + } + + dev_dbg(vcmdq->cmdqv->dev, "%sinited\n", h); + return 0; +} + +static void tegra241_vintf_hw_deinit(struct tegra241_vintf *vintf) +{ + u16 lidx; + + for (lidx = 0; lidx < vintf->cmdqv->num_lvcmdqs_per_vintf; lidx++) + if (vintf->lvcmdqs && vintf->lvcmdqs[lidx]) + tegra241_vcmdq_hw_deinit(vintf->lvcmdqs[lidx]); + vintf_write_config(vintf, 0); +} + +static int tegra241_vintf_hw_init(struct tegra241_vintf *vintf, bool hyp_own) +{ + u32 regval; + u16 lidx; + int ret; + + /* Reset VINTF */ + tegra241_vintf_hw_deinit(vintf); + + /* Configure and enable VINTF */ + regval = FIELD_PREP(VINTF_HYP_OWN, hyp_own); + writel(regval, REG_VINTF(vintf, CONFIG)); + + ret = vintf_write_config(vintf, regval | VINTF_EN); + if (ret) + return ret; + + for (lidx = 0; lidx < vintf->cmdqv->num_lvcmdqs_per_vintf; lidx++) { + if (vintf->lvcmdqs && vintf->lvcmdqs[lidx]) { + ret = tegra241_vcmdq_hw_init(vintf->lvcmdqs[lidx]); + if (ret) { + tegra241_vintf_hw_deinit(vintf); + return ret; + } + } + } + + return 0; +} + +static int tegra241_cmdqv_hw_reset(struct arm_smmu_device *smmu) +{ + struct tegra241_cmdqv *cmdqv = + container_of(smmu, struct tegra241_cmdqv, smmu); + u16 qidx, lidx, idx; + u32 regval; + int ret; + + /* Reset CMDQV */ + regval = readl_relaxed(REG_CMDQV(cmdqv, CONFIG)); + ret = cmdqv_write_config(cmdqv, regval & ~CMDQV_EN); + if (ret) + return ret; + ret = cmdqv_write_config(cmdqv, regval | CMDQV_EN); + if (ret) + return ret; + + /* Assign preallocated global VCMDQs to each VINTF as LVCMDQs */ + for (idx = 0, qidx = 0; idx < cmdqv->num_vintfs; idx++) { + for (lidx = 0; lidx < cmdqv->num_lvcmdqs_per_vintf; lidx++) { + regval = FIELD_PREP(CMDQV_CMDQ_ALLOC_VINTF, idx); + regval |= FIELD_PREP(CMDQV_CMDQ_ALLOC_LVCMDQ, lidx); + regval |= CMDQV_CMDQ_ALLOCATED; + writel_relaxed(regval, + REG_CMDQV(cmdqv, CMDQ_ALLOC(qidx++))); + } + } + + return tegra241_vintf_hw_init(cmdqv->vintfs[0], true); +} + +/* VCMDQ Resource Helpers */ + +static void tegra241_vcmdq_free_smmu_cmdq(struct tegra241_vcmdq *vcmdq) +{ + struct arm_smmu_queue *q = &vcmdq->cmdq.q; + size_t nents = 1 << q->llq.max_n_shift; + size_t qsz = nents << CMDQ_ENT_SZ_SHIFT; + + if (!q->base) + return; + dmam_free_coherent(vcmdq->cmdqv->smmu.dev, qsz, q->base, q->base_dma); +} + +static int tegra241_vcmdq_alloc_smmu_cmdq(struct tegra241_vcmdq *vcmdq) +{ + struct arm_smmu_device *smmu = &vcmdq->cmdqv->smmu; + struct arm_smmu_cmdq *cmdq = &vcmdq->cmdq; + struct arm_smmu_queue *q = &cmdq->q; + char name[16]; + int ret; + + snprintf(name, 16, "vcmdq%u", vcmdq->idx); + + q->llq.max_n_shift = VCMDQ_LOG2SIZE_MAX; + + /* Use the common helper to init the VCMDQ, and then... */ + ret = arm_smmu_init_one_queue(smmu, q, vcmdq->page0, + TEGRA241_VCMDQ_PROD, TEGRA241_VCMDQ_CONS, + CMDQ_ENT_DWORDS, name); + if (ret) + return ret; + + /* ...override q_base to write VCMDQ_BASE registers */ + q->q_base = q->base_dma & VCMDQ_ADDR; + q->q_base |= FIELD_PREP(VCMDQ_LOG2SIZE, q->llq.max_n_shift); + + return arm_smmu_cmdq_init(smmu, cmdq); +} + +/* VINTF Logical VCMDQ Resource Helpers */ + +static void tegra241_vintf_deinit_lvcmdq(struct tegra241_vintf *vintf, u16 lidx) +{ + vintf->lvcmdqs[lidx] = NULL; +} + +static int tegra241_vintf_init_lvcmdq(struct tegra241_vintf *vintf, u16 lidx, + struct tegra241_vcmdq *vcmdq) +{ + struct tegra241_cmdqv *cmdqv = vintf->cmdqv; + u16 idx = vintf->idx; + + vcmdq->idx = idx * cmdqv->num_lvcmdqs_per_vintf + lidx; + vcmdq->lidx = lidx; + vcmdq->cmdqv = cmdqv; + vcmdq->vintf = vintf; + vcmdq->page0 = cmdqv->base + TEGRA241_VINTFi_LVCMDQ_PAGE0(idx, lidx); + vcmdq->page1 = cmdqv->base + TEGRA241_VINTFi_LVCMDQ_PAGE1(idx, lidx); + + vintf->lvcmdqs[lidx] = vcmdq; + return 0; +} + +static void tegra241_vintf_free_lvcmdq(struct tegra241_vintf *vintf, u16 lidx) +{ + struct tegra241_vcmdq *vcmdq = vintf->lvcmdqs[lidx]; + char header[32]; + + tegra241_vcmdq_free_smmu_cmdq(vcmdq); + tegra241_vintf_deinit_lvcmdq(vintf, lidx); + + dev_dbg(vintf->cmdqv->dev, + "%sdeallocated\n", lvcmdq_error_header(vcmdq, header, 32)); + kfree(vcmdq); +} + +static struct tegra241_vcmdq * +tegra241_vintf_alloc_lvcmdq(struct tegra241_vintf *vintf, u16 lidx) +{ + struct tegra241_cmdqv *cmdqv = vintf->cmdqv; + struct tegra241_vcmdq *vcmdq; + char header[32]; + int ret; + + vcmdq = kzalloc(sizeof(*vcmdq), GFP_KERNEL); + if (!vcmdq) + return ERR_PTR(-ENOMEM); + + ret = tegra241_vintf_init_lvcmdq(vintf, lidx, vcmdq); + if (ret) + goto free_vcmdq; + + /* Build an arm_smmu_cmdq for each LVCMDQ */ + ret = tegra241_vcmdq_alloc_smmu_cmdq(vcmdq); + if (ret) + goto deinit_lvcmdq; + + dev_dbg(cmdqv->dev, + "%sallocated\n", lvcmdq_error_header(vcmdq, header, 32)); + return vcmdq; + +deinit_lvcmdq: + tegra241_vintf_deinit_lvcmdq(vintf, lidx); +free_vcmdq: + kfree(vcmdq); + return ERR_PTR(ret); +} + +/* VINTF Resource Helpers */ + +static void tegra241_cmdqv_deinit_vintf(struct tegra241_cmdqv *cmdqv, u16 idx) +{ + kfree(cmdqv->vintfs[idx]->lvcmdqs); + ida_free(&cmdqv->vintf_ids, idx); + cmdqv->vintfs[idx] = NULL; +} + +static int tegra241_cmdqv_init_vintf(struct tegra241_cmdqv *cmdqv, u16 max_idx, + struct tegra241_vintf *vintf) +{ + + u16 idx; + int ret; + + ret = ida_alloc_max(&cmdqv->vintf_ids, max_idx, GFP_KERNEL); + if (ret < 0) + return ret; + idx = ret; + + vintf->idx = idx; + vintf->cmdqv = cmdqv; + vintf->base = cmdqv->base + TEGRA241_VINTF(idx); + + vintf->lvcmdqs = kcalloc(cmdqv->num_lvcmdqs_per_vintf, + sizeof(*vintf->lvcmdqs), GFP_KERNEL); + if (!vintf->lvcmdqs) { + ida_free(&cmdqv->vintf_ids, idx); + return -ENOMEM; + } + + cmdqv->vintfs[idx] = vintf; + return ret; +} + +/* Remove Helpers */ + +static void tegra241_vintf_remove_lvcmdq(struct tegra241_vintf *vintf, u16 lidx) +{ + tegra241_vcmdq_hw_deinit(vintf->lvcmdqs[lidx]); + tegra241_vintf_free_lvcmdq(vintf, lidx); +} + +static void tegra241_cmdqv_remove_vintf(struct tegra241_cmdqv *cmdqv, u16 idx) +{ + struct tegra241_vintf *vintf = cmdqv->vintfs[idx]; + u16 lidx; + + /* Remove LVCMDQ resources */ + for (lidx = 0; lidx < vintf->cmdqv->num_lvcmdqs_per_vintf; lidx++) + if (vintf->lvcmdqs[lidx]) + tegra241_vintf_remove_lvcmdq(vintf, lidx); + + /* Remove VINTF resources */ + tegra241_vintf_hw_deinit(vintf); + + dev_dbg(cmdqv->dev, "VINTF%u: deallocated\n", vintf->idx); + tegra241_cmdqv_deinit_vintf(cmdqv, idx); + kfree(vintf); +} + +static void tegra241_cmdqv_remove(struct arm_smmu_device *smmu) +{ + struct tegra241_cmdqv *cmdqv = + container_of(smmu, struct tegra241_cmdqv, smmu); + u16 idx; + + /* Remove VINTF resources */ + for (idx = 0; idx < cmdqv->num_vintfs; idx++) { + if (cmdqv->vintfs[idx]) { + /* Only vintf0 should remain at this stage */ + WARN_ON(idx > 0); + tegra241_cmdqv_remove_vintf(cmdqv, idx); + } + } + + /* Remove cmdqv resources */ + ida_destroy(&cmdqv->vintf_ids); + + if (cmdqv->irq > 0) + free_irq(cmdqv->irq, cmdqv); + iounmap(cmdqv->base); + kfree(cmdqv->vintfs); + put_device(cmdqv->dev); /* smmu->impl_dev */ +} + +static struct arm_smmu_impl_ops tegra241_cmdqv_impl_ops = { + .get_secondary_cmdq = tegra241_cmdqv_get_cmdq, + .device_reset = tegra241_cmdqv_hw_reset, + .device_remove = tegra241_cmdqv_remove, +}; + +/* Probe Functions */ + +static int tegra241_cmdqv_acpi_is_memory(struct acpi_resource *res, void *data) +{ + struct resource_win win; + + return !acpi_dev_resource_address_space(res, &win); +} + +static int tegra241_cmdqv_acpi_get_irqs(struct acpi_resource *ares, void *data) +{ + struct resource r; + int *irq = data; + + if (*irq <= 0 && acpi_dev_resource_interrupt(ares, 0, &r)) + *irq = r.start; + return 1; /* No need to add resource to the list */ +} + +static struct resource * +tegra241_cmdqv_find_acpi_resource(struct device *dev, int *irq) +{ + struct acpi_device *adev = to_acpi_device(dev); + struct list_head resource_list; + struct resource_entry *rentry; + struct resource *res = NULL; + int ret; + + INIT_LIST_HEAD(&resource_list); + ret = acpi_dev_get_resources(adev, &resource_list, + tegra241_cmdqv_acpi_is_memory, NULL); + if (ret < 0) { + dev_err(dev, "failed to get memory resource: %d\n", ret); + return NULL; + } + + rentry = list_first_entry_or_null(&resource_list, + struct resource_entry, node); + if (!rentry) { + dev_err(dev, "failed to get memory resource entry\n"); + goto free_list; + } + + /* Caller must free the res */ + res = kzalloc(sizeof(*res), GFP_KERNEL); + if (!res) + goto free_list; + + *res = *rentry->res; + + acpi_dev_free_resource_list(&resource_list); + + INIT_LIST_HEAD(&resource_list); + + if (irq) + ret = acpi_dev_get_resources(adev, &resource_list, + tegra241_cmdqv_acpi_get_irqs, irq); + if (ret < 0 || !irq || *irq <= 0) + dev_warn(dev, "no interrupt. errors will not be reported\n"); + +free_list: + acpi_dev_free_resource_list(&resource_list); + return res; +} + +struct dentry *cmdqv_debugfs_dir; + +static struct arm_smmu_device * +__tegra241_cmdqv_probe(struct arm_smmu_device *smmu, struct resource *res, + int irq) +{ + static struct arm_smmu_device *new_smmu; + struct tegra241_cmdqv *cmdqv = NULL; + struct tegra241_vintf *vintf; + void __iomem *base; + u32 regval; + int lidx; + int ret; + + static_assert(offsetof(struct tegra241_cmdqv, smmu) == 0); + + base = ioremap(res->start, resource_size(res)); + if (IS_ERR(base)) { + dev_err(smmu->dev, "failed to ioremap: %ld\n", PTR_ERR(base)); + goto iounmap; + } + + regval = readl(base + TEGRA241_CMDQV_CONFIG); + if (disable_cmdqv) { + dev_info(smmu->dev, "Detected disable_cmdqv=true\n"); + writel(regval & ~CMDQV_EN, base + TEGRA241_CMDQV_CONFIG); + goto iounmap; + } + + cmdqv = devm_krealloc(smmu->dev, smmu, sizeof(*cmdqv), GFP_KERNEL); + if (!cmdqv) + goto iounmap; + new_smmu = &cmdqv->smmu; + + cmdqv->irq = irq; + cmdqv->base = base; + cmdqv->dev = smmu->impl_dev; + + if (cmdqv->irq > 0) { + ret = request_irq(irq, tegra241_cmdqv_isr, 0, "tegra241-cmdqv", + cmdqv); + if (ret) { + dev_err(cmdqv->dev, "failed to request irq (%d): %d\n", + cmdqv->irq, ret); + goto iounmap; + } + } + + regval = readl_relaxed(REG_CMDQV(cmdqv, PARAM)); + cmdqv->num_vintfs = 1 << FIELD_GET(CMDQV_NUM_VINTF_LOG2, regval); + cmdqv->num_vcmdqs = 1 << FIELD_GET(CMDQV_NUM_VCMDQ_LOG2, regval); + cmdqv->num_lvcmdqs_per_vintf = cmdqv->num_vcmdqs / cmdqv->num_vintfs; + + cmdqv->vintfs = + kcalloc(cmdqv->num_vintfs, sizeof(*cmdqv->vintfs), GFP_KERNEL); + if (!cmdqv->vintfs) + goto free_irq; + + ida_init(&cmdqv->vintf_ids); + + vintf = kzalloc(sizeof(*vintf), GFP_KERNEL); + if (!vintf) + goto destroy_ids; + + /* Init VINTF0 for in-kernel use */ + ret = tegra241_cmdqv_init_vintf(cmdqv, 0, vintf); + if (ret) { + dev_err(cmdqv->dev, "failed to init vintf0: %d\n", ret); + goto free_vintf; + } + + /* Preallocate logical VCMDQs to VINTF0 */ + for (lidx = 0; lidx < cmdqv->num_lvcmdqs_per_vintf; lidx++) { + struct tegra241_vcmdq *vcmdq; + + vcmdq = tegra241_vintf_alloc_lvcmdq(vintf, lidx); + if (IS_ERR(vcmdq)) + goto free_lvcmdq; + } + +#ifdef CONFIG_IOMMU_DEBUGFS + if (!cmdqv_debugfs_dir) { + cmdqv_debugfs_dir = + debugfs_create_dir("tegra241_cmdqv", iommu_debugfs_dir); + debugfs_create_bool("bypass_vcmdq", 0644, cmdqv_debugfs_dir, + &bypass_vcmdq); + } +#endif + + new_smmu->impl_ops = &tegra241_cmdqv_impl_ops; + + return new_smmu; + +free_lvcmdq: + for (lidx--; lidx >= 0; lidx--) + tegra241_vintf_free_lvcmdq(vintf, lidx); + tegra241_cmdqv_deinit_vintf(cmdqv, vintf->idx); +free_vintf: + kfree(vintf); +destroy_ids: + ida_destroy(&cmdqv->vintf_ids); + kfree(cmdqv->vintfs); +free_irq: + if (cmdqv->irq > 0) + free_irq(cmdqv->irq, cmdqv); +iounmap: + iounmap(base); + return NULL; +} + +struct arm_smmu_device *tegra241_cmdqv_probe(struct arm_smmu_device *smmu) +{ + struct arm_smmu_device *new_smmu; + struct resource *res = NULL; + int irq; + + if (!smmu->dev->of_node) + res = tegra241_cmdqv_find_acpi_resource(smmu->impl_dev, &irq); + if (!res) + goto out_fallback; + + new_smmu = __tegra241_cmdqv_probe(smmu, res, irq); + kfree(res); + + if (new_smmu) + return new_smmu; + +out_fallback: + dev_info(smmu->impl_dev, "Falling back to standard SMMU CMDQ\n"); + smmu->options &= ~ARM_SMMU_OPT_TEGRA241_CMDQV; + put_device(smmu->impl_dev); + return ERR_PTR(-ENODEV); +} From f59e854907128ec3d4a82b7fc4efe9be8da2e78e Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Thu, 29 Aug 2024 15:34:38 -0700 Subject: [PATCH 258/573] iommu/arm-smmu-v3: Start a new batch if new command is not supported The VCMDQ in the tegra241-cmdqv driver has a guest mode that supports only a few invalidation commands. A batch is initialized with a cmdq, so it has to confirm whether a new command is supported or not. Add a supports_cmd function pointer to the cmdq structure, where the vcmdq driver should hook a command scan function. Add an inline helper too so it can be used by both sides. If a new command is not supported, simply issue the existing batch and re- init it as a new batch. Signed-off-by: Nicolin Chen Link: https://lore.kernel.org/r/aafb24b881504f18c5d0c7c15f2134e40ad2c486.1724970714.git.nicolinc@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 6 ++++-- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index dee23ec352e7..1e67fd4fbcf3 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -923,10 +923,12 @@ static void arm_smmu_cmdq_batch_add(struct arm_smmu_device *smmu, struct arm_smmu_cmdq_batch *cmds, struct arm_smmu_cmdq_ent *cmd) { + bool unsupported_cmd = !arm_smmu_cmdq_supports_cmd(cmds->cmdq, cmd); + bool force_sync = (cmds->num == CMDQ_BATCH_ENTRIES - 1) && + (smmu->options & ARM_SMMU_OPT_CMDQ_FORCE_SYNC); int index; - if (cmds->num == CMDQ_BATCH_ENTRIES - 1 && - (smmu->options & ARM_SMMU_OPT_CMDQ_FORCE_SYNC)) { + if (force_sync || unsupported_cmd) { arm_smmu_cmdq_issue_cmdlist(smmu, cmds->cmdq, cmds->cmds, cmds->num, true); arm_smmu_cmdq_batch_init(smmu, cmds); diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 9fa22ee2383d..4deb40cfe2e1 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -568,8 +568,15 @@ struct arm_smmu_cmdq { atomic_long_t *valid_map; atomic_t owner_prod; atomic_t lock; + bool (*supports_cmd)(struct arm_smmu_cmdq_ent *ent); }; +static inline bool arm_smmu_cmdq_supports_cmd(struct arm_smmu_cmdq *cmdq, + struct arm_smmu_cmdq_ent *ent) +{ + return cmdq->supports_cmd ? cmdq->supports_cmd(ent) : true; +} + struct arm_smmu_cmdq_batch { u64 cmds[CMDQ_BATCH_ENTRIES * CMDQ_ENT_DWORDS]; struct arm_smmu_cmdq *cmdq; From a9d40285bdefef700ebc7551ef79d2f3e4559e73 Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Thu, 29 Aug 2024 15:34:39 -0700 Subject: [PATCH 259/573] iommu/tegra241-cmdqv: Limit CMDs for VCMDQs of a guest owned VINTF When VCMDQs are assigned to a VINTF owned by a guest (HYP_OWN bit unset), only TLB and ATC invalidation commands are supported by the VCMDQ HW. So, implement the new cmdq->supports_cmd op to scan the input cmd in order to make sure that it is supported by the selected queue. Note that the guest VM shouldn't have HYP_OWN bit being set regardless of guest kernel driver writing it or not, i.e. the hypervisor running in the host OS should wire this bit to zero when trapping a write access to this VINTF_CONFIG register from a guest kernel. Reviewed-by: Jason Gunthorpe Signed-off-by: Nicolin Chen Link: https://lore.kernel.org/r/8160292337059b91271045800e5c62f7295e2c24.1724970714.git.nicolinc@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 28 ++++++++------- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 3 +- .../iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 34 ++++++++++++++++++- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 1e67fd4fbcf3..0c28e2b5b723 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -346,12 +346,13 @@ static int arm_smmu_cmdq_build_cmd(u64 *cmd, struct arm_smmu_cmdq_ent *ent) return 0; } -static struct arm_smmu_cmdq *arm_smmu_get_cmdq(struct arm_smmu_device *smmu) +static struct arm_smmu_cmdq *arm_smmu_get_cmdq(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq_ent *ent) { struct arm_smmu_cmdq *cmdq = NULL; if (smmu->impl_ops && smmu->impl_ops->get_secondary_cmdq) - cmdq = smmu->impl_ops->get_secondary_cmdq(smmu); + cmdq = smmu->impl_ops->get_secondary_cmdq(smmu, ent); return cmdq ?: &smmu->cmdq; } @@ -897,7 +898,7 @@ static int __arm_smmu_cmdq_issue_cmd(struct arm_smmu_device *smmu, } return arm_smmu_cmdq_issue_cmdlist( - smmu, arm_smmu_get_cmdq(smmu), cmd, 1, sync); + smmu, arm_smmu_get_cmdq(smmu, ent), cmd, 1, sync); } static int arm_smmu_cmdq_issue_cmd(struct arm_smmu_device *smmu, @@ -913,10 +914,11 @@ static int arm_smmu_cmdq_issue_cmd_with_sync(struct arm_smmu_device *smmu, } static void arm_smmu_cmdq_batch_init(struct arm_smmu_device *smmu, - struct arm_smmu_cmdq_batch *cmds) + struct arm_smmu_cmdq_batch *cmds, + struct arm_smmu_cmdq_ent *ent) { cmds->num = 0; - cmds->cmdq = arm_smmu_get_cmdq(smmu); + cmds->cmdq = arm_smmu_get_cmdq(smmu, ent); } static void arm_smmu_cmdq_batch_add(struct arm_smmu_device *smmu, @@ -931,13 +933,13 @@ static void arm_smmu_cmdq_batch_add(struct arm_smmu_device *smmu, if (force_sync || unsupported_cmd) { arm_smmu_cmdq_issue_cmdlist(smmu, cmds->cmdq, cmds->cmds, cmds->num, true); - arm_smmu_cmdq_batch_init(smmu, cmds); + arm_smmu_cmdq_batch_init(smmu, cmds, cmd); } if (cmds->num == CMDQ_BATCH_ENTRIES) { arm_smmu_cmdq_issue_cmdlist(smmu, cmds->cmdq, cmds->cmds, cmds->num, false); - arm_smmu_cmdq_batch_init(smmu, cmds); + arm_smmu_cmdq_batch_init(smmu, cmds, cmd); } index = cmds->num * CMDQ_ENT_DWORDS; @@ -1205,7 +1207,7 @@ static void arm_smmu_sync_cd(struct arm_smmu_master *master, }, }; - arm_smmu_cmdq_batch_init(smmu, &cmds); + arm_smmu_cmdq_batch_init(smmu, &cmds, &cmd); for (i = 0; i < master->num_streams; i++) { cmd.cfgi.sid = master->streams[i].id; arm_smmu_cmdq_batch_add(smmu, &cmds, &cmd); @@ -2056,7 +2058,7 @@ static int arm_smmu_atc_inv_master(struct arm_smmu_master *master, arm_smmu_atc_inv_to_cmd(ssid, 0, 0, &cmd); - arm_smmu_cmdq_batch_init(master->smmu, &cmds); + arm_smmu_cmdq_batch_init(master->smmu, &cmds, &cmd); for (i = 0; i < master->num_streams; i++) { cmd.atc.sid = master->streams[i].id; arm_smmu_cmdq_batch_add(master->smmu, &cmds, &cmd); @@ -2071,7 +2073,9 @@ int arm_smmu_atc_inv_domain(struct arm_smmu_domain *smmu_domain, struct arm_smmu_master_domain *master_domain; int i; unsigned long flags; - struct arm_smmu_cmdq_ent cmd; + struct arm_smmu_cmdq_ent cmd = { + .opcode = CMDQ_OP_ATC_INV, + }; struct arm_smmu_cmdq_batch cmds; if (!(smmu_domain->smmu->features & ARM_SMMU_FEAT_ATS)) @@ -2094,7 +2098,7 @@ int arm_smmu_atc_inv_domain(struct arm_smmu_domain *smmu_domain, if (!atomic_read(&smmu_domain->nr_ats_masters)) return 0; - arm_smmu_cmdq_batch_init(smmu_domain->smmu, &cmds); + arm_smmu_cmdq_batch_init(smmu_domain->smmu, &cmds, &cmd); spin_lock_irqsave(&smmu_domain->devices_lock, flags); list_for_each_entry(master_domain, &smmu_domain->devices, @@ -2176,7 +2180,7 @@ static void __arm_smmu_tlb_inv_range(struct arm_smmu_cmdq_ent *cmd, num_pages++; } - arm_smmu_cmdq_batch_init(smmu, &cmds); + arm_smmu_cmdq_batch_init(smmu, &cmds, cmd); while (iova < end) { if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) { diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 4deb40cfe2e1..4d5af5ac8a63 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -642,7 +642,8 @@ struct arm_smmu_strtab_cfg { struct arm_smmu_impl_ops { int (*device_reset)(struct arm_smmu_device *smmu); void (*device_remove)(struct arm_smmu_device *smmu); - struct arm_smmu_cmdq *(*get_secondary_cmdq)(struct arm_smmu_device *smmu); + struct arm_smmu_cmdq *(*get_secondary_cmdq)( + struct arm_smmu_device *smmu, struct arm_smmu_cmdq_ent *ent); }; /* An SMMUv3 instance */ diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c index 5ac3032ee6dd..9eb9d959f3e5 100644 --- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c +++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c @@ -142,6 +142,7 @@ struct tegra241_vcmdq { * struct tegra241_vintf - Virtual Interface * @idx: Global index in the CMDQV * @enabled: Enable status + * @hyp_own: Owned by hypervisor (in-kernel) * @cmdqv: Parent CMDQV pointer * @lvcmdqs: List of logical VCMDQ pointers * @base: MMIO base address @@ -150,6 +151,7 @@ struct tegra241_vintf { u16 idx; bool enabled; + bool hyp_own; struct tegra241_cmdqv *cmdqv; struct tegra241_vcmdq **lvcmdqs; @@ -301,8 +303,21 @@ static irqreturn_t tegra241_cmdqv_isr(int irq, void *devid) /* Command Queue Function */ +static bool tegra241_guest_vcmdq_supports_cmd(struct arm_smmu_cmdq_ent *ent) +{ + switch (ent->opcode) { + case CMDQ_OP_TLBI_NH_ASID: + case CMDQ_OP_TLBI_NH_VA: + case CMDQ_OP_ATC_INV: + return true; + default: + return false; + } +} + static struct arm_smmu_cmdq * -tegra241_cmdqv_get_cmdq(struct arm_smmu_device *smmu) +tegra241_cmdqv_get_cmdq(struct arm_smmu_device *smmu, + struct arm_smmu_cmdq_ent *ent) { struct tegra241_cmdqv *cmdqv = container_of(smmu, struct tegra241_cmdqv, smmu); @@ -328,6 +343,10 @@ tegra241_cmdqv_get_cmdq(struct arm_smmu_device *smmu) vcmdq = vintf->lvcmdqs[lidx]; if (!vcmdq || !READ_ONCE(vcmdq->enabled)) return NULL; + + /* Unsupported CMD goes for smmu->cmdq pathway */ + if (!arm_smmu_cmdq_supports_cmd(&vcmdq->cmdq, ent)) + return NULL; return &vcmdq->cmdq; } @@ -406,12 +425,22 @@ static int tegra241_vintf_hw_init(struct tegra241_vintf *vintf, bool hyp_own) tegra241_vintf_hw_deinit(vintf); /* Configure and enable VINTF */ + /* + * Note that HYP_OWN bit is wired to zero when running in guest kernel, + * whether enabling it here or not, as !HYP_OWN cmdq HWs only support a + * restricted set of supported commands. + */ regval = FIELD_PREP(VINTF_HYP_OWN, hyp_own); writel(regval, REG_VINTF(vintf, CONFIG)); ret = vintf_write_config(vintf, regval | VINTF_EN); if (ret) return ret; + /* + * As being mentioned above, HYP_OWN bit is wired to zero for a guest + * kernel, so read it back from HW to ensure that reflects in hyp_own + */ + vintf->hyp_own = !!(VINTF_HYP_OWN & readl(REG_VINTF(vintf, CONFIG))); for (lidx = 0; lidx < vintf->cmdqv->num_lvcmdqs_per_vintf; lidx++) { if (vintf->lvcmdqs && vintf->lvcmdqs[lidx]) { @@ -493,6 +522,9 @@ static int tegra241_vcmdq_alloc_smmu_cmdq(struct tegra241_vcmdq *vcmdq) q->q_base = q->base_dma & VCMDQ_ADDR; q->q_base |= FIELD_PREP(VCMDQ_LOG2SIZE, q->llq.max_n_shift); + if (!vcmdq->vintf->hyp_own) + cmdq->supports_cmd = tegra241_guest_vcmdq_supports_cmd; + return arm_smmu_cmdq_init(smmu, cmdq); } From ce7cb08e22e09f43649b025c849a3ae3b80833c4 Mon Sep 17 00:00:00 2001 From: Mostafa Saleh Date: Fri, 30 Aug 2024 11:03:47 +0000 Subject: [PATCH 260/573] iommu/arm-smmu-v3: Match Stall behaviour for S2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to the spec (ARM IHI 0070 F.b), in "5.5 Fault configuration (A, R, S bits)": A STE with stage 2 translation enabled and STE.S2S == 0 is considered ILLEGAL if SMMU_IDR0.STALL_MODEL == 0b10. Also described in the pseudocode “SteIllegal()” if STE.Config == '11x' then [..] if eff_idr0_stall_model == '10' && STE.S2S == '0' then // stall_model forcing stall, but S2S == 0 return TRUE; Which means, S2S must be set when stall model is "ARM_SMMU_FEAT_STALL_FORCE", but currently the driver ignores that. Although, the driver can do the minimum and only set S2S for “ARM_SMMU_FEAT_STALL_FORCE”, it is more consistent to match S1 behaviour, which also sets it for “ARM_SMMU_FEAT_STALL” if the master has requested stalls. Also, since S2 stalls are enabled now, report them to the IOMMU layer and for VFIO devices it will fail anyway as VFIO doesn’t register an iopf handler. Signed-off-by: Mostafa Saleh Link: https://lore.kernel.org/r/20240830110349.797399-2-smostafa@google.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 8 +++----- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 0c28e2b5b723..b2de56dfceb9 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -1049,7 +1049,8 @@ void arm_smmu_get_ste_used(const __le64 *ent, __le64 *used_bits) used_bits[2] |= cpu_to_le64(STRTAB_STE_2_S2VMID | STRTAB_STE_2_VTCR | STRTAB_STE_2_S2AA64 | STRTAB_STE_2_S2ENDI | - STRTAB_STE_2_S2PTW | STRTAB_STE_2_S2R); + STRTAB_STE_2_S2PTW | STRTAB_STE_2_S2S | + STRTAB_STE_2_S2R); used_bits[3] |= cpu_to_le64(STRTAB_STE_3_S2TTB_MASK); } @@ -1683,6 +1684,7 @@ void arm_smmu_make_s2_domain_ste(struct arm_smmu_ste *target, STRTAB_STE_2_S2ENDI | #endif STRTAB_STE_2_S2PTW | + (master->stall_enabled ? STRTAB_STE_2_S2S : 0) | STRTAB_STE_2_S2R); target->data[3] = cpu_to_le64(pgtbl_cfg->arm_lpae_s2_cfg.vttbr & @@ -1776,10 +1778,6 @@ static int arm_smmu_handle_evt(struct arm_smmu_device *smmu, u64 *evt) return -EOPNOTSUPP; } - /* Stage-2 is always pinned at the moment */ - if (evt[1] & EVTQ_1_S2) - return -EFAULT; - if (!(evt[1] & EVTQ_1_STALL)) return -EOPNOTSUPP; diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 4d5af5ac8a63..e044ce5b5372 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -269,6 +269,7 @@ struct arm_smmu_ste { #define STRTAB_STE_2_S2AA64 (1UL << 51) #define STRTAB_STE_2_S2ENDI (1UL << 52) #define STRTAB_STE_2_S2PTW (1UL << 54) +#define STRTAB_STE_2_S2S (1UL << 57) #define STRTAB_STE_2_S2R (1UL << 58) #define STRTAB_STE_3_S2TTB_MASK GENMASK_ULL(51, 4) From 070e326f327a8d32669b5bf9b50a12c2cd8277ff Mon Sep 17 00:00:00 2001 From: Mostafa Saleh Date: Fri, 30 Aug 2024 11:03:48 +0000 Subject: [PATCH 261/573] iommu/arm-smmu-v3-test: Test masters with stall enabled At the moment, the SMMUv3 unit tests assume ATS is always enabled, although this is sufficient to test hitless/non-hitless transitions, but exercising other features is useful to check ste/cd population logic (for example the .get_used logic). Add an enum where bits define features per-master, at the moment there is only ATS and STALLs which are mutually exclusive, but this would make it easier to extend with other features in the future. Also, Add 2 more tests for s1 <-> s2 transitions with stalls enabled. Signed-off-by: Mostafa Saleh Link: https://lore.kernel.org/r/20240830110349.797399-3-smostafa@google.com Signed-off-by: Will Deacon --- .../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 83 ++++++++++++++----- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c index cceb737a7001..84baa021370a 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c @@ -30,6 +30,11 @@ static struct mm_struct sva_mm = { .pgd = (void *)0xdaedbeefdeadbeefULL, }; +enum arm_smmu_test_master_feat { + ARM_SMMU_MASTER_TEST_ATS = BIT(0), + ARM_SMMU_MASTER_TEST_STALL = BIT(1), +}; + static bool arm_smmu_entry_differs_in_used_bits(const __le64 *entry, const __le64 *used_bits, const __le64 *target, @@ -164,16 +169,22 @@ static const dma_addr_t fake_cdtab_dma_addr = 0xF0F0F0F0F0F0; static void arm_smmu_test_make_cdtable_ste(struct arm_smmu_ste *ste, unsigned int s1dss, - const dma_addr_t dma_addr) + const dma_addr_t dma_addr, + enum arm_smmu_test_master_feat feat) { + bool ats_enabled = feat & ARM_SMMU_MASTER_TEST_ATS; + bool stall_enabled = feat & ARM_SMMU_MASTER_TEST_STALL; + struct arm_smmu_master master = { + .ats_enabled = ats_enabled, .cd_table.cdtab_dma = dma_addr, .cd_table.s1cdmax = 0xFF, .cd_table.s1fmt = STRTAB_STE_0_S1FMT_64K_L2, .smmu = &smmu, + .stall_enabled = stall_enabled, }; - arm_smmu_make_cdtable_ste(ste, &master, true, s1dss); + arm_smmu_make_cdtable_ste(ste, &master, ats_enabled, s1dss); } static void arm_smmu_v3_write_ste_test_bypass_to_abort(struct kunit *test) @@ -204,7 +215,7 @@ static void arm_smmu_v3_write_ste_test_cdtable_to_abort(struct kunit *test) struct arm_smmu_ste ste; arm_smmu_test_make_cdtable_ste(&ste, STRTAB_STE_1_S1DSS_SSID0, - fake_cdtab_dma_addr); + fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_v3_test_ste_expect_hitless_transition(test, &ste, &abort_ste, NUM_EXPECTED_SYNCS(2)); } @@ -214,7 +225,7 @@ static void arm_smmu_v3_write_ste_test_abort_to_cdtable(struct kunit *test) struct arm_smmu_ste ste; arm_smmu_test_make_cdtable_ste(&ste, STRTAB_STE_1_S1DSS_SSID0, - fake_cdtab_dma_addr); + fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_v3_test_ste_expect_hitless_transition(test, &abort_ste, &ste, NUM_EXPECTED_SYNCS(2)); } @@ -224,7 +235,7 @@ static void arm_smmu_v3_write_ste_test_cdtable_to_bypass(struct kunit *test) struct arm_smmu_ste ste; arm_smmu_test_make_cdtable_ste(&ste, STRTAB_STE_1_S1DSS_SSID0, - fake_cdtab_dma_addr); + fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_v3_test_ste_expect_hitless_transition(test, &ste, &bypass_ste, NUM_EXPECTED_SYNCS(3)); } @@ -234,7 +245,7 @@ static void arm_smmu_v3_write_ste_test_bypass_to_cdtable(struct kunit *test) struct arm_smmu_ste ste; arm_smmu_test_make_cdtable_ste(&ste, STRTAB_STE_1_S1DSS_SSID0, - fake_cdtab_dma_addr); + fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_v3_test_ste_expect_hitless_transition(test, &bypass_ste, &ste, NUM_EXPECTED_SYNCS(3)); } @@ -245,9 +256,9 @@ static void arm_smmu_v3_write_ste_test_cdtable_s1dss_change(struct kunit *test) struct arm_smmu_ste s1dss_bypass; arm_smmu_test_make_cdtable_ste(&ste, STRTAB_STE_1_S1DSS_SSID0, - fake_cdtab_dma_addr); + fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_test_make_cdtable_ste(&s1dss_bypass, STRTAB_STE_1_S1DSS_BYPASS, - fake_cdtab_dma_addr); + fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS); /* * Flipping s1dss on a CD table STE only involves changes to the second @@ -265,7 +276,7 @@ arm_smmu_v3_write_ste_test_s1dssbypass_to_stebypass(struct kunit *test) struct arm_smmu_ste s1dss_bypass; arm_smmu_test_make_cdtable_ste(&s1dss_bypass, STRTAB_STE_1_S1DSS_BYPASS, - fake_cdtab_dma_addr); + fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_v3_test_ste_expect_hitless_transition( test, &s1dss_bypass, &bypass_ste, NUM_EXPECTED_SYNCS(2)); } @@ -276,16 +287,20 @@ arm_smmu_v3_write_ste_test_stebypass_to_s1dssbypass(struct kunit *test) struct arm_smmu_ste s1dss_bypass; arm_smmu_test_make_cdtable_ste(&s1dss_bypass, STRTAB_STE_1_S1DSS_BYPASS, - fake_cdtab_dma_addr); + fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_v3_test_ste_expect_hitless_transition( test, &bypass_ste, &s1dss_bypass, NUM_EXPECTED_SYNCS(2)); } static void arm_smmu_test_make_s2_ste(struct arm_smmu_ste *ste, - bool ats_enabled) + enum arm_smmu_test_master_feat feat) { + bool ats_enabled = feat & ARM_SMMU_MASTER_TEST_ATS; + bool stall_enabled = feat & ARM_SMMU_MASTER_TEST_STALL; struct arm_smmu_master master = { + .ats_enabled = ats_enabled, .smmu = &smmu, + .stall_enabled = stall_enabled, }; struct io_pgtable io_pgtable = {}; struct arm_smmu_domain smmu_domain = { @@ -308,7 +323,7 @@ static void arm_smmu_v3_write_ste_test_s2_to_abort(struct kunit *test) { struct arm_smmu_ste ste; - arm_smmu_test_make_s2_ste(&ste, true); + arm_smmu_test_make_s2_ste(&ste, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_v3_test_ste_expect_hitless_transition(test, &ste, &abort_ste, NUM_EXPECTED_SYNCS(2)); } @@ -317,7 +332,7 @@ static void arm_smmu_v3_write_ste_test_abort_to_s2(struct kunit *test) { struct arm_smmu_ste ste; - arm_smmu_test_make_s2_ste(&ste, true); + arm_smmu_test_make_s2_ste(&ste, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_v3_test_ste_expect_hitless_transition(test, &abort_ste, &ste, NUM_EXPECTED_SYNCS(2)); } @@ -326,7 +341,7 @@ static void arm_smmu_v3_write_ste_test_s2_to_bypass(struct kunit *test) { struct arm_smmu_ste ste; - arm_smmu_test_make_s2_ste(&ste, true); + arm_smmu_test_make_s2_ste(&ste, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_v3_test_ste_expect_hitless_transition(test, &ste, &bypass_ste, NUM_EXPECTED_SYNCS(2)); } @@ -335,7 +350,7 @@ static void arm_smmu_v3_write_ste_test_bypass_to_s2(struct kunit *test) { struct arm_smmu_ste ste; - arm_smmu_test_make_s2_ste(&ste, true); + arm_smmu_test_make_s2_ste(&ste, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_v3_test_ste_expect_hitless_transition(test, &bypass_ste, &ste, NUM_EXPECTED_SYNCS(2)); } @@ -346,8 +361,8 @@ static void arm_smmu_v3_write_ste_test_s1_to_s2(struct kunit *test) struct arm_smmu_ste s2_ste; arm_smmu_test_make_cdtable_ste(&s1_ste, STRTAB_STE_1_S1DSS_SSID0, - fake_cdtab_dma_addr); - arm_smmu_test_make_s2_ste(&s2_ste, true); + fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS); + arm_smmu_test_make_s2_ste(&s2_ste, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_v3_test_ste_expect_hitless_transition(test, &s1_ste, &s2_ste, NUM_EXPECTED_SYNCS(3)); } @@ -358,8 +373,8 @@ static void arm_smmu_v3_write_ste_test_s2_to_s1(struct kunit *test) struct arm_smmu_ste s2_ste; arm_smmu_test_make_cdtable_ste(&s1_ste, STRTAB_STE_1_S1DSS_SSID0, - fake_cdtab_dma_addr); - arm_smmu_test_make_s2_ste(&s2_ste, true); + fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS); + arm_smmu_test_make_s2_ste(&s2_ste, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_v3_test_ste_expect_hitless_transition(test, &s2_ste, &s1_ste, NUM_EXPECTED_SYNCS(3)); } @@ -375,9 +390,9 @@ static void arm_smmu_v3_write_ste_test_non_hitless(struct kunit *test) * s1 dss field in the same update. */ arm_smmu_test_make_cdtable_ste(&ste, STRTAB_STE_1_S1DSS_SSID0, - fake_cdtab_dma_addr); + fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_test_make_cdtable_ste(&ste_2, STRTAB_STE_1_S1DSS_BYPASS, - 0x4B4B4b4B4B); + 0x4B4B4b4B4B, ARM_SMMU_MASTER_TEST_ATS); arm_smmu_v3_test_ste_expect_non_hitless_transition( test, &ste, &ste_2, NUM_EXPECTED_SYNCS(3)); } @@ -503,6 +518,30 @@ static void arm_smmu_test_make_sva_release_cd(struct arm_smmu_cd *cd, arm_smmu_make_sva_cd(cd, &master, NULL, asid); } +static void arm_smmu_v3_write_ste_test_s1_to_s2_stall(struct kunit *test) +{ + struct arm_smmu_ste s1_ste; + struct arm_smmu_ste s2_ste; + + arm_smmu_test_make_cdtable_ste(&s1_ste, STRTAB_STE_1_S1DSS_SSID0, + fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_STALL); + arm_smmu_test_make_s2_ste(&s2_ste, ARM_SMMU_MASTER_TEST_STALL); + arm_smmu_v3_test_ste_expect_hitless_transition(test, &s1_ste, &s2_ste, + NUM_EXPECTED_SYNCS(3)); +} + +static void arm_smmu_v3_write_ste_test_s2_to_s1_stall(struct kunit *test) +{ + struct arm_smmu_ste s1_ste; + struct arm_smmu_ste s2_ste; + + arm_smmu_test_make_cdtable_ste(&s1_ste, STRTAB_STE_1_S1DSS_SSID0, + fake_cdtab_dma_addr, ARM_SMMU_MASTER_TEST_STALL); + arm_smmu_test_make_s2_ste(&s2_ste, ARM_SMMU_MASTER_TEST_STALL); + arm_smmu_v3_test_ste_expect_hitless_transition(test, &s2_ste, &s1_ste, + NUM_EXPECTED_SYNCS(3)); +} + static void arm_smmu_v3_write_cd_test_sva_clear(struct kunit *test) { struct arm_smmu_cd cd = {}; @@ -547,6 +586,8 @@ static struct kunit_case arm_smmu_v3_test_cases[] = { KUNIT_CASE(arm_smmu_v3_write_ste_test_non_hitless), KUNIT_CASE(arm_smmu_v3_write_cd_test_s1_clear), KUNIT_CASE(arm_smmu_v3_write_cd_test_s1_change_asid), + KUNIT_CASE(arm_smmu_v3_write_ste_test_s1_to_s2_stall), + KUNIT_CASE(arm_smmu_v3_write_ste_test_s2_to_s1_stall), KUNIT_CASE(arm_smmu_v3_write_cd_test_sva_clear), KUNIT_CASE(arm_smmu_v3_write_cd_test_sva_release), {}, From 9e60bb811f582a13a1f8346675f270db7e48404c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 23 Jul 2024 13:21:12 -0700 Subject: [PATCH 262/573] hwmon: (ina2xx) Reorder include files to alphabetic order Simplify driver maintenance by reordering include files to alphabetic order. Whule at it, drop unnecessary / unused jiffies.h. No functional change. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 9ab4205622e2..a6a619a85eb6 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -22,19 +22,18 @@ * Thanks to Jan Volkering */ -#include -#include -#include +#include #include -#include -#include #include #include -#include +#include +#include +#include +#include #include -#include -#include #include +#include +#include #include From 61a4a8414e1c6331a5a2fcfd235e6252c985f9d7 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 23 Jul 2024 13:23:30 -0700 Subject: [PATCH 263/573] hwmon: (ina2xx) Replace platform data with device properties There are no in-tree users of ina2xx platform data. Drop it and support device properties instead as alternative if it should ever be needed. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index a6a619a85eb6..897657f8d685 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -30,13 +30,11 @@ #include #include #include -#include +#include #include #include #include -#include - /* common register definitions */ #define INA2XX_CONFIG 0x00 #define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */ @@ -643,14 +641,8 @@ static int ina2xx_probe(struct i2c_client *client) data->config = &ina2xx_config[chip]; mutex_init(&data->config_lock); - if (of_property_read_u32(dev->of_node, "shunt-resistor", &val) < 0) { - struct ina2xx_platform_data *pdata = dev_get_platdata(dev); - - if (pdata) - val = pdata->shunt_uohms; - else - val = INA2XX_RSHUNT_DEFAULT; - } + if (device_property_read_u32(dev, "shunt-resistor", &val) < 0) + val = INA2XX_RSHUNT_DEFAULT; ina2xx_set_shunt(data, val); @@ -667,7 +659,7 @@ static int ina2xx_probe(struct i2c_client *client) return dev_err_probe(dev, ret, "failed to enable vs regulator\n"); if (chip == ina226) { - if (of_property_read_bool(dev->of_node, "ti,alert-polarity-active-high")) { + if (device_property_read_bool(dev, "ti,alert-polarity-active-high")) { ret = ina2xx_set_alert_polarity(data, INA226_ALERT_POL_HIGH); if (ret < 0) { From 232177a37b90d76d3616f28fb47636d81c02fe8b Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 23 Jul 2024 13:25:31 -0700 Subject: [PATCH 264/573] hwmon: (ina2xx) Use bit operations Use bit operations where possible to make the code more generic and to align it with other drivers. Also use compile time conversion from bit to mask to reduce runtime overhead. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 84 +++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 897657f8d685..1b4170d02c94 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -22,6 +22,8 @@ * Thanks to Jan Volkering */ +#include +#include #include #include #include @@ -65,25 +67,23 @@ #define INA2XX_RSHUNT_DEFAULT 10000 /* bit mask for reading the averaging setting in the configuration register */ -#define INA226_AVG_RD_MASK 0x0E00 +#define INA226_AVG_RD_MASK GENMASK(11, 9) -#define INA226_READ_AVG(reg) (((reg) & INA226_AVG_RD_MASK) >> 9) -#define INA226_SHIFT_AVG(val) ((val) << 9) +#define INA226_READ_AVG(reg) FIELD_GET(INA226_AVG_RD_MASK, reg) -#define INA226_ALERT_POLARITY_MASK 0x0002 -#define INA226_SHIFT_ALERT_POLARITY(val) ((val) << 1) -#define INA226_ALERT_POL_LOW 0 -#define INA226_ALERT_POL_HIGH 1 +#define INA226_ALERT_POLARITY_MASK BIT(1) +#define INA226_ALERT_POL_LOW 0 +#define INA226_ALERT_POL_HIGH 1 /* bit number of alert functions in Mask/Enable Register */ -#define INA226_SHUNT_OVER_VOLTAGE_BIT 15 -#define INA226_SHUNT_UNDER_VOLTAGE_BIT 14 -#define INA226_BUS_OVER_VOLTAGE_BIT 13 -#define INA226_BUS_UNDER_VOLTAGE_BIT 12 -#define INA226_POWER_OVER_LIMIT_BIT 11 +#define INA226_SHUNT_OVER_VOLTAGE_MASK BIT(15) +#define INA226_SHUNT_UNDER_VOLTAGE_MASK BIT(14) +#define INA226_BUS_OVER_VOLTAGE_MASK BIT(13) +#define INA226_BUS_UNDER_VOLTAGE_MASK BIT(12) +#define INA226_POWER_OVER_LIMIT_MASK BIT(11) /* bit mask for alert config bits of Mask/Enable Register */ -#define INA226_ALERT_CONFIG_MASK 0xFC00 +#define INA226_ALERT_CONFIG_MASK GENMASK(15, 10) #define INA226_ALERT_FUNCTION_FLAG BIT(4) /* common attrs, ina226 attrs and NULL */ @@ -177,7 +177,7 @@ static u16 ina226_interval_to_reg(int interval) avg_bits = find_closest(avg, ina226_avg_tab, ARRAY_SIZE(ina226_avg_tab)); - return INA226_SHIFT_AVG(avg_bits); + return FIELD_PREP(INA226_AVG_RD_MASK, avg_bits); } static int ina2xx_set_alert_polarity(struct ina2xx_data *data, @@ -185,7 +185,7 @@ static int ina2xx_set_alert_polarity(struct ina2xx_data *data, { return regmap_update_bits(data->regmap, INA226_MASK_ENABLE, INA226_ALERT_POLARITY_MASK, - INA226_SHIFT_ALERT_POLARITY(val)); + FIELD_PREP(INA226_ALERT_POLARITY_MASK, val)); } /* @@ -322,20 +322,20 @@ static ssize_t ina2xx_value_show(struct device *dev, return sysfs_emit(buf, "%d\n", ina2xx_get_value(data, attr->index, regval)); } -static int ina226_reg_to_alert(struct ina2xx_data *data, u8 bit, u16 regval) +static int ina226_reg_to_alert(struct ina2xx_data *data, u32 mask, u16 regval) { int reg; - switch (bit) { - case INA226_SHUNT_OVER_VOLTAGE_BIT: - case INA226_SHUNT_UNDER_VOLTAGE_BIT: + switch (mask) { + case INA226_SHUNT_OVER_VOLTAGE_MASK: + case INA226_SHUNT_UNDER_VOLTAGE_MASK: reg = INA2XX_SHUNT_VOLTAGE; break; - case INA226_BUS_OVER_VOLTAGE_BIT: - case INA226_BUS_UNDER_VOLTAGE_BIT: + case INA226_BUS_OVER_VOLTAGE_MASK: + case INA226_BUS_UNDER_VOLTAGE_MASK: reg = INA2XX_BUS_VOLTAGE; break; - case INA226_POWER_OVER_LIMIT_BIT: + case INA226_POWER_OVER_LIMIT_MASK: reg = INA2XX_POWER; break; default: @@ -351,19 +351,19 @@ static int ina226_reg_to_alert(struct ina2xx_data *data, u8 bit, u16 regval) * Turns alert limit values into register values. * Opposite of the formula in ina2xx_get_value(). */ -static s16 ina226_alert_to_reg(struct ina2xx_data *data, u8 bit, int val) +static s16 ina226_alert_to_reg(struct ina2xx_data *data, u32 mask, int val) { - switch (bit) { - case INA226_SHUNT_OVER_VOLTAGE_BIT: - case INA226_SHUNT_UNDER_VOLTAGE_BIT: + switch (mask) { + case INA226_SHUNT_OVER_VOLTAGE_MASK: + case INA226_SHUNT_UNDER_VOLTAGE_MASK: val *= data->config->shunt_div; return clamp_val(val, SHRT_MIN, SHRT_MAX); - case INA226_BUS_OVER_VOLTAGE_BIT: - case INA226_BUS_UNDER_VOLTAGE_BIT: + case INA226_BUS_OVER_VOLTAGE_MASK: + case INA226_BUS_UNDER_VOLTAGE_MASK: val = (val * 1000) << data->config->bus_voltage_shift; val = DIV_ROUND_CLOSEST(val, data->config->bus_voltage_lsb); return clamp_val(val, 0, SHRT_MAX); - case INA226_POWER_OVER_LIMIT_BIT: + case INA226_POWER_OVER_LIMIT_MASK: val = DIV_ROUND_CLOSEST(val, data->power_lsb_uW); return clamp_val(val, 0, USHRT_MAX); default: @@ -387,7 +387,7 @@ static ssize_t ina226_alert_show(struct device *dev, if (ret) goto abort; - if (regval & BIT(attr->index)) { + if (regval & attr->index) { ret = regmap_read(data->regmap, INA226_ALERT_LIMIT, ®val); if (ret) goto abort; @@ -432,7 +432,7 @@ static ssize_t ina226_alert_store(struct device *dev, if (val != 0) { ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE, INA226_ALERT_CONFIG_MASK, - BIT(attr->index)); + attr->index); if (ret < 0) goto abort; } @@ -456,7 +456,7 @@ static ssize_t ina226_alarm_show(struct device *dev, if (ret) return ret; - alarm = (regval & BIT(attr->index)) && + alarm = (regval & attr->index) && (regval & INA226_ALERT_FUNCTION_FLAG); return sysfs_emit(buf, "%d\n", alarm); } @@ -552,25 +552,25 @@ static ssize_t ina226_interval_show(struct device *dev, static SENSOR_DEVICE_ATTR_RO(in0_input, ina2xx_value, INA2XX_SHUNT_VOLTAGE); /* shunt voltage over/under voltage alert setting and alarm */ static SENSOR_DEVICE_ATTR_RW(in0_crit, ina226_alert, - INA226_SHUNT_OVER_VOLTAGE_BIT); + INA226_SHUNT_OVER_VOLTAGE_MASK); static SENSOR_DEVICE_ATTR_RW(in0_lcrit, ina226_alert, - INA226_SHUNT_UNDER_VOLTAGE_BIT); + INA226_SHUNT_UNDER_VOLTAGE_MASK); static SENSOR_DEVICE_ATTR_RO(in0_crit_alarm, ina226_alarm, - INA226_SHUNT_OVER_VOLTAGE_BIT); + INA226_SHUNT_OVER_VOLTAGE_MASK); static SENSOR_DEVICE_ATTR_RO(in0_lcrit_alarm, ina226_alarm, - INA226_SHUNT_UNDER_VOLTAGE_BIT); + INA226_SHUNT_UNDER_VOLTAGE_MASK); /* bus voltage */ static SENSOR_DEVICE_ATTR_RO(in1_input, ina2xx_value, INA2XX_BUS_VOLTAGE); /* bus voltage over/under voltage alert setting and alarm */ static SENSOR_DEVICE_ATTR_RW(in1_crit, ina226_alert, - INA226_BUS_OVER_VOLTAGE_BIT); + INA226_BUS_OVER_VOLTAGE_MASK); static SENSOR_DEVICE_ATTR_RW(in1_lcrit, ina226_alert, - INA226_BUS_UNDER_VOLTAGE_BIT); + INA226_BUS_UNDER_VOLTAGE_MASK); static SENSOR_DEVICE_ATTR_RO(in1_crit_alarm, ina226_alarm, - INA226_BUS_OVER_VOLTAGE_BIT); + INA226_BUS_OVER_VOLTAGE_MASK); static SENSOR_DEVICE_ATTR_RO(in1_lcrit_alarm, ina226_alarm, - INA226_BUS_UNDER_VOLTAGE_BIT); + INA226_BUS_UNDER_VOLTAGE_MASK); /* calculated current */ static SENSOR_DEVICE_ATTR_RO(curr1_input, ina2xx_value, INA2XX_CURRENT); @@ -579,9 +579,9 @@ static SENSOR_DEVICE_ATTR_RO(curr1_input, ina2xx_value, INA2XX_CURRENT); static SENSOR_DEVICE_ATTR_RO(power1_input, ina2xx_value, INA2XX_POWER); /* over-limit power alert setting and alarm */ static SENSOR_DEVICE_ATTR_RW(power1_crit, ina226_alert, - INA226_POWER_OVER_LIMIT_BIT); + INA226_POWER_OVER_LIMIT_MASK); static SENSOR_DEVICE_ATTR_RO(power1_crit_alarm, ina226_alarm, - INA226_POWER_OVER_LIMIT_BIT); + INA226_POWER_OVER_LIMIT_MASK); /* shunt resistance */ static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ina2xx_shunt, INA2XX_CALIBRATION); From 2bb476524c61f43e6e89aeacaee6599ce5dd3505 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 1 Aug 2024 16:43:03 -0700 Subject: [PATCH 265/573] hwmon: (ina2xx) Mark regmap_config as const Recent versions of checkpatch complain that struct regmap_config should be declared as const. WARNING: struct regmap_config should normally be const Doing so reveals a potential problem in the driver: If both supported chips are present in a single system, the maximum number of registers may race when devices are instantiated since max_registers is updated in the probe function. Solve the problem by setting .max_registers to the maximum register address of all supported chips. This does not make a practical difference while fixing the potential race condition and reducing code complexity. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 1b4170d02c94..9d93190874d7 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -50,10 +50,6 @@ #define INA226_ALERT_LIMIT 0x07 #define INA226_DIE_ID 0xFF -/* register count */ -#define INA219_REGISTERS 6 -#define INA226_REGISTERS 8 - #define INA2XX_MAX_REGISTERS 8 /* settings - depend on use case */ @@ -95,9 +91,10 @@ */ #define INA226_TOTAL_CONV_TIME_DEFAULT 2200 -static struct regmap_config ina2xx_regmap_config = { +static const struct regmap_config ina2xx_regmap_config = { .reg_bits = 8, .val_bits = 16, + .max_register = INA2XX_MAX_REGISTERS, }; enum ina2xx_ids { ina219, ina226 }; @@ -105,7 +102,6 @@ enum ina2xx_ids { ina219, ina226 }; struct ina2xx_config { u16 config_default; int calibration_value; - int registers; int shunt_div; int bus_voltage_shift; int bus_voltage_lsb; /* uV */ @@ -128,7 +124,6 @@ static const struct ina2xx_config ina2xx_config[] = { [ina219] = { .config_default = INA219_CONFIG_DEFAULT, .calibration_value = 4096, - .registers = INA219_REGISTERS, .shunt_div = 100, .bus_voltage_shift = 3, .bus_voltage_lsb = 4000, @@ -137,7 +132,6 @@ static const struct ina2xx_config ina2xx_config[] = { [ina226] = { .config_default = INA226_CONFIG_DEFAULT, .calibration_value = 2048, - .registers = INA226_REGISTERS, .shunt_div = 400, .bus_voltage_shift = 0, .bus_voltage_lsb = 1250, @@ -646,8 +640,6 @@ static int ina2xx_probe(struct i2c_client *client) ina2xx_set_shunt(data, val); - ina2xx_regmap_config.max_register = data->config->registers; - data->regmap = devm_regmap_init_i2c(client, &ina2xx_regmap_config); if (IS_ERR(data->regmap)) { dev_err(dev, "failed to allocate register map\n"); From d491e781b0600487be9f85977deb5f833d15db56 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 1 Aug 2024 14:57:39 -0700 Subject: [PATCH 266/573] hwmon: (ina2xx) Use local regmap pointer if used more than once If regmap is accessed more than once in a function, declare and used local regmap variable. While at it, drop low value debug messages. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 9d93190874d7..ed8764a29d3f 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -210,18 +210,14 @@ static int ina2xx_init(struct ina2xx_data *data) static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval) { struct ina2xx_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; int ret, retry; - dev_dbg(dev, "Starting register %d read\n", reg); - for (retry = 5; retry; retry--) { - - ret = regmap_read(data->regmap, reg, regval); + ret = regmap_read(regmap, reg, regval); if (ret < 0) return ret; - dev_dbg(dev, "read %d, val = 0x%04x\n", reg, *regval); - /* * If the current value in the calibration register is 0, the * power and current registers will also remain at 0. In case @@ -233,8 +229,7 @@ static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval) if (*regval == 0) { unsigned int cal; - ret = regmap_read(data->regmap, INA2XX_CALIBRATION, - &cal); + ret = regmap_read(regmap, INA2XX_CALIBRATION, &cal); if (ret < 0) return ret; @@ -372,17 +367,18 @@ static ssize_t ina226_alert_show(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(da); struct ina2xx_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; int regval; int val = 0; int ret; mutex_lock(&data->config_lock); - ret = regmap_read(data->regmap, INA226_MASK_ENABLE, ®val); + ret = regmap_read(regmap, INA226_MASK_ENABLE, ®val); if (ret) goto abort; if (regval & attr->index) { - ret = regmap_read(data->regmap, INA226_ALERT_LIMIT, ®val); + ret = regmap_read(regmap, INA226_ALERT_LIMIT, ®val); if (ret) goto abort; val = ina226_reg_to_alert(data, attr->index, regval); @@ -400,6 +396,7 @@ static ssize_t ina226_alert_store(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(da); struct ina2xx_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; unsigned long val; int ret; @@ -413,18 +410,18 @@ static ssize_t ina226_alert_store(struct device *dev, * if the value is non-zero. */ mutex_lock(&data->config_lock); - ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE, + ret = regmap_update_bits(regmap, INA226_MASK_ENABLE, INA226_ALERT_CONFIG_MASK, 0); if (ret < 0) goto abort; - ret = regmap_write(data->regmap, INA226_ALERT_LIMIT, + ret = regmap_write(regmap, INA226_ALERT_LIMIT, ina226_alert_to_reg(data, attr->index, val)); if (ret < 0) goto abort; if (val != 0) { - ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE, + ret = regmap_update_bits(regmap, INA226_MASK_ENABLE, INA226_ALERT_CONFIG_MASK, attr->index); if (ret < 0) From bb25cdc2bff408ee3be1f20bd2cee4ea8b79c2d6 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 23 Jul 2024 16:04:29 -0700 Subject: [PATCH 267/573] hwmon: (ina2xx) Re-initialize chip using regmap functions If it is necessary to re-initialize the chip, for example because it has been power cycled, use regmap functions to update register contents. This ensures that all registers, including the configuration register and alert registers, are updated to previously configured values without having to locally cache everything. For this to work, volatile registers have to be marked as volatile. Also, the cache needs to be bypassed when reading the calibration and mask_enable registers. While the calibration register is not volatile, it will be reset to 0 if the chip has been power cycled. Most of the bits in the mask_enable register are configuration bits, except for bit 4 which reports if an alert has been observed. Both registers need to be marked as non-volatile to be updated after a power cycle, but it is necessary to bypass the cache when reading them to detect if the chip has been power cycled and to read the alert status. The chip does not support register auto-increments. It is therefore necessary to configure regmap to use single register read/write operations. Otherwise regmap tries to write all registers in a single operation when synchronizing register contents with the hardware, and the synchronization fails. Another necessary change is to declare ina226_alert_to_reg() as u16. So far it returned an s16 which is sign extended to a large negative value which is then sent to regmap as unsigned int, causing an -EINVAL error return. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 50 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index ed8764a29d3f..db6432523e59 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -91,10 +91,41 @@ */ #define INA226_TOTAL_CONV_TIME_DEFAULT 2200 +static bool ina2xx_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case INA2XX_CONFIG: + case INA2XX_CALIBRATION: + case INA226_MASK_ENABLE: + case INA226_ALERT_LIMIT: + return true; + default: + return false; + } +} + +static bool ina2xx_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case INA2XX_SHUNT_VOLTAGE: + case INA2XX_BUS_VOLTAGE: + case INA2XX_POWER: + case INA2XX_CURRENT: + return true; + default: + return false; + } +} + static const struct regmap_config ina2xx_regmap_config = { .reg_bits = 8, .val_bits = 16, + .use_single_write = true, + .use_single_read = true, .max_register = INA2XX_MAX_REGISTERS, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = ina2xx_volatile_reg, + .writeable_reg = ina2xx_writeable_reg, }; enum ina2xx_ids { ina219, ina226 }; @@ -229,16 +260,16 @@ static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval) if (*regval == 0) { unsigned int cal; - ret = regmap_read(regmap, INA2XX_CALIBRATION, &cal); + ret = regmap_read_bypassed(regmap, INA2XX_CALIBRATION, &cal); if (ret < 0) return ret; if (cal == 0) { dev_warn(dev, "chip not calibrated, reinitializing\n"); - ret = ina2xx_init(data); - if (ret < 0) - return ret; + regcache_mark_dirty(regmap); + regcache_sync(regmap); + /* * Let's make sure the power and current * registers have been updated before trying @@ -340,7 +371,7 @@ static int ina226_reg_to_alert(struct ina2xx_data *data, u32 mask, u16 regval) * Turns alert limit values into register values. * Opposite of the formula in ina2xx_get_value(). */ -static s16 ina226_alert_to_reg(struct ina2xx_data *data, u32 mask, int val) +static u16 ina226_alert_to_reg(struct ina2xx_data *data, u32 mask, int val) { switch (mask) { case INA226_SHUNT_OVER_VOLTAGE_MASK: @@ -439,16 +470,17 @@ static ssize_t ina226_alarm_show(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(da); struct ina2xx_data *data = dev_get_drvdata(dev); - int regval; + unsigned int mask; int alarm = 0; int ret; - ret = regmap_read(data->regmap, INA226_MASK_ENABLE, ®val); + ret = regmap_read_bypassed(data->regmap, INA226_MASK_ENABLE, &mask); if (ret) return ret; - alarm = (regval & attr->index) && - (regval & INA226_ALERT_FUNCTION_FLAG); + alarm = (mask & attr->index) && + (mask & INA226_ALERT_FUNCTION_FLAG); + return sysfs_emit(buf, "%d\n", alarm); } From ab7fbee452beca56b7c570d49190e679863362d5 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 24 Jul 2024 09:42:16 -0700 Subject: [PATCH 268/573] hwmon: (ina2xx) Fix various overflow issues Module tests show various overflow problems when writing limits and other attributes. in0_crit: Suspected overflow: [max=82, read 0, written 2147483648] in0_lcrit: Suspected overflow: [max=82, read 0, written 2147483648] in1_crit: Suspected overflow: [max=40959, read 0, written 2147483647] in1_lcrit: Suspected overflow: [max=40959, read 0, written 2147483647] power1_crit: Suspected overflow: [max=134218750, read 0, written 2147483648] update_interval: Suspected overflow: [max=2253, read 2, written 2147483647] Implement missing clamping on attribute write operations to avoid those problems. While at it, check in the probe function if the shunt resistor value passed from devicetree is valid, and bail out if it isn't. Also limit mutex use to the code calling ina2xx_set_shunt() since it isn't needed when called from the probe function. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index db6432523e59..2aea461b8c6d 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -193,10 +193,16 @@ static int ina226_reg_to_interval(u16 config) * Return the new, shifted AVG field value of CONFIG register, * to use with regmap_update_bits */ -static u16 ina226_interval_to_reg(int interval) +static u16 ina226_interval_to_reg(unsigned long interval) { int avg, avg_bits; + /* + * The maximum supported interval is 1,024 * (2 * 8.244ms) ~= 16.8s. + * Clamp to 32 seconds before calculations to avoid overflows. + */ + interval = clamp_val(interval, 0, 32000); + avg = DIV_ROUND_CLOSEST(interval * 1000, INA226_TOTAL_CONV_TIME_DEFAULT); avg_bits = find_closest(avg, ina226_avg_tab, @@ -371,19 +377,22 @@ static int ina226_reg_to_alert(struct ina2xx_data *data, u32 mask, u16 regval) * Turns alert limit values into register values. * Opposite of the formula in ina2xx_get_value(). */ -static u16 ina226_alert_to_reg(struct ina2xx_data *data, u32 mask, int val) +static u16 ina226_alert_to_reg(struct ina2xx_data *data, u32 mask, unsigned long val) { switch (mask) { case INA226_SHUNT_OVER_VOLTAGE_MASK: case INA226_SHUNT_UNDER_VOLTAGE_MASK: + val = clamp_val(val, 0, SHRT_MAX * data->config->shunt_div); val *= data->config->shunt_div; - return clamp_val(val, SHRT_MIN, SHRT_MAX); + return clamp_val(val, 0, SHRT_MAX); case INA226_BUS_OVER_VOLTAGE_MASK: case INA226_BUS_UNDER_VOLTAGE_MASK: + val = clamp_val(val, 0, 200000); val = (val * 1000) << data->config->bus_voltage_shift; val = DIV_ROUND_CLOSEST(val, data->config->bus_voltage_lsb); - return clamp_val(val, 0, SHRT_MAX); + return clamp_val(val, 0, USHRT_MAX); case INA226_POWER_OVER_LIMIT_MASK: + val = clamp_val(val, 0, UINT_MAX - data->power_lsb_uW); val = DIV_ROUND_CLOSEST(val, data->power_lsb_uW); return clamp_val(val, 0, USHRT_MAX); default: @@ -490,19 +499,17 @@ static ssize_t ina226_alarm_show(struct device *dev, * to shunt_voltage_lsb = 1 / shunt_div multiplied by 10^9 in order * to keep the scale. */ -static int ina2xx_set_shunt(struct ina2xx_data *data, long val) +static int ina2xx_set_shunt(struct ina2xx_data *data, unsigned long val) { unsigned int dividend = DIV_ROUND_CLOSEST(1000000000, data->config->shunt_div); - if (val <= 0 || val > dividend) + if (!val || val > dividend) return -EINVAL; - mutex_lock(&data->config_lock); data->rshunt = val; data->current_lsb_uA = DIV_ROUND_CLOSEST(dividend, val); data->power_lsb_uW = data->config->power_lsb_factor * data->current_lsb_uA; - mutex_unlock(&data->config_lock); return 0; } @@ -527,7 +534,9 @@ static ssize_t ina2xx_shunt_store(struct device *dev, if (status < 0) return status; + mutex_lock(&data->config_lock); status = ina2xx_set_shunt(data, val); + mutex_unlock(&data->config_lock); if (status < 0) return status; return count; @@ -545,9 +554,6 @@ static ssize_t ina226_interval_store(struct device *dev, if (status < 0) return status; - if (val > INT_MAX || val == 0) - return -EINVAL; - status = regmap_update_bits(data->regmap, INA2XX_CONFIG, INA226_AVG_RD_MASK, ina226_interval_to_reg(val)); @@ -667,7 +673,9 @@ static int ina2xx_probe(struct i2c_client *client) if (device_property_read_u32(dev, "shunt-resistor", &val) < 0) val = INA2XX_RSHUNT_DEFAULT; - ina2xx_set_shunt(data, val); + ret = ina2xx_set_shunt(data, val); + if (ret < 0) + return dev_err_probe(dev, ret, "Invalid shunt resistor value\n"); data->regmap = devm_regmap_init_i2c(client, &ina2xx_regmap_config); if (IS_ERR(data->regmap)) { From 51c6fa3246cd6f12e3194795b0a934c1aa8f9d4f Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 24 Jul 2024 09:31:08 -0700 Subject: [PATCH 269/573] hwmon: (ina2xx) Consolidate chip initialization code Move all chip initialization code into a single function. No functional change. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 111 ++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 68 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 2aea461b8c6d..a9e3b23445b4 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -67,9 +67,7 @@ #define INA226_READ_AVG(reg) FIELD_GET(INA226_AVG_RD_MASK, reg) -#define INA226_ALERT_POLARITY_MASK BIT(1) -#define INA226_ALERT_POL_LOW 0 -#define INA226_ALERT_POL_HIGH 1 +#define INA226_ALERT_POLARITY BIT(1) /* bit number of alert functions in Mask/Enable Register */ #define INA226_SHUNT_OVER_VOLTAGE_MASK BIT(15) @@ -141,6 +139,7 @@ struct ina2xx_config { struct ina2xx_data { const struct ina2xx_config *config; + enum ina2xx_ids chip; long rshunt; long current_lsb_uA; @@ -211,39 +210,6 @@ static u16 ina226_interval_to_reg(unsigned long interval) return FIELD_PREP(INA226_AVG_RD_MASK, avg_bits); } -static int ina2xx_set_alert_polarity(struct ina2xx_data *data, - unsigned long val) -{ - return regmap_update_bits(data->regmap, INA226_MASK_ENABLE, - INA226_ALERT_POLARITY_MASK, - FIELD_PREP(INA226_ALERT_POLARITY_MASK, val)); -} - -/* - * Calibration register is set to the best value, which eliminates - * truncation errors on calculating current register in hardware. - * According to datasheet (eq. 3) the best values are 2048 for - * ina226 and 4096 for ina219. They are hardcoded as calibration_value. - */ -static int ina2xx_calibrate(struct ina2xx_data *data) -{ - return regmap_write(data->regmap, INA2XX_CALIBRATION, - data->config->calibration_value); -} - -/* - * Initialize the configuration and calibration registers. - */ -static int ina2xx_init(struct ina2xx_data *data) -{ - int ret = regmap_write(data->regmap, INA2XX_CONFIG, - data->config->config_default); - if (ret < 0) - return ret; - - return ina2xx_calibrate(data); -} - static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval) { struct ina2xx_data *data = dev_get_drvdata(dev); @@ -651,12 +617,48 @@ static const struct attribute_group ina226_group = { .attrs = ina226_attrs, }; +/* + * Initialize chip + */ +static int ina2xx_init(struct device *dev, struct ina2xx_data *data) +{ + struct regmap *regmap = data->regmap; + u32 shunt; + int ret; + + if (device_property_read_u32(dev, "shunt-resistor", &shunt) < 0) + shunt = INA2XX_RSHUNT_DEFAULT; + + ret = ina2xx_set_shunt(data, shunt); + if (ret < 0) + return ret; + + ret = regmap_write(regmap, INA2XX_CONFIG, data->config->config_default); + if (ret < 0) + return ret; + + if (data->chip == ina226) { + bool active_high = device_property_read_bool(dev, "ti,alert-polarity-active-high"); + + regmap_update_bits(regmap, INA226_MASK_ENABLE, INA226_ALERT_POLARITY, + FIELD_PREP(INA226_ALERT_POLARITY, active_high)); + } + + /* + * Calibration register is set to the best value, which eliminates + * truncation errors on calculating current register in hardware. + * According to datasheet (eq. 3) the best values are 2048 for + * ina226 and 4096 for ina219. They are hardcoded as calibration_value. + */ + return regmap_write(regmap, INA2XX_CALIBRATION, + data->config->calibration_value); +} + static int ina2xx_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct ina2xx_data *data; struct device *hwmon_dev; - u32 val; int ret, group = 0; enum ina2xx_ids chip; @@ -668,15 +670,9 @@ static int ina2xx_probe(struct i2c_client *client) /* set the device type */ data->config = &ina2xx_config[chip]; + data->chip = chip; mutex_init(&data->config_lock); - if (device_property_read_u32(dev, "shunt-resistor", &val) < 0) - val = INA2XX_RSHUNT_DEFAULT; - - ret = ina2xx_set_shunt(data, val); - if (ret < 0) - return dev_err_probe(dev, ret, "Invalid shunt resistor value\n"); - data->regmap = devm_regmap_init_i2c(client, &ina2xx_regmap_config); if (IS_ERR(data->regmap)) { dev_err(dev, "failed to allocate register map\n"); @@ -687,30 +683,9 @@ static int ina2xx_probe(struct i2c_client *client) if (ret) return dev_err_probe(dev, ret, "failed to enable vs regulator\n"); - if (chip == ina226) { - if (device_property_read_bool(dev, "ti,alert-polarity-active-high")) { - ret = ina2xx_set_alert_polarity(data, - INA226_ALERT_POL_HIGH); - if (ret < 0) { - return dev_err_probe(dev, ret, - "failed to set alert polarity active high\n"); - } - } else { - /* Set default value i.e active low */ - ret = ina2xx_set_alert_polarity(data, - INA226_ALERT_POL_LOW); - if (ret < 0) { - return dev_err_probe(dev, ret, - "failed to set alert polarity active low\n"); - } - } - } - - ret = ina2xx_init(data); - if (ret < 0) { - dev_err(dev, "error configuring the device: %d\n", ret); - return -ENODEV; - } + ret = ina2xx_init(dev, data); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to configure device\n"); data->groups[group++] = &ina2xx_group; if (chip == ina226) From aa7d17636640233062075c22afbb3e25fc5e5a91 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 23 Jul 2024 16:17:02 -0700 Subject: [PATCH 270/573] hwmon: (ina2xx) Set alert latch Alerts should only be cleared after reported, not immediately after the alert condition has been cleared. Set the latch enable bit to keep alerts latched until the alert register has been read from the chip. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index a9e3b23445b4..98338b7e7437 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -67,6 +67,7 @@ #define INA226_READ_AVG(reg) FIELD_GET(INA226_AVG_RD_MASK, reg) +#define INA226_ALERT_LATCH_ENABLE BIT(0) #define INA226_ALERT_POLARITY BIT(1) /* bit number of alert functions in Mask/Enable Register */ @@ -640,8 +641,10 @@ static int ina2xx_init(struct device *dev, struct ina2xx_data *data) if (data->chip == ina226) { bool active_high = device_property_read_bool(dev, "ti,alert-polarity-active-high"); - regmap_update_bits(regmap, INA226_MASK_ENABLE, INA226_ALERT_POLARITY, - FIELD_PREP(INA226_ALERT_POLARITY, active_high)); + regmap_update_bits(regmap, INA226_MASK_ENABLE, + INA226_ALERT_LATCH_ENABLE | INA226_ALERT_POLARITY, + INA226_ALERT_LATCH_ENABLE | + FIELD_PREP(INA226_ALERT_POLARITY, active_high)); } /* From c263d9166765567433f759b6435a3255cfdd4620 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 1 Aug 2024 15:34:48 -0700 Subject: [PATCH 271/573] hwmon: (ina2xx) Move ina2xx_get_value() ina2xx_get_value() will be needed earlier in the next patch, so move it. No functional change. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 72 +++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 98338b7e7437..fe850ee06024 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -211,6 +211,42 @@ static u16 ina226_interval_to_reg(unsigned long interval) return FIELD_PREP(INA226_AVG_RD_MASK, avg_bits); } +static int ina2xx_get_value(struct ina2xx_data *data, u8 reg, + unsigned int regval) +{ + int val; + + switch (reg) { + case INA2XX_SHUNT_VOLTAGE: + /* signed register */ + val = DIV_ROUND_CLOSEST((s16)regval, data->config->shunt_div); + break; + case INA2XX_BUS_VOLTAGE: + val = (regval >> data->config->bus_voltage_shift) * + data->config->bus_voltage_lsb; + val = DIV_ROUND_CLOSEST(val, 1000); + break; + case INA2XX_POWER: + val = regval * data->power_lsb_uW; + break; + case INA2XX_CURRENT: + /* signed register, result in mA */ + val = (s16)regval * data->current_lsb_uA; + val = DIV_ROUND_CLOSEST(val, 1000); + break; + case INA2XX_CALIBRATION: + val = regval; + break; + default: + /* programmer goofed */ + WARN_ON_ONCE(1); + val = 0; + break; + } + + return val; +} + static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval) { struct ina2xx_data *data = dev_get_drvdata(dev); @@ -264,42 +300,6 @@ static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval) return -ENODEV; } -static int ina2xx_get_value(struct ina2xx_data *data, u8 reg, - unsigned int regval) -{ - int val; - - switch (reg) { - case INA2XX_SHUNT_VOLTAGE: - /* signed register */ - val = DIV_ROUND_CLOSEST((s16)regval, data->config->shunt_div); - break; - case INA2XX_BUS_VOLTAGE: - val = (regval >> data->config->bus_voltage_shift) - * data->config->bus_voltage_lsb; - val = DIV_ROUND_CLOSEST(val, 1000); - break; - case INA2XX_POWER: - val = regval * data->power_lsb_uW; - break; - case INA2XX_CURRENT: - /* signed register, result in mA */ - val = (s16)regval * data->current_lsb_uA; - val = DIV_ROUND_CLOSEST(val, 1000); - break; - case INA2XX_CALIBRATION: - val = regval; - break; - default: - /* programmer goofed */ - WARN_ON_ONCE(1); - val = 0; - break; - } - - return val; -} - static ssize_t ina2xx_value_show(struct device *dev, struct device_attribute *da, char *buf) { From 814db9f1b8ec1cad9fa707c52c695550cbb66b80 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 24 Jul 2024 22:41:16 -0700 Subject: [PATCH 272/573] hwmon: (ina2xx) Convert to use with_info hwmon API Convert driver to use the with_info hardware monitoring API to reduce its dependency on sysfs attribute functions. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 495 ++++++++++++++++++++++++----------------- 1 file changed, 293 insertions(+), 202 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index fe850ee06024..339d41dfa10e 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -25,9 +25,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -35,6 +35,7 @@ #include #include #include +#include #include /* common register definitions */ @@ -81,9 +82,6 @@ #define INA226_ALERT_CONFIG_MASK GENMASK(15, 10) #define INA226_ALERT_FUNCTION_FLAG BIT(4) -/* common attrs, ina226 attrs and NULL */ -#define INA2XX_MAX_ATTRIBUTE_GROUPS 3 - /* * Both bus voltage and shunt voltage conversion times for ina226 are set * to 0b0100 on POR, which translates to 2200 microseconds in total. @@ -147,8 +145,6 @@ struct ina2xx_data { long power_lsb_uW; struct mutex config_lock; struct regmap *regmap; - - const struct attribute_group *groups[INA2XX_MAX_ATTRIBUTE_GROUPS]; }; static const struct ina2xx_config ina2xx_config[] = { @@ -193,7 +189,7 @@ static int ina226_reg_to_interval(u16 config) * Return the new, shifted AVG field value of CONFIG register, * to use with regmap_update_bits */ -static u16 ina226_interval_to_reg(unsigned long interval) +static u16 ina226_interval_to_reg(long interval) { int avg, avg_bits; @@ -247,14 +243,19 @@ static int ina2xx_get_value(struct ina2xx_data *data, u8 reg, return val; } -static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval) +/* + * Read and convert register value from chip. If the register value is 0, + * check if the chip has been power cycled or reset. If so, re-initialize it. + */ +static int ina2xx_read_init(struct device *dev, int reg, long *val) { struct ina2xx_data *data = dev_get_drvdata(dev); struct regmap *regmap = data->regmap; + unsigned int regval; int ret, retry; for (retry = 5; retry; retry--) { - ret = regmap_read(regmap, reg, regval); + ret = regmap_read(regmap, reg, ®val); if (ret < 0) return ret; @@ -266,7 +267,7 @@ static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval) * We do that extra read of the calibration register if there * is some hint of a chip reset. */ - if (*regval == 0) { + if (regval == 0) { unsigned int cal; ret = regmap_read_bypassed(regmap, INA2XX_CALIBRATION, &cal); @@ -288,6 +289,7 @@ static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval) continue; } } + *val = ina2xx_get_value(data, reg, regval); return 0; } @@ -300,46 +302,6 @@ static int ina2xx_read_reg(struct device *dev, int reg, unsigned int *regval) return -ENODEV; } -static ssize_t ina2xx_value_show(struct device *dev, - struct device_attribute *da, char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct ina2xx_data *data = dev_get_drvdata(dev); - unsigned int regval; - - int err = ina2xx_read_reg(dev, attr->index, ®val); - - if (err < 0) - return err; - - return sysfs_emit(buf, "%d\n", ina2xx_get_value(data, attr->index, regval)); -} - -static int ina226_reg_to_alert(struct ina2xx_data *data, u32 mask, u16 regval) -{ - int reg; - - switch (mask) { - case INA226_SHUNT_OVER_VOLTAGE_MASK: - case INA226_SHUNT_UNDER_VOLTAGE_MASK: - reg = INA2XX_SHUNT_VOLTAGE; - break; - case INA226_BUS_OVER_VOLTAGE_MASK: - case INA226_BUS_UNDER_VOLTAGE_MASK: - reg = INA2XX_BUS_VOLTAGE; - break; - case INA226_POWER_OVER_LIMIT_MASK: - reg = INA2XX_POWER; - break; - default: - /* programmer goofed */ - WARN_ON_ONCE(1); - return 0; - } - - return ina2xx_get_value(data, reg, regval); -} - /* * Turns alert limit values into register values. * Opposite of the formula in ina2xx_get_value(). @@ -369,14 +331,10 @@ static u16 ina226_alert_to_reg(struct ina2xx_data *data, u32 mask, unsigned long } } -static ssize_t ina226_alert_show(struct device *dev, - struct device_attribute *da, char *buf) +static int ina226_alert_limit_read(struct ina2xx_data *data, u32 mask, int reg, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct ina2xx_data *data = dev_get_drvdata(dev); struct regmap *regmap = data->regmap; int regval; - int val = 0; int ret; mutex_lock(&data->config_lock); @@ -384,32 +342,26 @@ static ssize_t ina226_alert_show(struct device *dev, if (ret) goto abort; - if (regval & attr->index) { + if (regval & mask) { ret = regmap_read(regmap, INA226_ALERT_LIMIT, ®val); if (ret) goto abort; - val = ina226_reg_to_alert(data, attr->index, regval); + *val = ina2xx_get_value(data, reg, regval); + } else { + *val = 0; } - - ret = sysfs_emit(buf, "%d\n", val); abort: mutex_unlock(&data->config_lock); return ret; } -static ssize_t ina226_alert_store(struct device *dev, - struct device_attribute *da, - const char *buf, size_t count) +static int ina226_alert_limit_write(struct ina2xx_data *data, u32 mask, long val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct ina2xx_data *data = dev_get_drvdata(dev); struct regmap *regmap = data->regmap; - unsigned long val; int ret; - ret = kstrtoul(buf, 10, &val); - if (ret < 0) - return ret; + if (val < 0) + return -EINVAL; /* * Clear all alerts first to avoid accidentally triggering ALERT pin @@ -423,43 +375,285 @@ static ssize_t ina226_alert_store(struct device *dev, goto abort; ret = regmap_write(regmap, INA226_ALERT_LIMIT, - ina226_alert_to_reg(data, attr->index, val)); + ina226_alert_to_reg(data, mask, val)); if (ret < 0) goto abort; - if (val != 0) { + if (val) ret = regmap_update_bits(regmap, INA226_MASK_ENABLE, - INA226_ALERT_CONFIG_MASK, - attr->index); - if (ret < 0) - goto abort; - } - - ret = count; + INA226_ALERT_CONFIG_MASK, mask); abort: mutex_unlock(&data->config_lock); return ret; } -static ssize_t ina226_alarm_show(struct device *dev, - struct device_attribute *da, char *buf) +static int ina2xx_chip_read(struct device *dev, u32 attr, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); struct ina2xx_data *data = dev_get_drvdata(dev); - unsigned int mask; - int alarm = 0; + u32 regval; int ret; - ret = regmap_read_bypassed(data->regmap, INA226_MASK_ENABLE, &mask); + switch (attr) { + case hwmon_chip_update_interval: + ret = regmap_read(data->regmap, INA2XX_CONFIG, ®val); + if (ret) + return ret; + + *val = ina226_reg_to_interval(regval); + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int ina226_alert_read(struct regmap *regmap, u32 mask, long *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read_bypassed(regmap, INA226_MASK_ENABLE, ®val); if (ret) return ret; - alarm = (mask & attr->index) && - (mask & INA226_ALERT_FUNCTION_FLAG); + *val = (regval & mask) && (regval & INA226_ALERT_FUNCTION_FLAG); - return sysfs_emit(buf, "%d\n", alarm); + return 0; } +static int ina2xx_in_read(struct device *dev, u32 attr, int channel, long *val) +{ + int voltage_reg = channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE; + u32 under_voltage_mask = channel ? INA226_BUS_UNDER_VOLTAGE_MASK + : INA226_SHUNT_UNDER_VOLTAGE_MASK; + u32 over_voltage_mask = channel ? INA226_BUS_OVER_VOLTAGE_MASK + : INA226_SHUNT_OVER_VOLTAGE_MASK; + struct ina2xx_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + unsigned int regval; + int ret; + + switch (attr) { + case hwmon_in_input: + ret = regmap_read(regmap, voltage_reg, ®val); + if (ret) + return ret; + *val = ina2xx_get_value(data, voltage_reg, regval); + break; + case hwmon_in_lcrit: + return ina226_alert_limit_read(data, under_voltage_mask, + voltage_reg, val); + case hwmon_in_crit: + return ina226_alert_limit_read(data, over_voltage_mask, + voltage_reg, val); + case hwmon_in_lcrit_alarm: + return ina226_alert_read(regmap, under_voltage_mask, val); + case hwmon_in_crit_alarm: + return ina226_alert_read(regmap, over_voltage_mask, val); + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int ina2xx_power_read(struct device *dev, u32 attr, long *val) +{ + struct ina2xx_data *data = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_power_input: + return ina2xx_read_init(dev, INA2XX_POWER, val); + case hwmon_power_crit: + return ina226_alert_limit_read(data, INA226_POWER_OVER_LIMIT_MASK, + INA2XX_POWER, val); + case hwmon_power_crit_alarm: + return ina226_alert_read(data->regmap, INA226_POWER_OVER_LIMIT_MASK, val); + default: + return -EOPNOTSUPP; + } +} + +static int ina2xx_curr_read(struct device *dev, u32 attr, long *val) +{ + switch (attr) { + case hwmon_curr_input: + return ina2xx_read_init(dev, INA2XX_CURRENT, val); + default: + return -EOPNOTSUPP; + } +} + +static int ina2xx_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + return ina2xx_chip_read(dev, attr, val); + case hwmon_in: + return ina2xx_in_read(dev, attr, channel, val); + case hwmon_power: + return ina2xx_power_read(dev, attr, val); + case hwmon_curr: + return ina2xx_curr_read(dev, attr, val); + default: + return -EOPNOTSUPP; + } +} + +static int ina2xx_chip_write(struct device *dev, u32 attr, long val) +{ + struct ina2xx_data *data = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_chip_update_interval: + return regmap_update_bits(data->regmap, INA2XX_CONFIG, + INA226_AVG_RD_MASK, + ina226_interval_to_reg(val)); + default: + return -EOPNOTSUPP; + } +} + +static int ina2xx_in_write(struct device *dev, u32 attr, int channel, long val) +{ + struct ina2xx_data *data = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_in_lcrit: + return ina226_alert_limit_write(data, + channel ? INA226_BUS_UNDER_VOLTAGE_MASK : INA226_SHUNT_UNDER_VOLTAGE_MASK, + val); + case hwmon_in_crit: + return ina226_alert_limit_write(data, + channel ? INA226_BUS_OVER_VOLTAGE_MASK : INA226_SHUNT_OVER_VOLTAGE_MASK, + val); + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int ina2xx_power_write(struct device *dev, u32 attr, long val) +{ + struct ina2xx_data *data = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_power_crit: + return ina226_alert_limit_write(data, INA226_POWER_OVER_LIMIT_MASK, val); + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int ina2xx_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_chip: + return ina2xx_chip_write(dev, attr, val); + case hwmon_in: + return ina2xx_in_write(dev, attr, channel, val); + case hwmon_power: + return ina2xx_power_write(dev, attr, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t ina2xx_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct ina2xx_data *data = _data; + enum ina2xx_ids chip = data->chip; + + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + return 0444; + case hwmon_in_lcrit: + case hwmon_in_crit: + if (chip == ina226) + return 0644; + break; + case hwmon_in_lcrit_alarm: + case hwmon_in_crit_alarm: + if (chip == ina226) + return 0444; + break; + default: + break; + } + break; + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + return 0444; + default: + break; + } + break; + case hwmon_power: + switch (attr) { + case hwmon_power_input: + return 0444; + case hwmon_power_crit: + if (chip == ina226) + return 0644; + break; + case hwmon_power_crit_alarm: + if (chip == ina226) + return 0444; + break; + default: + break; + } + break; + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + if (chip == ina226) + return 0644; + break; + default: + break; + } + break; + default: + break; + } + return 0; +} + +static const struct hwmon_channel_info * const ina2xx_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_CRIT | HWMON_I_CRIT_ALARM | + HWMON_I_LCRIT | HWMON_I_LCRIT_ALARM, + HWMON_I_INPUT | HWMON_I_CRIT | HWMON_I_CRIT_ALARM | + HWMON_I_LCRIT | HWMON_I_LCRIT_ALARM + ), + HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT), + HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | HWMON_P_CRIT | HWMON_P_CRIT_ALARM), + NULL +}; + +static const struct hwmon_ops ina2xx_hwmon_ops = { + .is_visible = ina2xx_is_visible, + .read = ina2xx_read, + .write = ina2xx_write, +}; + +static const struct hwmon_chip_info ina2xx_chip_info = { + .ops = &ina2xx_hwmon_ops, + .info = ina2xx_info, +}; + +/* shunt resistance */ + /* * In order to keep calibration register value fixed, the product * of current_lsb and shunt_resistor should also be fixed and equal @@ -481,21 +675,21 @@ static int ina2xx_set_shunt(struct ina2xx_data *data, unsigned long val) return 0; } -static ssize_t ina2xx_shunt_show(struct device *dev, - struct device_attribute *da, char *buf) +static ssize_t shunt_resistor_show(struct device *dev, + struct device_attribute *da, char *buf) { struct ina2xx_data *data = dev_get_drvdata(dev); return sysfs_emit(buf, "%li\n", data->rshunt); } -static ssize_t ina2xx_shunt_store(struct device *dev, - struct device_attribute *da, - const char *buf, size_t count) +static ssize_t shunt_resistor_store(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) { + struct ina2xx_data *data = dev_get_drvdata(dev); unsigned long val; int status; - struct ina2xx_data *data = dev_get_drvdata(dev); status = kstrtoul(buf, 10, &val); if (status < 0) @@ -509,114 +703,14 @@ static ssize_t ina2xx_shunt_store(struct device *dev, return count; } -static ssize_t ina226_interval_store(struct device *dev, - struct device_attribute *da, - const char *buf, size_t count) -{ - struct ina2xx_data *data = dev_get_drvdata(dev); - unsigned long val; - int status; - - status = kstrtoul(buf, 10, &val); - if (status < 0) - return status; - - status = regmap_update_bits(data->regmap, INA2XX_CONFIG, - INA226_AVG_RD_MASK, - ina226_interval_to_reg(val)); - if (status < 0) - return status; - - return count; -} - -static ssize_t ina226_interval_show(struct device *dev, - struct device_attribute *da, char *buf) -{ - struct ina2xx_data *data = dev_get_drvdata(dev); - int status; - unsigned int regval; - - status = regmap_read(data->regmap, INA2XX_CONFIG, ®val); - if (status) - return status; - - return sysfs_emit(buf, "%d\n", ina226_reg_to_interval(regval)); -} - -/* shunt voltage */ -static SENSOR_DEVICE_ATTR_RO(in0_input, ina2xx_value, INA2XX_SHUNT_VOLTAGE); -/* shunt voltage over/under voltage alert setting and alarm */ -static SENSOR_DEVICE_ATTR_RW(in0_crit, ina226_alert, - INA226_SHUNT_OVER_VOLTAGE_MASK); -static SENSOR_DEVICE_ATTR_RW(in0_lcrit, ina226_alert, - INA226_SHUNT_UNDER_VOLTAGE_MASK); -static SENSOR_DEVICE_ATTR_RO(in0_crit_alarm, ina226_alarm, - INA226_SHUNT_OVER_VOLTAGE_MASK); -static SENSOR_DEVICE_ATTR_RO(in0_lcrit_alarm, ina226_alarm, - INA226_SHUNT_UNDER_VOLTAGE_MASK); - -/* bus voltage */ -static SENSOR_DEVICE_ATTR_RO(in1_input, ina2xx_value, INA2XX_BUS_VOLTAGE); -/* bus voltage over/under voltage alert setting and alarm */ -static SENSOR_DEVICE_ATTR_RW(in1_crit, ina226_alert, - INA226_BUS_OVER_VOLTAGE_MASK); -static SENSOR_DEVICE_ATTR_RW(in1_lcrit, ina226_alert, - INA226_BUS_UNDER_VOLTAGE_MASK); -static SENSOR_DEVICE_ATTR_RO(in1_crit_alarm, ina226_alarm, - INA226_BUS_OVER_VOLTAGE_MASK); -static SENSOR_DEVICE_ATTR_RO(in1_lcrit_alarm, ina226_alarm, - INA226_BUS_UNDER_VOLTAGE_MASK); - -/* calculated current */ -static SENSOR_DEVICE_ATTR_RO(curr1_input, ina2xx_value, INA2XX_CURRENT); - -/* calculated power */ -static SENSOR_DEVICE_ATTR_RO(power1_input, ina2xx_value, INA2XX_POWER); -/* over-limit power alert setting and alarm */ -static SENSOR_DEVICE_ATTR_RW(power1_crit, ina226_alert, - INA226_POWER_OVER_LIMIT_MASK); -static SENSOR_DEVICE_ATTR_RO(power1_crit_alarm, ina226_alarm, - INA226_POWER_OVER_LIMIT_MASK); - -/* shunt resistance */ -static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ina2xx_shunt, INA2XX_CALIBRATION); - -/* update interval (ina226 only) */ -static SENSOR_DEVICE_ATTR_RW(update_interval, ina226_interval, 0); +static DEVICE_ATTR_RW(shunt_resistor); /* pointers to created device attributes */ static struct attribute *ina2xx_attrs[] = { - &sensor_dev_attr_in0_input.dev_attr.attr, - &sensor_dev_attr_in1_input.dev_attr.attr, - &sensor_dev_attr_curr1_input.dev_attr.attr, - &sensor_dev_attr_power1_input.dev_attr.attr, - &sensor_dev_attr_shunt_resistor.dev_attr.attr, + &dev_attr_shunt_resistor.attr, NULL, }; - -static const struct attribute_group ina2xx_group = { - .attrs = ina2xx_attrs, -}; - -static struct attribute *ina226_attrs[] = { - &sensor_dev_attr_in0_crit.dev_attr.attr, - &sensor_dev_attr_in0_lcrit.dev_attr.attr, - &sensor_dev_attr_in0_crit_alarm.dev_attr.attr, - &sensor_dev_attr_in0_lcrit_alarm.dev_attr.attr, - &sensor_dev_attr_in1_crit.dev_attr.attr, - &sensor_dev_attr_in1_lcrit.dev_attr.attr, - &sensor_dev_attr_in1_crit_alarm.dev_attr.attr, - &sensor_dev_attr_in1_lcrit_alarm.dev_attr.attr, - &sensor_dev_attr_power1_crit.dev_attr.attr, - &sensor_dev_attr_power1_crit_alarm.dev_attr.attr, - &sensor_dev_attr_update_interval.dev_attr.attr, - NULL, -}; - -static const struct attribute_group ina226_group = { - .attrs = ina226_attrs, -}; +ATTRIBUTE_GROUPS(ina2xx); /* * Initialize chip @@ -662,8 +756,8 @@ static int ina2xx_probe(struct i2c_client *client) struct device *dev = &client->dev; struct ina2xx_data *data; struct device *hwmon_dev; - int ret, group = 0; enum ina2xx_ids chip; + int ret; chip = (uintptr_t)i2c_get_match_data(client); @@ -690,12 +784,9 @@ static int ina2xx_probe(struct i2c_client *client) if (ret < 0) return dev_err_probe(dev, ret, "failed to configure device\n"); - data->groups[group++] = &ina2xx_group; - if (chip == ina226) - data->groups[group++] = &ina226_group; - - hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, - data, data->groups); + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, &ina2xx_chip_info, + ina2xx_groups); if (IS_ERR(hwmon_dev)) return PTR_ERR(hwmon_dev); From 9965ebd1836fb75c7a80f20ca65469f5df0d6063 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 28 Aug 2024 15:18:51 -0700 Subject: [PATCH 273/573] hwmon: (ina2xx) Pass register to alert limit write functions Pass the to-be-limited register to alert functions and use it to determine conversion from limit to register value. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 339d41dfa10e..1cd6fffb1495 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -306,21 +306,19 @@ static int ina2xx_read_init(struct device *dev, int reg, long *val) * Turns alert limit values into register values. * Opposite of the formula in ina2xx_get_value(). */ -static u16 ina226_alert_to_reg(struct ina2xx_data *data, u32 mask, unsigned long val) +static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, unsigned long val) { - switch (mask) { - case INA226_SHUNT_OVER_VOLTAGE_MASK: - case INA226_SHUNT_UNDER_VOLTAGE_MASK: + switch (reg) { + case INA2XX_SHUNT_VOLTAGE: val = clamp_val(val, 0, SHRT_MAX * data->config->shunt_div); val *= data->config->shunt_div; return clamp_val(val, 0, SHRT_MAX); - case INA226_BUS_OVER_VOLTAGE_MASK: - case INA226_BUS_UNDER_VOLTAGE_MASK: + case INA2XX_BUS_VOLTAGE: val = clamp_val(val, 0, 200000); val = (val * 1000) << data->config->bus_voltage_shift; val = DIV_ROUND_CLOSEST(val, data->config->bus_voltage_lsb); return clamp_val(val, 0, USHRT_MAX); - case INA226_POWER_OVER_LIMIT_MASK: + case INA2XX_POWER: val = clamp_val(val, 0, UINT_MAX - data->power_lsb_uW); val = DIV_ROUND_CLOSEST(val, data->power_lsb_uW); return clamp_val(val, 0, USHRT_MAX); @@ -355,7 +353,7 @@ abort: return ret; } -static int ina226_alert_limit_write(struct ina2xx_data *data, u32 mask, long val) +static int ina226_alert_limit_write(struct ina2xx_data *data, u32 mask, int reg, long val) { struct regmap *regmap = data->regmap; int ret; @@ -375,7 +373,7 @@ static int ina226_alert_limit_write(struct ina2xx_data *data, u32 mask, long val goto abort; ret = regmap_write(regmap, INA226_ALERT_LIMIT, - ina226_alert_to_reg(data, mask, val)); + ina226_alert_to_reg(data, reg, val)); if (ret < 0) goto abort; @@ -522,10 +520,12 @@ static int ina2xx_in_write(struct device *dev, u32 attr, int channel, long val) case hwmon_in_lcrit: return ina226_alert_limit_write(data, channel ? INA226_BUS_UNDER_VOLTAGE_MASK : INA226_SHUNT_UNDER_VOLTAGE_MASK, + channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE, val); case hwmon_in_crit: return ina226_alert_limit_write(data, channel ? INA226_BUS_OVER_VOLTAGE_MASK : INA226_SHUNT_OVER_VOLTAGE_MASK, + channel ? INA2XX_BUS_VOLTAGE : INA2XX_SHUNT_VOLTAGE, val); default: return -EOPNOTSUPP; @@ -539,7 +539,8 @@ static int ina2xx_power_write(struct device *dev, u32 attr, long val) switch (attr) { case hwmon_power_crit: - return ina226_alert_limit_write(data, INA226_POWER_OVER_LIMIT_MASK, val); + return ina226_alert_limit_write(data, INA226_POWER_OVER_LIMIT_MASK, + INA2XX_POWER, val); default: return -EOPNOTSUPP; } From 4d5c2d986757e4d6f56761af8ab689218a2bc432 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 28 Aug 2024 15:23:53 -0700 Subject: [PATCH 274/573] hwmon: (ina2xx) Add support for current limits While the chips supported by this driver do not directly support current limits, they do support setting shunt voltage limits. The shunt voltage divided by the shunt resistor value is the current. On top of that, calibration values are set such that in the shunt voltage register and the current register report the same values. That means we can report and configure current limits based on shunt voltage limits, and we can do so with much better accuracy than by setting shunt voltage limits. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- Documentation/hwmon/ina2xx.rst | 4 +++ drivers/hwmon/ina2xx.c | 61 ++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/Documentation/hwmon/ina2xx.rst b/Documentation/hwmon/ina2xx.rst index 27d2e39bc8ac..7f1939b40f74 100644 --- a/Documentation/hwmon/ina2xx.rst +++ b/Documentation/hwmon/ina2xx.rst @@ -99,6 +99,10 @@ Sysfs entries for ina226, ina230 and ina231 only ------------------------------------------------ ======================= ==================================================== +curr1_lcrit Critical low current +curr1_crit Critical high current +curr1_lcrit_alarm Current critical low alarm +curr1_crit_alarm Current critical high alarm in0_lcrit Critical low shunt voltage in0_crit Critical high shunt voltage in0_lcrit_alarm Shunt voltage critical low alarm diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 1cd6fffb1495..10c8c475c634 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -306,7 +306,7 @@ static int ina2xx_read_init(struct device *dev, int reg, long *val) * Turns alert limit values into register values. * Opposite of the formula in ina2xx_get_value(). */ -static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, unsigned long val) +static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, long val) { switch (reg) { case INA2XX_SHUNT_VOLTAGE: @@ -322,6 +322,11 @@ static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, unsigned long val = clamp_val(val, 0, UINT_MAX - data->power_lsb_uW); val = DIV_ROUND_CLOSEST(val, data->power_lsb_uW); return clamp_val(val, 0, USHRT_MAX); + case INA2XX_CURRENT: + val = clamp_val(val, INT_MIN / 1000, INT_MAX / 1000); + /* signed register, result in mA */ + val = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb_uA); + return clamp_val(val, SHRT_MIN, SHRT_MAX); default: /* programmer goofed */ WARN_ON_ONCE(1); @@ -473,9 +478,31 @@ static int ina2xx_power_read(struct device *dev, u32 attr, long *val) static int ina2xx_curr_read(struct device *dev, u32 attr, long *val) { + struct ina2xx_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + + /* + * While the chips supported by this driver do not directly support + * current limits, they do support setting shunt voltage limits. + * The shunt voltage divided by the shunt resistor value is the current. + * On top of that, calibration values are set such that in the shunt + * voltage register and the current register report the same values. + * That means we can report and configure current limits based on shunt + * voltage limits. + */ switch (attr) { case hwmon_curr_input: return ina2xx_read_init(dev, INA2XX_CURRENT, val); + case hwmon_curr_lcrit: + return ina226_alert_limit_read(data, INA226_SHUNT_UNDER_VOLTAGE_MASK, + INA2XX_CURRENT, val); + case hwmon_curr_crit: + return ina226_alert_limit_read(data, INA226_SHUNT_OVER_VOLTAGE_MASK, + INA2XX_CURRENT, val); + case hwmon_curr_lcrit_alarm: + return ina226_alert_read(regmap, INA226_SHUNT_UNDER_VOLTAGE_MASK, val); + case hwmon_curr_crit_alarm: + return ina226_alert_read(regmap, INA226_SHUNT_OVER_VOLTAGE_MASK, val); default: return -EOPNOTSUPP; } @@ -547,6 +574,23 @@ static int ina2xx_power_write(struct device *dev, u32 attr, long val) return 0; } +static int ina2xx_curr_write(struct device *dev, u32 attr, long val) +{ + struct ina2xx_data *data = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_curr_lcrit: + return ina226_alert_limit_write(data, INA226_SHUNT_UNDER_VOLTAGE_MASK, + INA2XX_CURRENT, val); + case hwmon_curr_crit: + return ina226_alert_limit_write(data, INA226_SHUNT_OVER_VOLTAGE_MASK, + INA2XX_CURRENT, val); + default: + return -EOPNOTSUPP; + } + return 0; +} + static int ina2xx_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { @@ -557,6 +601,8 @@ static int ina2xx_write(struct device *dev, enum hwmon_sensor_types type, return ina2xx_in_write(dev, attr, channel, val); case hwmon_power: return ina2xx_power_write(dev, attr, val); + case hwmon_curr: + return ina2xx_curr_write(dev, attr, val); default: return -EOPNOTSUPP; } @@ -591,6 +637,16 @@ static umode_t ina2xx_is_visible(const void *_data, enum hwmon_sensor_types type switch (attr) { case hwmon_curr_input: return 0444; + case hwmon_curr_lcrit: + case hwmon_curr_crit: + if (chip == ina226) + return 0644; + break; + case hwmon_curr_lcrit_alarm: + case hwmon_curr_crit_alarm: + if (chip == ina226) + return 0444; + break; default: break; } @@ -636,7 +692,8 @@ static const struct hwmon_channel_info * const ina2xx_info[] = { HWMON_I_INPUT | HWMON_I_CRIT | HWMON_I_CRIT_ALARM | HWMON_I_LCRIT | HWMON_I_LCRIT_ALARM ), - HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT), + HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_CRIT | HWMON_C_CRIT_ALARM | + HWMON_C_LCRIT | HWMON_C_LCRIT_ALARM), HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_CRIT | HWMON_P_CRIT_ALARM), NULL From 63fb21afc1f5b2af746f332710491c61e2ad6d74 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 28 Aug 2024 17:21:41 -0700 Subject: [PATCH 275/573] hwmon: (ina2xx) Use shunt voltage to calculate current Since the shunt voltage and the current register report the same values when the chip is calibrated, we can calculate the current directly from the shunt voltage without relying on chip calibration. With this change, the current register is no longer accessed. Its register address is only used to indicate if reading or writing current or shunt voltage is desired when accessing registers. Reviewed-by: Tzung-Bi Shih Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 10c8c475c634..f0fa6d073627 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -480,6 +480,8 @@ static int ina2xx_curr_read(struct device *dev, u32 attr, long *val) { struct ina2xx_data *data = dev_get_drvdata(dev); struct regmap *regmap = data->regmap; + unsigned int regval; + int ret; /* * While the chips supported by this driver do not directly support @@ -492,7 +494,17 @@ static int ina2xx_curr_read(struct device *dev, u32 attr, long *val) */ switch (attr) { case hwmon_curr_input: - return ina2xx_read_init(dev, INA2XX_CURRENT, val); + /* + * Since the shunt voltage and the current register report the + * same values when the chip is calibrated, we can calculate + * the current directly from the shunt voltage without relying + * on chip calibration. + */ + ret = regmap_read(regmap, INA2XX_SHUNT_VOLTAGE, ®val); + if (ret) + return ret; + *val = ina2xx_get_value(data, INA2XX_CURRENT, regval); + return 0; case hwmon_curr_lcrit: return ina226_alert_limit_read(data, INA226_SHUNT_UNDER_VOLTAGE_MASK, INA2XX_CURRENT, val); From 33ffa2dd0de28ded1fa856b3fb9cbf2b873d7dd0 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Fri, 30 Aug 2024 17:21:36 +0200 Subject: [PATCH 276/573] selftests: vDSO: quash clang omitted parameter warning in getrandom test When building with clang, there's this warning: vdso_test_getrandom.c:145:40: warning: omitting the parameter name in a function definition is a C23 extension [-Wc23-extensions] 145 | static void *test_vdso_getrandom(void *) | ^ vdso_test_getrandom.c:155:40: warning: omitting the parameter name in a function definition is a C23 extension [-Wc23-extensions] 155 | static void *test_libc_getrandom(void *) | ^ vdso_test_getrandom.c:165:43: warning: omitting the parameter name in a function definition is a C23 extension [-Wc23-extensions] 165 | static void *test_syscall_getrandom(void *) Add the named ctx parameter to quash it. Reported-by: Mark Brown Reviewed-by: Mark Brown Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/vdso_test_getrandom.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/vDSO/vdso_test_getrandom.c b/tools/testing/selftests/vDSO/vdso_test_getrandom.c index 351daeb649c8..8866b65a4605 100644 --- a/tools/testing/selftests/vDSO/vdso_test_getrandom.c +++ b/tools/testing/selftests/vDSO/vdso_test_getrandom.c @@ -143,7 +143,7 @@ static ssize_t vgetrandom(void *buf, size_t len, unsigned long flags) enum { TRIALS = 25000000, THREADS = 256 }; -static void *test_vdso_getrandom(void *) +static void *test_vdso_getrandom(void *ctx) { for (size_t i = 0; i < TRIALS; ++i) { unsigned int val; @@ -153,7 +153,7 @@ static void *test_vdso_getrandom(void *) return NULL; } -static void *test_libc_getrandom(void *) +static void *test_libc_getrandom(void *ctx) { for (size_t i = 0; i < TRIALS; ++i) { unsigned int val; @@ -163,7 +163,7 @@ static void *test_libc_getrandom(void *) return NULL; } -static void *test_syscall_getrandom(void *) +static void *test_syscall_getrandom(void *ctx) { for (size_t i = 0; i < TRIALS; ++i) { unsigned int val; From aa578e897520f32ae12bec487f2474357d01ca9c Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Thu, 1 Aug 2024 22:34:39 +0200 Subject: [PATCH 277/573] fbdev: hpfb: Fix an error handling path in hpfb_dio_probe() If an error occurs after request_mem_region(), a corresponding release_mem_region() should be called, as already done in the remove function. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Christophe JAILLET Signed-off-by: Helge Deller --- drivers/video/fbdev/hpfb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/video/fbdev/hpfb.c b/drivers/video/fbdev/hpfb.c index 66fac8e5393e..a1144b150982 100644 --- a/drivers/video/fbdev/hpfb.c +++ b/drivers/video/fbdev/hpfb.c @@ -345,6 +345,7 @@ static int hpfb_dio_probe(struct dio_dev *d, const struct dio_device_id *ent) if (hpfb_init_one(paddr, vaddr)) { if (d->scode >= DIOII_SCBASE) iounmap((void *)vaddr); + release_mem_region(d->resource.start, resource_size(&d->resource)); return -ENOMEM; } return 0; From 5ee70bec7945827c4661b9d44b094aac352dbaa8 Mon Sep 17 00:00:00 2001 From: ying zuxin Date: Fri, 23 Aug 2024 20:29:57 +0800 Subject: [PATCH 278/573] fbdev: mmp: Use devm_clk_get_enabled() helpers The devm_clk_get_enabled() helpers: - call devm_clk_get() - call clk_prepare_enable() and register what is needed in order to call clk_disable_unprepare() when needed, as a managed resource. This simplifies the code and avoids the calls to clk_disable_unprepare(). Signed-off-by: ying zuxin Signed-off-by: Helge Deller --- drivers/video/fbdev/mmp/hw/mmp_ctrl.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/video/fbdev/mmp/hw/mmp_ctrl.c b/drivers/video/fbdev/mmp/hw/mmp_ctrl.c index a20a2c408127..03e23173198c 100644 --- a/drivers/video/fbdev/mmp/hw/mmp_ctrl.c +++ b/drivers/video/fbdev/mmp/hw/mmp_ctrl.c @@ -512,16 +512,13 @@ static int mmphw_probe(struct platform_device *pdev) } /* get clock */ - ctrl->clk = devm_clk_get(ctrl->dev, mi->clk_name); + ctrl->clk = devm_clk_get_enabled(ctrl->dev, mi->clk_name); if (IS_ERR(ctrl->clk)) { ret = PTR_ERR(ctrl->clk); dev_err_probe(ctrl->dev, ret, "unable to get clk %s\n", mi->clk_name); goto failed; } - ret = clk_prepare_enable(ctrl->clk); - if (ret) - goto failed; /* init global regs */ ctrl_set_default(ctrl); @@ -556,7 +553,6 @@ failed_path_init: path_deinit(path_plat); } - clk_disable_unprepare(ctrl->clk); failed: dev_err(&pdev->dev, "device init failed\n"); From 33ae421ad2b546e472e36d73cf13f2b3b386f713 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Thu, 8 Aug 2024 11:46:11 +0200 Subject: [PATCH 279/573] fbdev: omapfb: panel-sony-acx565akm: Simplify show_cabc_available_modes() Use sysfs_emit_at() instead of snprintf() + custom logic. Using sysfs_emit_at() is much more simple. Also, sysfs_emit() is already used in this function, so using sysfs_emit_at() is more consistent. Also simplify the logic: - always add a space after an entry - change the last space into a '\n' Finally it is easy to see that, given the size of cabc_modes, PAGE_SIZE can not be reached. So better keep everything simple (and correct). Signed-off-by: Christophe JAILLET Reviewed-by: Andi Shyti Signed-off-by: Helge Deller --- .../omap2/omapfb/displays/panel-sony-acx565akm.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c b/drivers/video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c index 71d2e015960c..fc975615d5c9 100644 --- a/drivers/video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c +++ b/drivers/video/fbdev/omap2/omapfb/displays/panel-sony-acx565akm.c @@ -466,19 +466,20 @@ static ssize_t show_cabc_available_modes(struct device *dev, char *buf) { struct panel_drv_data *ddata = dev_get_drvdata(dev); - int len; + int len = 0; int i; if (!ddata->has_cabc) return sysfs_emit(buf, "%s\n", cabc_modes[0]); - for (i = 0, len = 0; - len < PAGE_SIZE && i < ARRAY_SIZE(cabc_modes); i++) - len += snprintf(&buf[len], PAGE_SIZE - len, "%s%s%s", - i ? " " : "", cabc_modes[i], - i == ARRAY_SIZE(cabc_modes) - 1 ? "\n" : ""); + for (i = 0; i < ARRAY_SIZE(cabc_modes); i++) + len += sysfs_emit_at(buf, len, "%s ", cabc_modes[i]); - return len < PAGE_SIZE ? len : PAGE_SIZE - 1; + /* Remove the trailing space */ + if (len) + buf[len - 1] = '\n'; + + return len; } static DEVICE_ATTR(cabc_mode, S_IRUGO | S_IWUSR, From 2451a285ee5e8c83bedcee7f4962c828b890becf Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Thu, 8 Aug 2024 14:14:22 +0200 Subject: [PATCH 280/573] fbdev: omapfb: Use sysfs_emit_at() to simplify code This file already uses sysfs_emit(). So be consistent and also use sysfs_emit_at(). Moreover, size is always < PAGE_SIZE because scnprintf() (and now sysfs_emit_at()) returns the number of characters written not including the trailing '\0'. So some tests can be removed. This slightly simplifies the code and makes it more readable. Signed-off-by: Christophe JAILLET Reviewed-by: Andi Shyti Signed-off-by: Helge Deller --- drivers/video/fbdev/omap/omapfb_main.c | 36 ++++++++++---------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/drivers/video/fbdev/omap/omapfb_main.c b/drivers/video/fbdev/omap/omapfb_main.c index aa31c0d26e92..e12c6019a4d6 100644 --- a/drivers/video/fbdev/omap/omapfb_main.c +++ b/drivers/video/fbdev/omap/omapfb_main.c @@ -1241,14 +1241,13 @@ static ssize_t omapfb_show_caps_num(struct device *dev, { struct omapfb_device *fbdev = dev_get_drvdata(dev); int plane; - size_t size; + size_t size = 0; struct omapfb_caps caps; plane = 0; - size = 0; - while (size < PAGE_SIZE && plane < OMAPFB_PLANE_NUM) { + while (plane < OMAPFB_PLANE_NUM) { omapfb_get_caps(fbdev, plane, &caps); - size += scnprintf(&buf[size], PAGE_SIZE - size, + size += sysfs_emit_at(buf, size, "plane#%d %#010x %#010x %#010x\n", plane, caps.ctrl, caps.plane_color, caps.wnd_color); plane++; @@ -1263,34 +1262,27 @@ static ssize_t omapfb_show_caps_text(struct device *dev, int i; struct omapfb_caps caps; int plane; - size_t size; + size_t size = 0; plane = 0; - size = 0; - while (size < PAGE_SIZE && plane < OMAPFB_PLANE_NUM) { + while (plane < OMAPFB_PLANE_NUM) { omapfb_get_caps(fbdev, plane, &caps); - size += scnprintf(&buf[size], PAGE_SIZE - size, - "plane#%d:\n", plane); - for (i = 0; i < ARRAY_SIZE(ctrl_caps) && - size < PAGE_SIZE; i++) { + size += sysfs_emit_at(buf, size, "plane#%d:\n", plane); + for (i = 0; i < ARRAY_SIZE(ctrl_caps); i++) { if (ctrl_caps[i].flag & caps.ctrl) - size += scnprintf(&buf[size], PAGE_SIZE - size, + size += sysfs_emit_at(buf, size, " %s\n", ctrl_caps[i].name); } - size += scnprintf(&buf[size], PAGE_SIZE - size, - " plane colors:\n"); - for (i = 0; i < ARRAY_SIZE(color_caps) && - size < PAGE_SIZE; i++) { + size += sysfs_emit_at(buf, size, " plane colors:\n"); + for (i = 0; i < ARRAY_SIZE(color_caps); i++) { if (color_caps[i].flag & caps.plane_color) - size += scnprintf(&buf[size], PAGE_SIZE - size, + size += sysfs_emit_at(buf, size, " %s\n", color_caps[i].name); } - size += scnprintf(&buf[size], PAGE_SIZE - size, - " window colors:\n"); - for (i = 0; i < ARRAY_SIZE(color_caps) && - size < PAGE_SIZE; i++) { + size += sysfs_emit_at(buf, size, " window colors:\n"); + for (i = 0; i < ARRAY_SIZE(color_caps); i++) { if (color_caps[i].flag & caps.wnd_color) - size += scnprintf(&buf[size], PAGE_SIZE - size, + size += sysfs_emit_at(buf, size, " %s\n", color_caps[i].name); } From 929c81ade6355b87097a2a4886c10750e68626cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Fri, 30 Aug 2024 11:45:57 +0200 Subject: [PATCH 281/573] fbdev: Introduce devm_register_framebuffer() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a device-managed variant of register_framebuffer() which automatically unregisters the framebuffer on device destruction. This can simplify the error handling and resource management in drivers. Signed-off-by: Thomas Weißschuh Signed-off-by: Helge Deller --- drivers/video/fbdev/core/fbmem.c | 30 ++++++++++++++++++++++++++++++ include/linux/fb.h | 1 + 2 files changed, 31 insertions(+) diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c index 4c4ad0a86a50..3c568cff2913 100644 --- a/drivers/video/fbdev/core/fbmem.c +++ b/drivers/video/fbdev/core/fbmem.c @@ -544,6 +544,36 @@ unregister_framebuffer(struct fb_info *fb_info) } EXPORT_SYMBOL(unregister_framebuffer); +static void devm_unregister_framebuffer(void *data) +{ + struct fb_info *info = data; + + unregister_framebuffer(info); +} + +/** + * devm_register_framebuffer - resource-managed frame buffer device registration + * @dev: device the framebuffer belongs to + * @fb_info: frame buffer info structure + * + * Registers a frame buffer device @fb_info to device @dev. + * + * Returns negative errno on error, or zero for success. + * + */ +int +devm_register_framebuffer(struct device *dev, struct fb_info *fb_info) +{ + int ret; + + ret = register_framebuffer(fb_info); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, devm_unregister_framebuffer, fb_info); +} +EXPORT_SYMBOL(devm_register_framebuffer); + /** * fb_set_suspend - low level driver signals suspend * @info: framebuffer affected diff --git a/include/linux/fb.h b/include/linux/fb.h index db7d97b10964..abf6643ebcaf 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -601,6 +601,7 @@ extern ssize_t fb_sys_write(struct fb_info *info, const char __user *buf, /* fbmem.c */ extern int register_framebuffer(struct fb_info *fb_info); extern void unregister_framebuffer(struct fb_info *fb_info); +extern int devm_register_framebuffer(struct device *dev, struct fb_info *fb_info); extern char* fb_get_buffer_offset(struct fb_info *info, struct fb_pixmap *buf, u32 size); extern void fb_pad_unaligned_buffer(u8 *dst, u32 d_pitch, u8 *src, u32 idx, u32 height, u32 shift_high, u32 shift_low, u32 mod); From 95cdd538e0e5677efbdf8aade04ec098ab98f457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 27 Aug 2024 17:25:13 +0200 Subject: [PATCH 282/573] fbdev: efifb: Register sysfs groups through driver core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver core can register and cleanup sysfs groups already. Make use of that functionality to simplify the error handling and cleanup. Also avoid a UAF race during unregistering where the sysctl attributes were usable after the info struct was freed. Signed-off-by: Thomas Weißschuh Signed-off-by: Helge Deller --- drivers/video/fbdev/efifb.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/video/fbdev/efifb.c b/drivers/video/fbdev/efifb.c index 8dd82afb3452..595b8e27bea6 100644 --- a/drivers/video/fbdev/efifb.c +++ b/drivers/video/fbdev/efifb.c @@ -561,15 +561,10 @@ static int efifb_probe(struct platform_device *dev) break; } - err = sysfs_create_groups(&dev->dev.kobj, efifb_groups); - if (err) { - pr_err("efifb: cannot add sysfs attrs\n"); - goto err_unmap; - } err = fb_alloc_cmap(&info->cmap, 256, 0); if (err < 0) { pr_err("efifb: cannot allocate colormap\n"); - goto err_groups; + goto err_unmap; } err = devm_aperture_acquire_for_platform_device(dev, par->base, par->size); @@ -587,8 +582,6 @@ static int efifb_probe(struct platform_device *dev) err_fb_dealloc_cmap: fb_dealloc_cmap(&info->cmap); -err_groups: - sysfs_remove_groups(&dev->dev.kobj, efifb_groups); err_unmap: if (mem_flags & (EFI_MEMORY_UC | EFI_MEMORY_WC)) iounmap(info->screen_base); @@ -608,12 +601,12 @@ static void efifb_remove(struct platform_device *pdev) /* efifb_destroy takes care of info cleanup */ unregister_framebuffer(info); - sysfs_remove_groups(&pdev->dev.kobj, efifb_groups); } static struct platform_driver efifb_driver = { .driver = { .name = "efi-framebuffer", + .dev_groups = efifb_groups, }, .probe = efifb_probe, .remove_new = efifb_remove, From 077091721af00d2a9212218c2d61acbac5f47630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 27 Aug 2024 17:25:15 +0200 Subject: [PATCH 283/573] fbdev: efifb: Use devm_register_framebuffer() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This simplifies the error handling. Also the drvdata slot is now unused and can be used for other usecases. Signed-off-by: Thomas Weißschuh Signed-off-by: Helge Deller --- drivers/video/fbdev/efifb.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/drivers/video/fbdev/efifb.c b/drivers/video/fbdev/efifb.c index 595b8e27bea6..ee71a65b361d 100644 --- a/drivers/video/fbdev/efifb.c +++ b/drivers/video/fbdev/efifb.c @@ -449,7 +449,6 @@ static int efifb_probe(struct platform_device *dev) err = -ENOMEM; goto err_release_mem; } - platform_set_drvdata(dev, info); par = info->par; info->pseudo_palette = par->pseudo_palette; @@ -572,7 +571,7 @@ static int efifb_probe(struct platform_device *dev) pr_err("efifb: cannot acquire aperture\n"); goto err_fb_dealloc_cmap; } - err = register_framebuffer(info); + err = devm_register_framebuffer(&dev->dev, info); if (err < 0) { pr_err("efifb: cannot register framebuffer\n"); goto err_fb_dealloc_cmap; @@ -595,21 +594,12 @@ err_release_mem: return err; } -static void efifb_remove(struct platform_device *pdev) -{ - struct fb_info *info = platform_get_drvdata(pdev); - - /* efifb_destroy takes care of info cleanup */ - unregister_framebuffer(info); -} - static struct platform_driver efifb_driver = { .driver = { .name = "efi-framebuffer", .dev_groups = efifb_groups, }, .probe = efifb_probe, - .remove_new = efifb_remove, }; builtin_platform_driver(efifb_driver); From bd97615a331684590f6fe65420e9a959a57c975b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 27 Aug 2024 17:25:16 +0200 Subject: [PATCH 284/573] fbdev: efifb: Use driver-private screen_info for sysfs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit b9cfd1d271ab ("fbdev/efifb: Use screen_info pointer from device") efifb uses a local copy of screen_info and applies its modifications there. Adapt the sysfs attributes to also work with the custom copy instead of the unmodified platform data. Signed-off-by: Thomas Weißschuh Signed-off-by: Helge Deller --- drivers/video/fbdev/efifb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/video/fbdev/efifb.c b/drivers/video/fbdev/efifb.c index ee71a65b361d..20517448487e 100644 --- a/drivers/video/fbdev/efifb.c +++ b/drivers/video/fbdev/efifb.c @@ -322,7 +322,7 @@ static ssize_t name##_show(struct device *dev, \ struct device_attribute *attr, \ char *buf) \ { \ - struct screen_info *si = dev_get_platdata(dev); \ + struct screen_info *si = dev_get_drvdata(dev); \ if (!si) \ return -ENODEV; \ return sprintf(buf, fmt "\n", (si->lfb_##name)); \ @@ -369,6 +369,8 @@ static int efifb_probe(struct platform_device *dev) if (!si) return -ENOMEM; + dev_set_drvdata(&dev->dev, si); + if (si->orig_video_isVGA != VIDEO_TYPE_EFI) return -ENODEV; From 7b4b93e260c684d346998a3dddc5335957b8be78 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 21 Aug 2024 14:14:53 +0200 Subject: [PATCH 285/573] gpio: ath79: order headers alphabetically Put all headers in alphabetical order. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20240821121456.19553-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ath79.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-ath79.c b/drivers/gpio/gpio-ath79.c index 6211d99a5770..be2952fdae3b 100644 --- a/drivers/gpio/gpio-ath79.c +++ b/drivers/gpio/gpio-ath79.c @@ -9,12 +9,12 @@ */ #include -#include -#include -#include #include -#include #include +#include +#include +#include +#include #define AR71XX_GPIO_REG_OE 0x00 #define AR71XX_GPIO_REG_IN 0x04 From 4acde50b4d15373b55ff23424526378efa681dc6 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 21 Aug 2024 14:14:54 +0200 Subject: [PATCH 286/573] gpio: ath79: add missing header Include mod_devicetable.h for struct of_device_id and its helpers. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20240821121456.19553-2-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ath79.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpio/gpio-ath79.c b/drivers/gpio/gpio-ath79.c index be2952fdae3b..7f9e66d75c8b 100644 --- a/drivers/gpio/gpio-ath79.c +++ b/drivers/gpio/gpio-ath79.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include From 10a968b21b89c647faa26b1f06ea82aa2eef1f9a Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 21 Aug 2024 14:14:55 +0200 Subject: [PATCH 287/573] gpio: ath79: use generic device property getters Don't use specialized OF accessors if we can avoid it: switch to using the generic device property helpers. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20240821121456.19553-3-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ath79.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-ath79.c b/drivers/gpio/gpio-ath79.c index 7f9e66d75c8b..211faffbef32 100644 --- a/drivers/gpio/gpio-ath79.c +++ b/drivers/gpio/gpio-ath79.c @@ -8,12 +8,12 @@ * Copyright (C) 2008 Imre Kaloz */ +#include #include #include #include #include #include -#include #include #include @@ -239,12 +239,12 @@ static int ath79_gpio_probe(struct platform_device *pdev) return -ENOMEM; if (np) { - err = of_property_read_u32(np, "ngpios", &ath79_gpio_count); + err = device_property_read_u32(dev, "ngpios", &ath79_gpio_count); if (err) { dev_err(dev, "ngpios property is not valid\n"); return err; } - oe_inverted = of_device_is_compatible(np, "qca,ar9340-gpio"); + oe_inverted = device_is_compatible(dev, "qca,ar9340-gpio"); } else if (pdata) { ath79_gpio_count = pdata->ngpios; oe_inverted = pdata->oe_inverted; @@ -276,7 +276,7 @@ static int ath79_gpio_probe(struct platform_device *pdev) } /* Optional interrupt setup */ - if (!np || of_property_read_bool(np, "interrupt-controller")) { + if (device_property_read_bool(dev, "interrupt-controller")) { girq = &ctrl->gc.irq; gpio_irq_chip_set_chip(girq, &ath79_gpio_irqchip); girq->parent_handler = ath79_gpio_irq_handler; From c4a315eaf8eff0d3234600e13db7e7c71c0b3405 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 21 Aug 2024 14:14:56 +0200 Subject: [PATCH 288/573] gpio: ath79: remove support for platform data There are no more board files defining platform data for this driver so remove the header and support from the driver. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20240821121456.19553-4-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ath79.c | 22 ++++++---------------- include/linux/platform_data/gpio-ath79.h | 16 ---------------- 2 files changed, 6 insertions(+), 32 deletions(-) delete mode 100644 include/linux/platform_data/gpio-ath79.h diff --git a/drivers/gpio/gpio-ath79.c b/drivers/gpio/gpio-ath79.c index 211faffbef32..de4cc12e5e03 100644 --- a/drivers/gpio/gpio-ath79.c +++ b/drivers/gpio/gpio-ath79.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #define AR71XX_GPIO_REG_OE 0x00 @@ -225,9 +224,7 @@ MODULE_DEVICE_TABLE(of, ath79_gpio_of_match); static int ath79_gpio_probe(struct platform_device *pdev) { - struct ath79_gpio_platform_data *pdata = dev_get_platdata(&pdev->dev); struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; struct ath79_gpio_ctrl *ctrl; struct gpio_irq_chip *girq; u32 ath79_gpio_count; @@ -238,21 +235,14 @@ static int ath79_gpio_probe(struct platform_device *pdev) if (!ctrl) return -ENOMEM; - if (np) { - err = device_property_read_u32(dev, "ngpios", &ath79_gpio_count); - if (err) { - dev_err(dev, "ngpios property is not valid\n"); - return err; - } - oe_inverted = device_is_compatible(dev, "qca,ar9340-gpio"); - } else if (pdata) { - ath79_gpio_count = pdata->ngpios; - oe_inverted = pdata->oe_inverted; - } else { - dev_err(dev, "No DT node or platform data found\n"); - return -EINVAL; + err = device_property_read_u32(dev, "ngpios", &ath79_gpio_count); + if (err) { + dev_err(dev, "ngpios property is not valid\n"); + return err; } + oe_inverted = device_is_compatible(dev, "qca,ar9340-gpio"); + if (ath79_gpio_count >= 32) { dev_err(dev, "ngpios must be less than 32\n"); return -EINVAL; diff --git a/include/linux/platform_data/gpio-ath79.h b/include/linux/platform_data/gpio-ath79.h deleted file mode 100644 index 3ea6dd942c27..000000000000 --- a/include/linux/platform_data/gpio-ath79.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Atheros AR7XXX/AR9XXX GPIO controller platform data - * - * Copyright (C) 2015 Alban Bedel - */ - -#ifndef __LINUX_PLATFORM_DATA_GPIO_ATH79_H -#define __LINUX_PLATFORM_DATA_GPIO_ATH79_H - -struct ath79_gpio_platform_data { - unsigned ngpios; - bool oe_inverted; -}; - -#endif From 1882e769362b8e4ef68fd30a05295f5eedf5c54a Mon Sep 17 00:00:00 2001 From: Shen Lichuan Date: Thu, 29 Aug 2024 21:10:51 +0800 Subject: [PATCH 289/573] gpio: stmpe: Simplify with dev_err_probe() Use dev_err_probe() to simplify the error path and unify a message template. Using this helper is totally fine even if err is known to never be -EPROBE_DEFER. The benefit compared to a normal dev_err() is the standardized format of the error code, it being emitted symbolically and the fact that the error code is returned which allows more compact error paths. Signed-off-by: Shen Lichuan Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20240829131051.43200-1-shenlichuan@vivo.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-stmpe.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-stmpe.c b/drivers/gpio/gpio-stmpe.c index 6c5ee81d71b3..abd42a975b09 100644 --- a/drivers/gpio/gpio-stmpe.c +++ b/drivers/gpio/gpio-stmpe.c @@ -513,10 +513,9 @@ static int stmpe_gpio_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, stmpe_gpio_irq, IRQF_ONESHOT, "stmpe-gpio", stmpe_gpio); - if (ret) { - dev_err(&pdev->dev, "unable to get irq: %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "unable to get irq"); girq = &stmpe_gpio->chip.irq; gpio_irq_chip_set_chip(girq, &stmpe_gpio_irq_chip); From 4b2b0a2ce8153d65d0829e45e73bf6acdc291344 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 28 Aug 2024 17:23:57 +0300 Subject: [PATCH 290/573] gpiolib: legacy: Kill GPIOF_INIT_* definitions Besides the fact that (old) drivers use wrong definitions, e.g., GPIOF_INIT_HIGH instead of GPIOF_OUT_INIT_HIGH, shrink the legacy definitions by killing those GPIOF_INIT_* completely. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Acked-by: Alexander Sverdlin Reviewed-by: Alexander Sverdlin Link: https://lore.kernel.org/r/20240828142554.2424189-2-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- arch/arm/mach-ep93xx/vision_ep9307.c | 3 +-- arch/mips/bcm63xx/boards/board_bcm963xx.c | 2 +- drivers/gpio/gpiolib-legacy.c | 3 +-- include/linux/gpio.h | 7 ++----- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/arch/arm/mach-ep93xx/vision_ep9307.c b/arch/arm/mach-ep93xx/vision_ep9307.c index 9471938df64c..02c5a4724192 100644 --- a/arch/arm/mach-ep93xx/vision_ep9307.c +++ b/arch/arm/mach-ep93xx/vision_ep9307.c @@ -76,8 +76,7 @@ static int vision_lcd_setup(struct platform_device *pdev) { int err; - err = gpio_request_one(VISION_LCD_ENABLE, GPIOF_INIT_HIGH, - dev_name(&pdev->dev)); + err = gpio_request_one(VISION_LCD_ENABLE, GPIOF_OUT_INIT_HIGH, dev_name(&pdev->dev)); if (err) return err; diff --git a/arch/mips/bcm63xx/boards/board_bcm963xx.c b/arch/mips/bcm63xx/boards/board_bcm963xx.c index 99f321b6e417..9cc8fbf218a5 100644 --- a/arch/mips/bcm63xx/boards/board_bcm963xx.c +++ b/arch/mips/bcm63xx/boards/board_bcm963xx.c @@ -42,7 +42,7 @@ static struct board_info __initdata board_cvg834g = { .expected_cpu_id = 0x3368, .ephy_reset_gpio = 36, - .ephy_reset_gpio_flags = GPIOF_INIT_HIGH, + .ephy_reset_gpio_flags = GPIOF_OUT_INIT_HIGH, .has_pci = 1, .has_uart0 = 1, .has_uart1 = 1, diff --git a/drivers/gpio/gpiolib-legacy.c b/drivers/gpio/gpiolib-legacy.c index 5a9911ae9125..354dd0cc8f2f 100644 --- a/drivers/gpio/gpiolib-legacy.c +++ b/drivers/gpio/gpiolib-legacy.c @@ -43,8 +43,7 @@ int gpio_request_one(unsigned gpio, unsigned long flags, const char *label) if (flags & GPIOF_DIR_IN) err = gpiod_direction_input(desc); else - err = gpiod_direction_output_raw(desc, - (flags & GPIOF_INIT_HIGH) ? 1 : 0); + err = gpiod_direction_output_raw(desc, !!(flags & GPIOF_OUT_INIT_HIGH)); if (err) goto free_gpio; diff --git a/include/linux/gpio.h b/include/linux/gpio.h index 063f71b18a7c..4af8ad114557 100644 --- a/include/linux/gpio.h +++ b/include/linux/gpio.h @@ -20,12 +20,9 @@ struct device; #define GPIOF_DIR_OUT (0 << 0) #define GPIOF_DIR_IN (1 << 0) -#define GPIOF_INIT_LOW (0 << 1) -#define GPIOF_INIT_HIGH (1 << 1) - #define GPIOF_IN (GPIOF_DIR_IN) -#define GPIOF_OUT_INIT_LOW (GPIOF_DIR_OUT | GPIOF_INIT_LOW) -#define GPIOF_OUT_INIT_HIGH (GPIOF_DIR_OUT | GPIOF_INIT_HIGH) +#define GPIOF_OUT_INIT_LOW (GPIOF_DIR_OUT | (0 << 1)) +#define GPIOF_OUT_INIT_HIGH (GPIOF_DIR_OUT | (1 << 1)) /* Gpio pin is active-low */ #define GPIOF_ACTIVE_LOW (1 << 2) From 8c045ca534d03bab1ce4b4de49d29a36276a1e35 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 28 Aug 2024 17:23:58 +0300 Subject: [PATCH 291/573] gpiolib: legacy: Kill GPIOF_DIR_* definitions Besides the fact that (old) drivers use wrong definitions, e.g., GPIOF_DIR_IN instead of GPIOF_IN, shrink the legacy definitions by killing those GPIOF_DIR_* completely. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Acked-by: Alexander Sverdlin Reviewed-by: Alexander Sverdlin Link: https://lore.kernel.org/r/20240828142554.2424189-3-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- arch/arm/mach-ep93xx/vision_ep9307.c | 3 +-- drivers/gpio/gpiolib-legacy.c | 2 +- include/linux/gpio.h | 9 +++------ 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/arch/arm/mach-ep93xx/vision_ep9307.c b/arch/arm/mach-ep93xx/vision_ep9307.c index 02c5a4724192..85f0dd7255a9 100644 --- a/arch/arm/mach-ep93xx/vision_ep9307.c +++ b/arch/arm/mach-ep93xx/vision_ep9307.c @@ -292,8 +292,7 @@ static void __init vision_init_machine(void) * Request the gpio expander's interrupt gpio line now to prevent * the kernel from doing a WARN in gpiolib:gpio_ensure_requested(). */ - if (gpio_request_one(EP93XX_GPIO_LINE_F(7), GPIOF_DIR_IN, - "pca9539:74")) + if (gpio_request_one(EP93XX_GPIO_LINE_F(7), GPIOF_IN, "pca9539:74")) pr_warn("cannot request interrupt gpio for pca9539:74\n"); vision_i2c_info[1].irq = gpio_to_irq(EP93XX_GPIO_LINE_F(7)); diff --git a/drivers/gpio/gpiolib-legacy.c b/drivers/gpio/gpiolib-legacy.c index 354dd0cc8f2f..f421208ddbdd 100644 --- a/drivers/gpio/gpiolib-legacy.c +++ b/drivers/gpio/gpiolib-legacy.c @@ -40,7 +40,7 @@ int gpio_request_one(unsigned gpio, unsigned long flags, const char *label) if (flags & GPIOF_ACTIVE_LOW) set_bit(FLAG_ACTIVE_LOW, &desc->flags); - if (flags & GPIOF_DIR_IN) + if (flags & GPIOF_IN) err = gpiod_direction_input(desc); else err = gpiod_direction_output_raw(desc, !!(flags & GPIOF_OUT_INIT_HIGH)); diff --git a/include/linux/gpio.h b/include/linux/gpio.h index 4af8ad114557..2d105be7bbc3 100644 --- a/include/linux/gpio.h +++ b/include/linux/gpio.h @@ -17,12 +17,9 @@ struct device; /* make these flag values available regardless of GPIO kconfig options */ -#define GPIOF_DIR_OUT (0 << 0) -#define GPIOF_DIR_IN (1 << 0) - -#define GPIOF_IN (GPIOF_DIR_IN) -#define GPIOF_OUT_INIT_LOW (GPIOF_DIR_OUT | (0 << 1)) -#define GPIOF_OUT_INIT_HIGH (GPIOF_DIR_OUT | (1 << 1)) +#define GPIOF_IN ((1 << 0)) +#define GPIOF_OUT_INIT_LOW ((0 << 0) | (0 << 1)) +#define GPIOF_OUT_INIT_HIGH ((0 << 0) | (1 << 1)) /* Gpio pin is active-low */ #define GPIOF_ACTIVE_LOW (1 << 2) From 0cbda0499a2797278aa067e98c5221948494356d Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 28 Aug 2024 11:35:57 +0200 Subject: [PATCH 292/573] dt-bindings: gpio: fcs,fxl6408: add missing type to GPIO hogs GPIO hog nodes should define type, otherwise "dummy-hog" boolean properties would be allowed. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Linus Walleij Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20240828-dt-bindings-gpio-hog-v1-1-63b83e47d804@linaro.org Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/fcs,fxl6408.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/gpio/fcs,fxl6408.yaml b/Documentation/devicetree/bindings/gpio/fcs,fxl6408.yaml index 65b6970e42fb..b74fa81e7d05 100644 --- a/Documentation/devicetree/bindings/gpio/fcs,fxl6408.yaml +++ b/Documentation/devicetree/bindings/gpio/fcs,fxl6408.yaml @@ -28,6 +28,7 @@ properties: patternProperties: "^(hog-[0-9]+|.+-hog(-[0-9]+)?)$": + type: object required: - gpio-hog From a0c479bfff034c183bfe9f47a313b8aebc7f96f8 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 28 Aug 2024 11:35:59 +0200 Subject: [PATCH 293/573] dt-bindings: gpio: simplify GPIO hog nodes schema The core schema in dtschema already strictly defines contents of nodes with "gpio-hog" property (with additionalProperties: false), thus the only thing device schema should do is: define "type: object" and required "gpio-hog". Make the code a bit simpler by removing redundant parts. Reviewed-by: Rob Herring (Arm) Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240828-dt-bindings-gpio-hog-v1-3-63b83e47d804@linaro.org Signed-off-by: Bartosz Golaszewski --- .../devicetree/bindings/gpio/fairchild,74hc595.yaml | 11 ----------- .../devicetree/bindings/gpio/fsl-imx-gpio.yaml | 11 ----------- .../devicetree/bindings/gpio/gpio-pca95xx.yaml | 11 ----------- .../bindings/gpio/microchip,mpfs-gpio.yaml | 12 ------------ .../bindings/gpio/socionext,uniphier-gpio.yaml | 11 ----------- 5 files changed, 56 deletions(-) diff --git a/Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml b/Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml index c0ad70e66f76..e8bc9f018edb 100644 --- a/Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml +++ b/Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml @@ -36,19 +36,8 @@ properties: patternProperties: "^(hog-[0-9]+|.+-hog(-[0-9]+)?)$": type: object - - properties: - gpio-hog: true - gpios: true - output-high: true - output-low: true - line-name: true - required: - gpio-hog - - gpios - - additionalProperties: false required: - compatible diff --git a/Documentation/devicetree/bindings/gpio/fsl-imx-gpio.yaml b/Documentation/devicetree/bindings/gpio/fsl-imx-gpio.yaml index e1fc8bb6d379..6b06609c649e 100644 --- a/Documentation/devicetree/bindings/gpio/fsl-imx-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/fsl-imx-gpio.yaml @@ -85,19 +85,8 @@ properties: patternProperties: "^(hog-[0-9]+|.+-hog(-[0-9]+)?)$": type: object - properties: - gpio-hog: true - gpios: true - input: true - output-high: true - output-low: true - line-name: true - required: - gpio-hog - - gpios - - additionalProperties: false required: - compatible diff --git a/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml b/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml index 51e8390d6b32..7b1eb08fa055 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml +++ b/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml @@ -107,19 +107,8 @@ properties: patternProperties: "^(hog-[0-9]+|.+-hog(-[0-9]+)?)$": type: object - properties: - gpio-hog: true - gpios: true - input: true - output-high: true - output-low: true - line-name: true - required: - gpio-hog - - gpios - - additionalProperties: false required: - compatible diff --git a/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml b/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml index d61569b3f15b..d78da7dd2a56 100644 --- a/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/microchip,mpfs-gpio.yaml @@ -49,20 +49,8 @@ properties: patternProperties: "^.+-hog(-[0-9]+)?$": type: object - - additionalProperties: false - - properties: - gpio-hog: true - gpios: true - input: true - output-high: true - output-low: true - line-name: true - required: - gpio-hog - - gpios allOf: - if: diff --git a/Documentation/devicetree/bindings/gpio/socionext,uniphier-gpio.yaml b/Documentation/devicetree/bindings/gpio/socionext,uniphier-gpio.yaml index 228fa27ffdc3..36f5a0610471 100644 --- a/Documentation/devicetree/bindings/gpio/socionext,uniphier-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/socionext,uniphier-gpio.yaml @@ -55,19 +55,8 @@ properties: patternProperties: "^.+-hog(-[0-9]+)?$": type: object - properties: - gpio-hog: true - gpios: true - input: true - output-high: true - output-low: true - line-name: true - required: - gpio-hog - - gpios - - additionalProperties: false required: - compatible From 931a36c4138ac418d487bd4db0d03780b46a77ba Mon Sep 17 00:00:00 2001 From: zhangjiao Date: Thu, 29 Aug 2024 14:29:42 +0800 Subject: [PATCH 294/573] tools: gpio: rm .*.cmd on make clean rm .*.cmd when calling make clean Signed-off-by: zhangjiao Link: https://lore.kernel.org/r/20240829062942.11487-1-zhangjiao2@cmss.chinamobile.com Signed-off-by: Bartosz Golaszewski --- tools/gpio/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/gpio/Makefile b/tools/gpio/Makefile index d29c9c49e251..ed565eb52275 100644 --- a/tools/gpio/Makefile +++ b/tools/gpio/Makefile @@ -78,7 +78,7 @@ $(OUTPUT)gpio-watch: $(GPIO_WATCH_IN) clean: rm -f $(ALL_PROGRAMS) rm -f $(OUTPUT)include/linux/gpio.h - find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete + find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete -o -name '\.*.cmd' -delete install: $(ALL_PROGRAMS) install -d -m 755 $(DESTDIR)$(bindir); \ From 30a32e93117abf8b467897f4814e9b1261b3b803 Mon Sep 17 00:00:00 2001 From: Hongbo Li Date: Wed, 28 Aug 2024 20:20:39 +0800 Subject: [PATCH 295/573] gpio: Use IS_ERR_OR_NULL() helper function Use the IS_ERR_OR_NULL() helper instead of open-coding a NULL and an error pointer checks to simplify the code and improve readability. No functional changes are intended. Signed-off-by: Hongbo Li Link: https://lore.kernel.org/r/20240828122039.3697037-1-lihongbo22@huawei.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 3903d0a75304..de425db71111 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -356,7 +356,7 @@ int gpiod_get_direction(struct gpio_desc *desc) * We cannot use VALIDATE_DESC() as we must not return 0 for a NULL * descriptor like we usually do. */ - if (!desc || IS_ERR(desc)) + if (IS_ERR_OR_NULL(desc)) return -EINVAL; CLASS(gpio_chip_guard, guard)(desc); @@ -3591,7 +3591,7 @@ int gpiod_to_irq(const struct gpio_desc *desc) * requires this function to not return zero on an invalid descriptor * but rather a negative error number. */ - if (!desc || IS_ERR(desc)) + if (IS_ERR_OR_NULL(desc)) return -EINVAL; gdev = desc->gdev; From c1e4e5dc9bbf995e81884b2988502fded54542e4 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 23 Aug 2024 01:38:45 +0300 Subject: [PATCH 296/573] gpio: tegra: Replace of_node_to_fwnode() with more suitable API of_node_to_fwnode() is a IRQ domain specific implementation of of_fwnode_handle(). Replace the former with more suitable API. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240822223845.706346-1-andy.shevchenko@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tegra.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-tegra.c b/drivers/gpio/gpio-tegra.c index ea5f9cc14bc4..6d3a39a03f58 100644 --- a/drivers/gpio/gpio-tegra.c +++ b/drivers/gpio/gpio-tegra.c @@ -18,11 +18,12 @@ #include #include #include -#include #include #include #include #include +#include +#include #define GPIO_BANK(x) ((x) >> 5) #define GPIO_PORT(x) (((x) >> 3) & 0x3) @@ -755,7 +756,7 @@ static int tegra_gpio_probe(struct platform_device *pdev) } irq = &tgi->gc.irq; - irq->fwnode = of_node_to_fwnode(pdev->dev.of_node); + irq->fwnode = dev_fwnode(&pdev->dev); irq->child_to_parent_hwirq = tegra_gpio_child_to_parent_hwirq; irq->populate_parent_alloc_arg = tegra_gpio_populate_parent_fwspec; irq->handler = handle_simple_irq; From f5c4a495b189a4438a06fa0781f11dabdf5b28e0 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 23 Aug 2024 01:41:30 +0300 Subject: [PATCH 297/573] gpio: msc313: Replace of_node_to_fwnode() with more suitable API of_node_to_fwnode() is a IRQ domain specific implementation of of_fwnode_handle(). Replace the former with more suitable API. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240822224130.706564-1-andy.shevchenko@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-msc313.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-msc313.c b/drivers/gpio/gpio-msc313.c index 2f448eb23abb..6db9e469e0dc 100644 --- a/drivers/gpio/gpio-msc313.c +++ b/drivers/gpio/gpio-msc313.c @@ -3,13 +3,14 @@ #include #include -#include #include #include #include #include #include #include +#include +#include #include #include @@ -662,7 +663,7 @@ static int msc313_gpio_probe(struct platform_device *pdev) gpioirqchip = &gpiochip->irq; gpio_irq_chip_set_chip(gpioirqchip, &msc313_gpio_irqchip); - gpioirqchip->fwnode = of_node_to_fwnode(dev->of_node); + gpioirqchip->fwnode = dev_fwnode(dev); gpioirqchip->parent_domain = parent_domain; gpioirqchip->child_to_parent_hwirq = msc313e_gpio_child_to_parent_hwirq; gpioirqchip->populate_parent_alloc_arg = msc313_gpio_populate_parent_fwspec; From 9f12737342291ea5b657a79c35d24b33983668be Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 23 Aug 2024 01:56:29 +0300 Subject: [PATCH 298/573] gpio: uniphier: Replace of_node_to_fwnode() with more suitable API of_node_to_fwnode() is a IRQ domain specific implementation of of_fwnode_handle(). Replace the former with more suitable API. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240822225629.707365-1-andy.shevchenko@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-uniphier.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-uniphier.c b/drivers/gpio/gpio-uniphier.c index 1f440707f8f4..da99ba13e82d 100644 --- a/drivers/gpio/gpio-uniphier.c +++ b/drivers/gpio/gpio-uniphier.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -164,7 +165,7 @@ static int uniphier_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) if (offset < UNIPHIER_GPIO_IRQ_OFFSET) return -ENXIO; - fwspec.fwnode = of_node_to_fwnode(chip->parent->of_node); + fwspec.fwnode = dev_fwnode(chip->parent); fwspec.param_count = 2; fwspec.param[0] = offset - UNIPHIER_GPIO_IRQ_OFFSET; /* @@ -404,7 +405,7 @@ static int uniphier_gpio_probe(struct platform_device *pdev) priv->domain = irq_domain_create_hierarchy( parent_domain, 0, UNIPHIER_GPIO_IRQ_MAX_NUM, - of_node_to_fwnode(dev->of_node), + dev_fwnode(dev), &uniphier_gpio_irq_domain_ops, priv); if (!priv->domain) return -ENOMEM; From 5482f1a5c2003829be84cf4fa9d23b7ec65acf8c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 23 Aug 2024 01:47:37 +0300 Subject: [PATCH 299/573] gpio: tegra186: Replace of_node_to_fwnode() with more suitable API of_node_to_fwnode() is a IRQ domain specific implementation of of_fwnode_handle(). Replace the former with more suitable API. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240822224737.706870-1-andy.shevchenko@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-tegra186.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-tegra186.c b/drivers/gpio/gpio-tegra186.c index 9130c691a2dd..1ecb733a5e88 100644 --- a/drivers/gpio/gpio-tegra186.c +++ b/drivers/gpio/gpio-tegra186.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -928,7 +929,7 @@ static int tegra186_gpio_probe(struct platform_device *pdev) irq = &gpio->gpio.irq; gpio_irq_chip_set_chip(irq, &tegra186_gpio_irq_chip); - irq->fwnode = of_node_to_fwnode(pdev->dev.of_node); + irq->fwnode = dev_fwnode(&pdev->dev); irq->child_to_parent_hwirq = tegra186_gpio_child_to_parent_hwirq; irq->populate_parent_alloc_arg = tegra186_gpio_populate_parent_fwspec; irq->child_offset_to_irq = tegra186_gpio_child_offset_to_irq; From 6d6395cd500fccd407edf7480ea7d53ea4cf8345 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 23 Aug 2024 01:53:00 +0300 Subject: [PATCH 300/573] gpio: thunderx: Replace of_node_to_fwnode() with more suitable API of_node_to_fwnode() is a IRQ domain specific implementation of of_fwnode_handle(). Replace the former with more suitable API. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240822225300.707178-1-andy.shevchenko@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-thunderx.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-thunderx.c b/drivers/gpio/gpio-thunderx.c index 8521c6aacace..5b851e904c11 100644 --- a/drivers/gpio/gpio-thunderx.c +++ b/drivers/gpio/gpio-thunderx.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #define GPIO_RX_DAT 0x0 @@ -533,7 +534,7 @@ static int thunderx_gpio_probe(struct pci_dev *pdev, chip->set_config = thunderx_gpio_set_config; girq = &chip->irq; gpio_irq_chip_set_chip(girq, &thunderx_gpio_irq_chip); - girq->fwnode = of_node_to_fwnode(dev->of_node); + girq->fwnode = dev_fwnode(dev); girq->parent_domain = irq_get_irq_data(txgpio->msix_entries[0].vector)->domain; girq->child_to_parent_hwirq = thunderx_gpio_child_to_parent_hwirq; @@ -549,7 +550,7 @@ static int thunderx_gpio_probe(struct pci_dev *pdev, for (i = 0; i < ngpio; i++) { struct irq_fwspec fwspec; - fwspec.fwnode = of_node_to_fwnode(dev->of_node); + fwspec.fwnode = dev_fwnode(dev); fwspec.param_count = 2; fwspec.param[0] = i; fwspec.param[1] = IRQ_TYPE_NONE; From 35ea26245ec24f89491fec945a15c0b2ae60bab9 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 23 Aug 2024 01:58:18 +0300 Subject: [PATCH 301/573] gpio: visconti: Replace of_node_to_fwnode() with more suitable API of_node_to_fwnode() is a IRQ domain specific implementation of of_fwnode_handle(). Replace the former with more suitable API. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240822225818.707550-1-andy.shevchenko@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-visconti.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-visconti.c b/drivers/gpio/gpio-visconti.c index 6734e7e1e2a4..ebc71ecdb6cf 100644 --- a/drivers/gpio/gpio-visconti.c +++ b/drivers/gpio/gpio-visconti.c @@ -8,6 +8,7 @@ * Nobuhiro Iwamatsu */ +#include #include #include #include @@ -15,8 +16,8 @@ #include #include #include +#include #include -#include /* register offset */ #define GPIO_DIR 0x00 @@ -202,7 +203,7 @@ static int visconti_gpio_probe(struct platform_device *pdev) girq = &priv->gpio_chip.irq; gpio_irq_chip_set_chip(girq, &visconti_gpio_irq_chip); - girq->fwnode = of_node_to_fwnode(dev->of_node); + girq->fwnode = dev_fwnode(dev); girq->parent_domain = parent; girq->child_to_parent_hwirq = visconti_gpio_child_to_parent_hwirq; girq->populate_parent_alloc_arg = visconti_gpio_populate_parent_fwspec; From 1e3d42f508ee03ce5c515fc72ef9dcb65212d221 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 23 Aug 2024 01:33:32 +0300 Subject: [PATCH 302/573] gpio: ixp4xx: Replace of_node_to_fwnode() with more suitable API of_node_to_fwnode() is a IRQ domain specific implementation of of_fwnode_handle(). Replace the former with more suitable API. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20240822223332.705560-1-andy.shevchenko@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ixp4xx.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/gpio-ixp4xx.c b/drivers/gpio/gpio-ixp4xx.c index c5a9fa640566..28a8a6a8f05f 100644 --- a/drivers/gpio/gpio-ixp4xx.c +++ b/drivers/gpio/gpio-ixp4xx.c @@ -6,6 +6,7 @@ // based on previous work and know-how from: // Deepak Saxena +#include #include #include #include @@ -13,7 +14,7 @@ #include #include #include -#include +#include #define IXP4XX_REG_GPOUT 0x00 #define IXP4XX_REG_GPOE 0x04 @@ -53,16 +54,14 @@ /** * struct ixp4xx_gpio - IXP4 GPIO state container * @dev: containing device for this instance - * @fwnode: the fwnode for this GPIO chip * @gc: gpiochip for this instance * @base: remapped I/O-memory base * @irq_edge: Each bit represents an IRQ: 1: edge-triggered, * 0: level triggered */ struct ixp4xx_gpio { - struct device *dev; - struct fwnode_handle *fwnode; struct gpio_chip gc; + struct device *dev; void __iomem *base; unsigned long long irq_edge; }; @@ -237,7 +236,6 @@ static int ixp4xx_gpio_probe(struct platform_device *pdev) dev_err(dev, "no IRQ parent domain\n"); return -ENODEV; } - g->fwnode = of_node_to_fwnode(np); /* * If either clock output is enabled explicitly in the device tree @@ -322,7 +320,7 @@ static int ixp4xx_gpio_probe(struct platform_device *pdev) girq = &g->gc.irq; gpio_irq_chip_set_chip(girq, &ixp4xx_gpio_irqchip); - girq->fwnode = g->fwnode; + girq->fwnode = dev_fwnode(dev); girq->parent_domain = parent; girq->child_to_parent_hwirq = ixp4xx_gpio_child_to_parent_hwirq; girq->handler = handle_bad_irq; From 94bd9ce16063a264c85f3b5907fefd8ec979d214 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 28 Aug 2024 19:41:35 +0300 Subject: [PATCH 303/573] gpiolib: Update the kernel documentation - add Return sections $ scripts/kernel-doc -v -none -Wall drivers/gpio/gpiolib* 2>&1 | grep -w warning | wc -l 67 Fix these by adding Return sections. While at it, make sure all of Return sections use the same style. Signed-off-by: Andy Shevchenko Reviewed-by: Randy Dunlap Tested-by: Randy Dunlap Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240828164449.2777666-1-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-acpi.c | 22 +++-- drivers/gpio/gpiolib-cdev.c | 8 +- drivers/gpio/gpiolib-devres.c | 38 +++++++- drivers/gpio/gpiolib-legacy.c | 3 + drivers/gpio/gpiolib-of.c | 48 ++++++++--- drivers/gpio/gpiolib-swnode.c | 2 +- drivers/gpio/gpiolib-sysfs.c | 6 +- drivers/gpio/gpiolib.c | 157 +++++++++++++++++++++++++++------- 8 files changed, 226 insertions(+), 58 deletions(-) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index cf4b1f068bac..78ecd56123a3 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -153,8 +153,12 @@ static int acpi_gpiochip_find(struct gpio_chip *gc, const void *data) * @path: ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1") * @pin: ACPI GPIO pin number (0-based, controller-relative) * - * Return: GPIO descriptor to use with Linux generic GPIO API, or ERR_PTR - * error value. Specifically returns %-EPROBE_DEFER if the referenced GPIO + * Returns: + * GPIO descriptor to use with Linux generic GPIO API. + * If the GPIO cannot be translated or there is an error an ERR_PTR is + * returned. + * + * Specifically returns %-EPROBE_DEFER if the referenced GPIO * controller does not have GPIO chip registered at the moment. This is to * support probe deferral. */ @@ -224,6 +228,9 @@ EXPORT_SYMBOL_GPL(acpi_gpio_get_irq_resource); * I/O resource or return False if not. * @ares: Pointer to the ACPI resource to fetch * @agpio: Pointer to a &struct acpi_resource_gpio to store the output pointer + * + * Returns: + * %true if GpioIo resource is found, %false otherwise. */ bool acpi_gpio_get_io_resource(struct acpi_resource *ares, struct acpi_resource_gpio **agpio) @@ -876,7 +883,9 @@ static int acpi_gpio_property_lookup(struct fwnode_handle *fwnode, * that case @index is used to select the GPIO entry in the property value * (in case of multiple). * - * If the GPIO cannot be translated or there is an error, an ERR_PTR is + * Returns: + * GPIO descriptor to use with Linux generic GPIO API. + * If the GPIO cannot be translated or there is an error an ERR_PTR is * returned. * * Note: if the GPIO resource has multiple entries in the pin list, this @@ -924,6 +933,8 @@ static struct gpio_desc *acpi_get_gpiod_by_index(struct acpi_device *adev, * resource with the relevant information from a data-only ACPI firmware node * and uses that to obtain the GPIO descriptor to return. * + * Returns: + * GPIO descriptor to use with Linux generic GPIO API. * If the GPIO cannot be translated or there is an error an ERR_PTR is * returned. */ @@ -1042,7 +1053,8 @@ struct gpio_desc *acpi_find_gpio(struct fwnode_handle *fwnode, * The GPIO is considered wake capable if the GpioInt resource specifies * SharedAndWake or ExclusiveAndWake. * - * Return: Linux IRQ number (> %0) on success, negative errno on failure. + * Returns: + * Linux IRQ number (> 0) on success, negative errno on failure. */ int acpi_dev_gpio_irq_wake_get_by(struct acpi_device *adev, const char *con_id, int index, bool *wake_capable) @@ -1429,7 +1441,7 @@ static int acpi_find_gpio_count(struct acpi_resource *ares, void *data) * @fwnode: firmware node of the GPIO consumer * @con_id: function within the GPIO consumer * - * Return: + * Returns: * The number of GPIOs associated with a firmware node / function or %-ENOENT, * if no GPIO has been assigned to the requested function. */ diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index ef08b23a56e2..5aac59de0d76 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2748,7 +2748,9 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf, * gpio_chrdev_open() - open the chardev for ioctl operations * @inode: inode for this chardev * @file: file struct for storing private data - * Returns 0 on success + * + * Returns: + * 0 on success, or negative errno on failure. */ static int gpio_chrdev_open(struct inode *inode, struct file *file) { @@ -2814,7 +2816,9 @@ out_free_cdev: * gpio_chrdev_release() - close chardev after ioctl operations * @inode: inode for this chardev * @file: file struct for storing private data - * Returns 0 on success + * + * Returns: + * 0 on success, or negative errno on failure. */ static int gpio_chrdev_release(struct inode *inode, struct file *file) { diff --git a/drivers/gpio/gpiolib-devres.c b/drivers/gpio/gpiolib-devres.c index 4987e62dcb3d..0fdf1356e717 100644 --- a/drivers/gpio/gpiolib-devres.c +++ b/drivers/gpio/gpiolib-devres.c @@ -52,6 +52,11 @@ static int devm_gpiod_match_array(struct device *dev, void *res, void *data) * Managed gpiod_get(). GPIO descriptors returned from this function are * automatically disposed on driver detach. See gpiod_get() for detailed * information about behavior and return values. + * + * Returns: + * The GPIO descriptor corresponding to the function @con_id of device + * dev, %-ENOENT if no GPIO has been assigned to the requested function, or + * another IS_ERR() code if an error occurred while trying to acquire the GPIO. */ struct gpio_desc *__must_check devm_gpiod_get(struct device *dev, const char *con_id, @@ -70,6 +75,11 @@ EXPORT_SYMBOL_GPL(devm_gpiod_get); * Managed gpiod_get_optional(). GPIO descriptors returned from this function * are automatically disposed on driver detach. See gpiod_get_optional() for * detailed information about behavior and return values. + * + * Returns: + * The GPIO descriptor corresponding to the function @con_id of device + * dev, NULL if no GPIO has been assigned to the requested function, or + * another IS_ERR() code if an error occurred while trying to acquire the GPIO. */ struct gpio_desc *__must_check devm_gpiod_get_optional(struct device *dev, const char *con_id, @@ -89,6 +99,11 @@ EXPORT_SYMBOL_GPL(devm_gpiod_get_optional); * Managed gpiod_get_index(). GPIO descriptors returned from this function are * automatically disposed on driver detach. See gpiod_get_index() for detailed * information about behavior and return values. + * + * Returns: + * The GPIO descriptor corresponding to the function @con_id of device + * dev, %-ENOENT if no GPIO has been assigned to the requested function, or + * another IS_ERR() code if an error occurred while trying to acquire the GPIO. */ struct gpio_desc *__must_check devm_gpiod_get_index(struct device *dev, const char *con_id, @@ -141,8 +156,10 @@ EXPORT_SYMBOL_GPL(devm_gpiod_get_index); * GPIO descriptors returned from this function are automatically disposed on * driver detach. * - * On successful request the GPIO pin is configured in accordance with - * provided @flags. + * Returns: + * The GPIO descriptor corresponding to the function @con_id of device + * dev, %-ENOENT if no GPIO has been assigned to the requested function, or + * another IS_ERR() code if an error occurred while trying to acquire the GPIO. */ struct gpio_desc *devm_fwnode_gpiod_get_index(struct device *dev, struct fwnode_handle *fwnode, @@ -182,6 +199,11 @@ EXPORT_SYMBOL_GPL(devm_fwnode_gpiod_get_index); * function are automatically disposed on driver detach. See * gpiod_get_index_optional() for detailed information about behavior and * return values. + * + * Returns: + * The GPIO descriptor corresponding to the function @con_id of device + * dev, %NULL if no GPIO has been assigned to the requested function, or + * another IS_ERR() code if an error occurred while trying to acquire the GPIO. */ struct gpio_desc *__must_check devm_gpiod_get_index_optional(struct device *dev, const char *con_id, @@ -207,6 +229,12 @@ EXPORT_SYMBOL_GPL(devm_gpiod_get_index_optional); * Managed gpiod_get_array(). GPIO descriptors returned from this function are * automatically disposed on driver detach. See gpiod_get_array() for detailed * information about behavior and return values. + * + * Returns: + * The GPIO descriptors corresponding to the function @con_id of device + * dev, %-ENOENT if no GPIO has been assigned to the requested function, + * or another IS_ERR() code if an error occurred while trying to acquire + * the GPIOs. */ struct gpio_descs *__must_check devm_gpiod_get_array(struct device *dev, const char *con_id, @@ -243,6 +271,12 @@ EXPORT_SYMBOL_GPL(devm_gpiod_get_array); * function are automatically disposed on driver detach. * See gpiod_get_array_optional() for detailed information about behavior and * return values. + * + * Returns: + * The GPIO descriptors corresponding to the function @con_id of device + * dev, %NULL if no GPIO has been assigned to the requested function, + * or another IS_ERR() code if an error occurred while trying to acquire + * the GPIOs. */ struct gpio_descs *__must_check devm_gpiod_get_array_optional(struct device *dev, const char *con_id, diff --git a/drivers/gpio/gpiolib-legacy.c b/drivers/gpio/gpiolib-legacy.c index f421208ddbdd..087fe3227e35 100644 --- a/drivers/gpio/gpiolib-legacy.c +++ b/drivers/gpio/gpiolib-legacy.c @@ -22,6 +22,9 @@ EXPORT_SYMBOL_GPL(gpio_free); * @label: a literal description string of this GPIO * * **DEPRECATED** This function is deprecated and must not be used in new code. + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpio_request_one(unsigned gpio, unsigned long flags, const char *label) { diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index d0d78e0fa28c..880f1efcaca5 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -46,16 +46,19 @@ enum of_gpio_flags { * @propname: property name containing gpio specifier(s) * * The function returns the count of GPIOs specified for a node. - * Note that the empty GPIO specifiers count too. Returns either - * Number of gpios defined in property, - * -EINVAL for an incorrectly formed gpios property, or - * -ENOENT for a missing gpios property + * NOTE: The empty GPIO specifiers count too. * - * Example: - * gpios = <0 - * &gpio1 1 2 - * 0 - * &gpio2 3 4>; + * Returns: + * Either number of GPIOs defined in the property, or + * * %-EINVAL for an incorrectly formed "gpios" property, or + * * %-ENOENT for a missing "gpios" property. + * + * Example:: + * + * gpios = <0 + * &gpio1 1 2 + * 0 + * &gpio2 3 4>; * * The above example defines four GPIOs, two of which are not specified. * This function will return '4' @@ -77,6 +80,11 @@ static int of_gpio_named_count(const struct device_node *np, * "gpios" for the chip select lines. If we detect this, we redirect * the counting of "cs-gpios" to count "gpios" transparent to the * driver. + * + * Returns: + * Either number of GPIOs defined in the property, or + * * %-EINVAL for an incorrectly formed "gpios" property, or + * * %-ENOENT for a missing "gpios" property. */ static int of_gpio_spi_cs_get_count(const struct device_node *np, const char *con_id) @@ -373,7 +381,8 @@ static void of_gpio_flags_quirks(const struct device_node *np, * @index: index of the GPIO * @flags: a flags pointer to fill in * - * Returns GPIO descriptor to use with Linux GPIO API, or one of the errno + * Returns: + * GPIO descriptor to use with Linux GPIO API, or one of the errno * value on the error condition. If @flags is not NULL the function also fills * in flags for the GPIO. */ @@ -425,7 +434,8 @@ out: * * **DEPRECATED** This function is deprecated and must not be used in new code. * - * Returns GPIO number to use with Linux generic GPIO API, or one of the errno + * Returns: + * GPIO number to use with Linux generic GPIO API, or one of the errno * value on the error condition. */ int of_get_named_gpio(const struct device_node *np, const char *propname, @@ -711,7 +721,8 @@ struct gpio_desc *of_find_gpio(struct device_node *np, const char *con_id, * of_find_gpio() or of_parse_own_gpio() * @dflags: gpiod_flags - optional GPIO initialization flags * - * Returns GPIO descriptor to use with Linux GPIO API, or one of the errno + * Returns: + * GPIO descriptor to use with Linux GPIO API, or one of the errno * value on the error condition. */ static struct gpio_desc *of_parse_own_gpio(struct device_node *np, @@ -779,7 +790,8 @@ static struct gpio_desc *of_parse_own_gpio(struct device_node *np, * @chip: gpio chip to act on * @hog: device node describing the hogs * - * Returns error if it fails otherwise 0 on success. + * Returns: + * 0 on success, or negative errno on failure. */ static int of_gpiochip_add_hog(struct gpio_chip *chip, struct device_node *hog) { @@ -813,7 +825,9 @@ static int of_gpiochip_add_hog(struct gpio_chip *chip, struct device_node *hog) * * This is only used by of_gpiochip_add to request/set GPIO initial * configuration. - * It returns error if it fails otherwise 0 on success. + * + * Returns: + * 0 on success, or negative errno on failure. */ static int of_gpiochip_scan_gpios(struct gpio_chip *chip) { @@ -923,6 +937,9 @@ struct notifier_block gpio_of_notifier = { * This is simple translation function, suitable for the most 1:1 mapped * GPIO chips. This function performs only one sanity check: whether GPIO * is less than ngpios (that is specified in the gpio_chip). + * + * Returns: + * GPIO number (>= 0) on success, negative errno on failure. */ static int of_gpio_simple_xlate(struct gpio_chip *gc, const struct of_phandle_args *gpiospec, @@ -972,6 +989,9 @@ static int of_gpio_simple_xlate(struct gpio_chip *gc, * If succeeded, this function will map bank's memory and will * do all necessary work for you. Then you'll able to use .regs * to manage GPIOs from the callbacks. + * + * Returns: + * 0 on success, or negative errno on failure. */ int of_mm_gpiochip_add_data(struct device_node *np, struct of_mm_gpio_chip *mm_gc, diff --git a/drivers/gpio/gpiolib-swnode.c b/drivers/gpio/gpiolib-swnode.c index 1a6f70671816..2b2dd7e92211 100644 --- a/drivers/gpio/gpiolib-swnode.c +++ b/drivers/gpio/gpiolib-swnode.c @@ -106,7 +106,7 @@ struct gpio_desc *swnode_find_gpio(struct fwnode_handle *fwnode, * system-global GPIOs * @con_id: function within the GPIO consumer * - * Return: + * Returns: * The number of GPIOs associated with a device / function or %-ENOENT, * if no GPIO has been assigned to the requested function. */ diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 26202586fd39..17ed229412af 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -568,7 +568,8 @@ static struct class gpio_class = { * will see "direction" sysfs attribute which may be used to change * the gpio's direction. A "value" attribute will always be provided. * - * Returns zero on success, else an error. + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_export(struct gpio_desc *desc, bool direction_may_change) { @@ -667,7 +668,8 @@ static int match_export(struct device *dev, const void *desc) * Set up a symlink from /sys/.../dev/name to /sys/class/gpio/gpioN * node. Caller is responsible for unlinking. * - * Returns zero on success, else an error. + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_export_link(struct device *dev, const char *name, struct gpio_desc *desc) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index de425db71111..c6afbf434366 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -230,6 +230,9 @@ EXPORT_SYMBOL_GPL(desc_to_gpio); * This function is unsafe and should not be used. Using the chip address * without taking the SRCU read lock may result in dereferencing a dangling * pointer. + * + * Returns: + * Address of the GPIO chip backing this device. */ struct gpio_chip *gpiod_to_chip(const struct gpio_desc *desc) { @@ -342,7 +345,8 @@ static int gpiochip_find_base_unlocked(u16 ngpio) * gpiod_get_direction - return the current direction of a GPIO * @desc: GPIO to get the direction of * - * Returns 0 for output, 1 for input, or an error code in case of error. + * Returns: + * 0 for output, 1 for input, or an error code in case of error. * * This function may sleep if gpiod_cansleep() is true. */ @@ -399,8 +403,8 @@ EXPORT_SYMBOL_GPL(gpiod_get_direction); * Add a new chip to the global chips list, keeping the list of chips sorted * by range(means [base, base + ngpio - 1]) order. * - * Return -EBUSY if the new chip overlaps with some other chip's integer - * space. + * Returns: + * -EBUSY if the new chip overlaps with some other chip's integer space. */ static int gpiodev_add_to_list_unlocked(struct gpio_device *gdev) { @@ -1516,6 +1520,9 @@ static unsigned int gpiochip_child_offset_to_irq_noop(struct gpio_chip *gc, * This function is a wrapper that calls gpiochip_lock_as_irq() and is to be * used as the activate function for the &struct irq_domain_ops. The host_data * for the IRQ domain must be the &struct gpio_chip. + * + * Returns: + * 0 on success, or negative errno on failure. */ static int gpiochip_irq_domain_activate(struct irq_domain *domain, struct irq_data *data, bool reserve) @@ -1660,6 +1667,9 @@ static bool gpiochip_hierarchy_is_hierarchical(struct gpio_chip *gc) * This function will set up the mapping for a certain IRQ line on a * gpiochip by assigning the gpiochip as chip data, and using the irqchip * stored inside the gpiochip. + * + * Returns: + * 0 on success, or negative errno on failure. */ static int gpiochip_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hwirq) @@ -1894,6 +1904,9 @@ static int gpiochip_irqchip_add_allocated_domain(struct gpio_chip *gc, * @gc: the GPIO chip to add the IRQ chip to * @lock_key: lockdep class for IRQ lock * @request_key: lockdep class for IRQ request + * + * Returns: + * 0 on success, or a negative errno on failure. */ static int gpiochip_add_irqchip(struct gpio_chip *gc, struct lock_class_key *lock_key, @@ -2029,6 +2042,9 @@ static void gpiochip_irqchip_remove(struct gpio_chip *gc) * @domain: the irqdomain to add to the gpiochip * * This function adds an IRQ domain to the gpiochip. + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiochip_irqchip_add_domain(struct gpio_chip *gc, struct irq_domain *domain) @@ -2065,6 +2081,9 @@ static inline void gpiochip_irqchip_free_valid_mask(struct gpio_chip *gc) * gpiochip_generic_request() - request the gpio function for a pin * @gc: the gpiochip owning the GPIO * @offset: the offset of the GPIO to request for GPIO function + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiochip_generic_request(struct gpio_chip *gc, unsigned int offset) { @@ -2098,6 +2117,9 @@ EXPORT_SYMBOL_GPL(gpiochip_generic_free); * @gc: the gpiochip owning the GPIO * @offset: the offset of the GPIO to apply the configuration * @config: the configuration to be applied + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiochip_generic_config(struct gpio_chip *gc, unsigned int offset, unsigned long config) @@ -2124,6 +2146,9 @@ EXPORT_SYMBOL_GPL(gpiochip_generic_config); * pinctrl driver is DEPRECATED. Please see Section 2.1 of * Documentation/devicetree/bindings/gpio/gpio.txt on how to * bind pinctrl and gpio drivers via the "gpio-ranges" property. + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiochip_add_pingroup_range(struct gpio_chip *gc, struct pinctrl_dev *pctldev, @@ -2175,13 +2200,13 @@ EXPORT_SYMBOL_GPL(gpiochip_add_pingroup_range); * @npins: the number of pins from the offset of each pin space (GPIO and * pin controller) to accumulate in this range * - * Returns: - * 0 on success, or a negative error-code on failure. - * * Calling this function directly from a DeviceTree-supported * pinctrl driver is DEPRECATED. Please see Section 2.1 of * Documentation/devicetree/bindings/gpio/gpio.txt on how to * bind pinctrl and gpio drivers via the "gpio-ranges" property. + * + * Returns: + * 0 on success, or a negative errno on failure. */ int gpiochip_add_pin_range(struct gpio_chip *gc, const char *pinctl_name, unsigned int gpio_offset, unsigned int pin_offset, @@ -2585,7 +2610,8 @@ static int gpio_set_bias(struct gpio_desc *desc) * The function calls the certain GPIO driver to set debounce timeout * in the hardware. * - * Returns 0 on success, or negative error code otherwise. + * Returns: + * 0 on success, or negative errno on failure. */ int gpio_set_debounce_timeout(struct gpio_desc *desc, unsigned int debounce) { @@ -2601,7 +2627,8 @@ int gpio_set_debounce_timeout(struct gpio_desc *desc, unsigned int debounce) * Set the direction of the passed GPIO to input, such as gpiod_get_value() can * be called safely on it. * - * Return 0 in case of success, else an error code. + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_direction_input(struct gpio_desc *desc) { @@ -2708,7 +2735,8 @@ static int gpiod_direction_output_raw_commit(struct gpio_desc *desc, int value) * be called safely on it. The initial value of the output must be specified * as raw value on the physical line without regard for the ACTIVE_LOW status. * - * Return 0 in case of success, else an error code. + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_direction_output_raw(struct gpio_desc *desc, int value) { @@ -2727,7 +2755,8 @@ EXPORT_SYMBOL_GPL(gpiod_direction_output_raw); * as the logical value of the GPIO, i.e. taking its ACTIVE_LOW status into * account. * - * Return 0 in case of success, else an error code. + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_direction_output(struct gpio_desc *desc, int value) { @@ -2800,7 +2829,8 @@ EXPORT_SYMBOL_GPL(gpiod_direction_output); * @desc: GPIO to enable. * @flags: Flags related to GPIO edge. * - * Return 0 in case of success, else negative error code. + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_enable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags) { @@ -2832,7 +2862,8 @@ EXPORT_SYMBOL_GPL(gpiod_enable_hw_timestamp_ns); * @desc: GPIO to disable. * @flags: Flags related to GPIO edge, same value as used during enable call. * - * Return 0 in case of success, else negative error code. + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_disable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags) { @@ -2924,7 +2955,8 @@ int gpiod_set_transitory(struct gpio_desc *desc, bool transitory) * gpiod_is_active_low - test whether a GPIO is active-low or not * @desc: the gpio descriptor to test * - * Returns 1 if the GPIO is active-low, 0 otherwise. + * Returns: + * 1 if the GPIO is active-low, 0 otherwise. */ int gpiod_is_active_low(const struct gpio_desc *desc) { @@ -3139,7 +3171,8 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep, * gpiod_get_raw_value() - return a gpio's raw value * @desc: gpio whose value will be returned * - * Return the GPIO's raw value, i.e. the value of the physical line disregarding + * Returns: + * The GPIO's raw value, i.e. the value of the physical line disregarding * its ACTIVE_LOW status, or negative errno on failure. * * This function can be called from contexts where we cannot sleep, and will @@ -3158,7 +3191,8 @@ EXPORT_SYMBOL_GPL(gpiod_get_raw_value); * gpiod_get_value() - return a gpio's value * @desc: gpio whose value will be returned * - * Return the GPIO's logical value, i.e. taking the ACTIVE_LOW status into + * Returns: + * The GPIO's logical value, i.e. taking the ACTIVE_LOW status into * account, or negative errno on failure. * * This function can be called from contexts where we cannot sleep, and will @@ -3191,11 +3225,13 @@ EXPORT_SYMBOL_GPL(gpiod_get_value); * @value_bitmap: bitmap to store the read values * * Read the raw values of the GPIOs, i.e. the values of the physical lines - * without regard for their ACTIVE_LOW status. Return 0 in case of success, - * else an error code. + * without regard for their ACTIVE_LOW status. * * This function can be called from contexts where we cannot sleep, * and it will complain if the GPIO chip functions potentially sleep. + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_get_raw_array_value(unsigned int array_size, struct gpio_desc **desc_array, @@ -3218,10 +3254,13 @@ EXPORT_SYMBOL_GPL(gpiod_get_raw_array_value); * @value_bitmap: bitmap to store the read values * * Read the logical values of the GPIOs, i.e. taking their ACTIVE_LOW status - * into account. Return 0 in case of success, else an error code. + * into account. * * This function can be called from contexts where we cannot sleep, * and it will complain if the GPIO chip functions potentially sleep. + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_get_array_value(unsigned int array_size, struct gpio_desc **desc_array, @@ -3509,6 +3548,9 @@ EXPORT_SYMBOL_GPL(gpiod_set_value); * * This function can be called from contexts where we cannot sleep, and will * complain if the GPIO chip functions potentially sleep. + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_set_raw_array_value(unsigned int array_size, struct gpio_desc **desc_array, @@ -3534,6 +3576,9 @@ EXPORT_SYMBOL_GPL(gpiod_set_raw_array_value); * * This function can be called from contexts where we cannot sleep, and will * complain if the GPIO chip functions potentially sleep. + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_set_array_value(unsigned int array_size, struct gpio_desc **desc_array, @@ -3552,6 +3597,8 @@ EXPORT_SYMBOL_GPL(gpiod_set_array_value); * gpiod_cansleep() - report whether gpio value access may sleep * @desc: gpio to check * + * Returns: + * 0 for non-sleepable, 1 for sleepable, or an error code in case of error. */ int gpiod_cansleep(const struct gpio_desc *desc) { @@ -3564,6 +3611,9 @@ EXPORT_SYMBOL_GPL(gpiod_cansleep); * gpiod_set_consumer_name() - set the consumer name for the descriptor * @desc: gpio to set the consumer name on * @name: the new consumer name + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_set_consumer_name(struct gpio_desc *desc, const char *name) { @@ -3577,8 +3627,8 @@ EXPORT_SYMBOL_GPL(gpiod_set_consumer_name); * gpiod_to_irq() - return the IRQ corresponding to a GPIO * @desc: gpio whose IRQ will be returned (already requested) * - * Return the IRQ corresponding to the passed GPIO, or an error code in case of - * error. + * Returns: + * The IRQ corresponding to the passed GPIO, or an error code in case of error. */ int gpiod_to_irq(const struct gpio_desc *desc) { @@ -3632,6 +3682,9 @@ EXPORT_SYMBOL_GPL(gpiod_to_irq); * * This is used directly by GPIO drivers that want to lock down * a certain GPIO line to be used for IRQs. + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiochip_lock_as_irq(struct gpio_chip *gc, unsigned int offset) { @@ -3783,7 +3836,8 @@ EXPORT_SYMBOL_GPL(gpiochip_line_is_persistent); * gpiod_get_raw_value_cansleep() - return a gpio's raw value * @desc: gpio whose value will be returned * - * Return the GPIO's raw value, i.e. the value of the physical line disregarding + * Returns: + * The GPIO's raw value, i.e. the value of the physical line disregarding * its ACTIVE_LOW status, or negative errno on failure. * * This function is to be called from contexts that can sleep. @@ -3800,7 +3854,8 @@ EXPORT_SYMBOL_GPL(gpiod_get_raw_value_cansleep); * gpiod_get_value_cansleep() - return a gpio's value * @desc: gpio whose value will be returned * - * Return the GPIO's logical value, i.e. taking the ACTIVE_LOW status into + * Returns: + * The GPIO's logical value, i.e. taking the ACTIVE_LOW status into * account, or negative errno on failure. * * This function is to be called from contexts that can sleep. @@ -3830,10 +3885,12 @@ EXPORT_SYMBOL_GPL(gpiod_get_value_cansleep); * @value_bitmap: bitmap to store the read values * * Read the raw values of the GPIOs, i.e. the values of the physical lines - * without regard for their ACTIVE_LOW status. Return 0 in case of success, - * else an error code. + * without regard for their ACTIVE_LOW status. * * This function is to be called from contexts that can sleep. + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_get_raw_array_value_cansleep(unsigned int array_size, struct gpio_desc **desc_array, @@ -3857,9 +3914,12 @@ EXPORT_SYMBOL_GPL(gpiod_get_raw_array_value_cansleep); * @value_bitmap: bitmap to store the read values * * Read the logical values of the GPIOs, i.e. taking their ACTIVE_LOW status - * into account. Return 0 in case of success, else an error code. + * into account. * * This function is to be called from contexts that can sleep. + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_get_array_value_cansleep(unsigned int array_size, struct gpio_desc **desc_array, @@ -3922,6 +3982,9 @@ EXPORT_SYMBOL_GPL(gpiod_set_value_cansleep); * without regard for their ACTIVE_LOW status. * * This function is to be called from contexts that can sleep. + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_set_raw_array_value_cansleep(unsigned int array_size, struct gpio_desc **desc_array, @@ -3964,6 +4027,9 @@ void gpiod_add_lookup_tables(struct gpiod_lookup_table **tables, size_t n) * into account. * * This function is to be called from contexts that can sleep. + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_set_array_value_cansleep(unsigned int array_size, struct gpio_desc **desc_array, @@ -4297,9 +4363,12 @@ EXPORT_SYMBOL_GPL(fwnode_gpiod_get_index); /** * gpiod_count - return the number of GPIOs associated with a device / function - * or -ENOENT if no GPIO has been assigned to the requested function * @dev: GPIO consumer, can be NULL for system-global GPIOs * @con_id: function within the GPIO consumer + * + * Returns: + * The number of GPIOs associated with a device / function or -ENOENT if no + * GPIO has been assigned to the requested function. */ int gpiod_count(struct device *dev, const char *con_id) { @@ -4326,7 +4395,8 @@ EXPORT_SYMBOL_GPL(gpiod_count); * @con_id: function within the GPIO consumer * @flags: optional GPIO initialization flags * - * Return the GPIO descriptor corresponding to the function con_id of device + * Returns: + * The GPIO descriptor corresponding to the function @con_id of device * dev, -ENOENT if no GPIO has been assigned to the requested function, or * another IS_ERR() code if an error occurred while trying to acquire the GPIO. */ @@ -4346,6 +4416,11 @@ EXPORT_SYMBOL_GPL(gpiod_get); * This is equivalent to gpiod_get(), except that when no GPIO was assigned to * the requested function it will return NULL. This is convenient for drivers * that need to handle optional GPIOs. + * + * Returns: + * The GPIO descriptor corresponding to the function @con_id of device + * dev, NULL if no GPIO has been assigned to the requested function, or + * another IS_ERR() code if an error occurred while trying to acquire the GPIO. */ struct gpio_desc *__must_check gpiod_get_optional(struct device *dev, const char *con_id, @@ -4364,7 +4439,8 @@ EXPORT_SYMBOL_GPL(gpiod_get_optional); * of_find_gpio() or of_get_gpio_hog() * @dflags: gpiod_flags - optional GPIO initialization flags * - * Return 0 on success, -ENOENT if no GPIO has been assigned to the + * Returns: + * 0 on success, -ENOENT if no GPIO has been assigned to the * requested function and/or index, or another IS_ERR() code if an error * occurred while trying to acquire the GPIO. */ @@ -4439,7 +4515,8 @@ int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id, * This variant of gpiod_get() allows to access GPIOs other than the first * defined one for functions that define several GPIOs. * - * Return a valid GPIO descriptor, -ENOENT if no GPIO has been assigned to the + * Returns: + * A valid GPIO descriptor, -ENOENT if no GPIO has been assigned to the * requested function and/or index, or another IS_ERR() code if an error * occurred while trying to acquire the GPIO. */ @@ -4467,6 +4544,11 @@ EXPORT_SYMBOL_GPL(gpiod_get_index); * This is equivalent to gpiod_get_index(), except that when no GPIO with the * specified index was assigned to the requested function it will return NULL. * This is convenient for drivers that need to handle optional GPIOs. + * + * Returns: + * A valid GPIO descriptor, NULL if no GPIO has been assigned to the + * requested function and/or index, or another IS_ERR() code if an error + * occurred while trying to acquire the GPIO. */ struct gpio_desc *__must_check gpiod_get_index_optional(struct device *dev, const char *con_id, @@ -4490,6 +4572,9 @@ EXPORT_SYMBOL_GPL(gpiod_get_index_optional); * @lflags: bitmask of gpio_lookup_flags GPIO_* values - returned from * of_find_gpio() or of_get_gpio_hog() * @dflags: gpiod_flags - optional GPIO initialization flags + * + * Returns: + * 0 on success, or negative errno on failure. */ int gpiod_hog(struct gpio_desc *desc, const char *name, unsigned long lflags, enum gpiod_flags dflags) @@ -4546,9 +4631,11 @@ static void gpiochip_free_hogs(struct gpio_chip *gc) * * This function acquires all the GPIOs defined under a given function. * - * Return a struct gpio_descs containing an array of descriptors, -ENOENT if - * no GPIO has been assigned to the requested function, or another IS_ERR() - * code if an error occurred while trying to acquire the GPIOs. + * Returns: + * The GPIO descriptors corresponding to the function @con_id of device + * dev, -ENOENT if no GPIO has been assigned to the requested function, + * or another IS_ERR() code if an error occurred while trying to acquire + * the GPIOs. */ struct gpio_descs *__must_check gpiod_get_array(struct device *dev, const char *con_id, @@ -4674,6 +4761,12 @@ EXPORT_SYMBOL_GPL(gpiod_get_array); * * This is equivalent to gpiod_get_array(), except that when no GPIO was * assigned to the requested function it will return NULL. + * + * Returns: + * The GPIO descriptors corresponding to the function @con_id of device + * dev, NULL if no GPIO has been assigned to the requested function, + * or another IS_ERR() code if an error occurred while trying to acquire + * the GPIOs. */ struct gpio_descs *__must_check gpiod_get_array_optional(struct device *dev, const char *con_id, From d25f9ab17de95f483b6be3911577150ae324f2dd Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 28 Aug 2024 18:12:43 +0300 Subject: [PATCH 304/573] gpiolib: legacy: Consolidate devm_gpio_*() with other legacy APIs There is no reason to keep deprecated legacy API implementations in the gpiolib-devres.c. Consolidate devm_gpio_*() with other legacy APIs. While at it, clean up header inclusion block in gpiolib-devres.c. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240828151357.2677340-1-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-devres.c | 82 ++++----------------------------- drivers/gpio/gpiolib-legacy.c | 86 +++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 74 deletions(-) diff --git a/drivers/gpio/gpiolib-devres.c b/drivers/gpio/gpiolib-devres.c index 0fdf1356e717..08205f355ceb 100644 --- a/drivers/gpio/gpiolib-devres.c +++ b/drivers/gpio/gpiolib-devres.c @@ -6,15 +6,19 @@ * Copyright (c) 2011 John Crispin */ -#include -#include -#include -#include #include +#include +#include #include +#include + +#include #include "gpiolib.h" +struct fwnode_handle; +struct lock_class_key; + static void devm_gpiod_release(struct device *dev, void *res) { struct gpio_desc **desc = res; @@ -354,76 +358,6 @@ void devm_gpiod_put_array(struct device *dev, struct gpio_descs *descs) } EXPORT_SYMBOL_GPL(devm_gpiod_put_array); -static void devm_gpio_release(struct device *dev, void *res) -{ - unsigned *gpio = res; - - gpio_free(*gpio); -} - -/** - * devm_gpio_request - request a GPIO for a managed device - * @dev: device to request the GPIO for - * @gpio: GPIO to allocate - * @label: the name of the requested GPIO - * - * Except for the extra @dev argument, this function takes the - * same arguments and performs the same function as - * gpio_request(). GPIOs requested with this function will be - * automatically freed on driver detach. - */ -int devm_gpio_request(struct device *dev, unsigned gpio, const char *label) -{ - unsigned *dr; - int rc; - - dr = devres_alloc(devm_gpio_release, sizeof(unsigned), GFP_KERNEL); - if (!dr) - return -ENOMEM; - - rc = gpio_request(gpio, label); - if (rc) { - devres_free(dr); - return rc; - } - - *dr = gpio; - devres_add(dev, dr); - - return 0; -} -EXPORT_SYMBOL_GPL(devm_gpio_request); - -/** - * devm_gpio_request_one - request a single GPIO with initial setup - * @dev: device to request for - * @gpio: the GPIO number - * @flags: GPIO configuration as specified by GPIOF_* - * @label: a literal description string of this GPIO - */ -int devm_gpio_request_one(struct device *dev, unsigned gpio, - unsigned long flags, const char *label) -{ - unsigned *dr; - int rc; - - dr = devres_alloc(devm_gpio_release, sizeof(unsigned), GFP_KERNEL); - if (!dr) - return -ENOMEM; - - rc = gpio_request_one(gpio, flags, label); - if (rc) { - devres_free(dr); - return rc; - } - - *dr = gpio; - devres_add(dev, dr); - - return 0; -} -EXPORT_SYMBOL_GPL(devm_gpio_request_one); - static void devm_gpio_chip_release(void *data) { struct gpio_chip *gc = data; diff --git a/drivers/gpio/gpiolib-legacy.c b/drivers/gpio/gpiolib-legacy.c index 087fe3227e35..28f1046fb670 100644 --- a/drivers/gpio/gpiolib-legacy.c +++ b/drivers/gpio/gpiolib-legacy.c @@ -1,4 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include + #include #include @@ -74,3 +80,83 @@ int gpio_request(unsigned gpio, const char *label) return gpiod_request(desc, label); } EXPORT_SYMBOL_GPL(gpio_request); + +static void devm_gpio_release(struct device *dev, void *res) +{ + unsigned *gpio = res; + + gpio_free(*gpio); +} + +/** + * devm_gpio_request - request a GPIO for a managed device + * @dev: device to request the GPIO for + * @gpio: GPIO to allocate + * @label: the name of the requested GPIO + * + * Except for the extra @dev argument, this function takes the + * same arguments and performs the same function as gpio_request(). + * GPIOs requested with this function will be automatically freed + * on driver detach. + * + * **DEPRECATED** This function is deprecated and must not be used in new code. + * + * Returns: + * 0 on success, or negative errno on failure. + */ +int devm_gpio_request(struct device *dev, unsigned gpio, const char *label) +{ + unsigned *dr; + int rc; + + dr = devres_alloc(devm_gpio_release, sizeof(unsigned), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + rc = gpio_request(gpio, label); + if (rc) { + devres_free(dr); + return rc; + } + + *dr = gpio; + devres_add(dev, dr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_gpio_request); + +/** + * devm_gpio_request_one - request a single GPIO with initial setup + * @dev: device to request for + * @gpio: the GPIO number + * @flags: GPIO configuration as specified by GPIOF_* + * @label: a literal description string of this GPIO + * + * **DEPRECATED** This function is deprecated and must not be used in new code. + * + * Returns: + * 0 on success, or negative errno on failure. + */ +int devm_gpio_request_one(struct device *dev, unsigned gpio, + unsigned long flags, const char *label) +{ + unsigned *dr; + int rc; + + dr = devres_alloc(devm_gpio_release, sizeof(unsigned), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + rc = gpio_request_one(gpio, flags, label); + if (rc) { + devres_free(dr); + return rc; + } + + *dr = gpio; + devres_add(dev, dr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_gpio_request_one); From ece70e79868c75d946819db4fba095c8c96ddb32 Mon Sep 17 00:00:00 2001 From: Rong Qianfeng Date: Tue, 20 Aug 2024 20:16:50 +0800 Subject: [PATCH 305/573] gpio: stp-xway: Simplify using devm_clk_get_enabled() Use devm_clk_get_enabled() simplify xway_stp_probe(). Signed-off-by: Rong Qianfeng Link: https://lore.kernel.org/r/20240820121651.29706-2-rongqianfeng@vivo.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-stp-xway.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-stp-xway.c b/drivers/gpio/gpio-stp-xway.c index 053d616f2e02..5a6406d1f03a 100644 --- a/drivers/gpio/gpio-stp-xway.c +++ b/drivers/gpio/gpio-stp-xway.c @@ -296,23 +296,17 @@ static int xway_stp_probe(struct platform_device *pdev) if (!of_property_read_bool(pdev->dev.of_node, "lantiq,rising")) chip->edge = XWAY_STP_FALLING; - clk = devm_clk_get(&pdev->dev, NULL); + clk = devm_clk_get_enabled(&pdev->dev, NULL); if (IS_ERR(clk)) { dev_err(&pdev->dev, "Failed to get clock\n"); return PTR_ERR(clk); } - ret = clk_prepare_enable(clk); - if (ret) - return ret; - xway_stp_hw_init(chip); ret = devm_gpiochip_add_data(&pdev->dev, &chip->gc, chip); - if (ret) { - clk_disable_unprepare(clk); + if (ret) return ret; - } dev_info(&pdev->dev, "Init done\n"); From c10c762f76b8cbce165bef9dfd33e6a0d9e52ba7 Mon Sep 17 00:00:00 2001 From: Dhruva Gole Date: Mon, 2 Sep 2024 16:00:02 +0530 Subject: [PATCH 306/573] gpio: syscon: fix excess struct member build warning Fix the build warning with W=1 flag, "Excess struct member 'compatible' description in 'syscon_gpio_data' " by removing the documentation for the non existent member. Signed-off-by: Dhruva Gole Link: https://lore.kernel.org/r/20240902-b4-gpio-fix-v1-1-49a997994fa5@ti.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-syscon.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpio/gpio-syscon.c b/drivers/gpio/gpio-syscon.c index 3a90a3a1caea..5ab394ec81e6 100644 --- a/drivers/gpio/gpio-syscon.c +++ b/drivers/gpio/gpio-syscon.c @@ -23,7 +23,6 @@ /** * struct syscon_gpio_data - Configuration for the device. - * @compatible: SYSCON driver compatible string. * @flags: Set of GPIO_SYSCON_FEAT_ flags: * GPIO_SYSCON_FEAT_IN: GPIOs supports input, * GPIO_SYSCON_FEAT_OUT: GPIOs supports output, From e9482dc50ab2cb81b7735180345c48ada87205a5 Mon Sep 17 00:00:00 2001 From: Martyn Welch Date: Tue, 20 Aug 2024 15:33:27 +0100 Subject: [PATCH 307/573] gpio: mpc8xxx: Add wake on GPIO support The mpc8xxx GPIO can generate an interrupt on state change. This interrupt can be used to wake up the device from its sleep state if enabled to do so. Add required support to the driver so that the GPIO can be used in this way. In order for the GPIO to actually function in this way, it is necessary to also set the GPIO bit in the RCPM. This can be done via the device tree fsl,rcpm-wakeup property. Signed-off-by: Martyn Welch Link: https://lore.kernel.org/r/20240820143328.1987442-1-martyn.welch@collabora.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mpc8xxx.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/gpio/gpio-mpc8xxx.c b/drivers/gpio/gpio-mpc8xxx.c index c0125ac73906..ab30c911c9d5 100644 --- a/drivers/gpio/gpio-mpc8xxx.c +++ b/drivers/gpio/gpio-mpc8xxx.c @@ -413,6 +413,8 @@ static int mpc8xxx_probe(struct platform_device *pdev) goto err; } + device_init_wakeup(&pdev->dev, true); + return 0; err: irq_domain_remove(mpc8xxx_gc->irq); @@ -429,6 +431,31 @@ static void mpc8xxx_remove(struct platform_device *pdev) } } +#ifdef CONFIG_PM +static int mpc8xxx_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct mpc8xxx_gpio_chip *mpc8xxx_gc = platform_get_drvdata(pdev); + + if (mpc8xxx_gc->irqn && device_may_wakeup(&pdev->dev)) + enable_irq_wake(mpc8xxx_gc->irqn); + + return 0; +} + +static int mpc8xxx_resume(struct platform_device *pdev) +{ + struct mpc8xxx_gpio_chip *mpc8xxx_gc = platform_get_drvdata(pdev); + + if (mpc8xxx_gc->irqn && device_may_wakeup(&pdev->dev)) + disable_irq_wake(mpc8xxx_gc->irqn); + + return 0; +} +#else +#define mpc8xxx_suspend NULL +#define mpc8xxx_resume NULL +#endif + #ifdef CONFIG_ACPI static const struct acpi_device_id gpio_acpi_ids[] = { {"NXP0031",}, @@ -440,6 +467,8 @@ MODULE_DEVICE_TABLE(acpi, gpio_acpi_ids); static struct platform_driver mpc8xxx_plat_driver = { .probe = mpc8xxx_probe, .remove_new = mpc8xxx_remove, + .suspend = mpc8xxx_suspend, + .resume = mpc8xxx_resume, .driver = { .name = "gpio-mpc8xxx", .of_match_table = mpc8xxx_gpio_ids, From db184a1ced56dde6bbf8cc4d9b936c9f6a510e28 Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Sun, 1 Sep 2024 22:57:45 -0700 Subject: [PATCH 308/573] iommu/tegra241-cmdqv: Fix -Wformat-truncation warnings in lvcmdq_error_header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kernel test robot reported a few trucation warnings at the snprintf: drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c: In function ‘tegra241_vintf_free_lvcmdq’: drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c:239:56: warning: ‘%u’ directive output may be truncated writing between 1 and 5 bytes into a region of size between 3 and 11 [-Wformat-truncation=] 239 | snprintf(header, hlen, "VINTF%u: VCMDQ%u/LVCMDQ%u: ", | ^~ drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c:239:32: note: directive argument in the range [0, 65535] 239 | snprintf(header, hlen, "VINTF%u: VCMDQ%u/LVCMDQ%u: ", | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c:239:9: note: ‘snprintf’ output between 25 and 37 bytes into a destination of size 32 239 | snprintf(header, hlen, "VINTF%u: VCMDQ%u/LVCMDQ%u: ", | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 240 | vcmdq->vintf->idx, vcmdq->idx, vcmdq->lidx); Fix by bumping up the size of the header to hold more characters. Fixes: 918eb5c856f6 ("iommu/arm-smmu-v3: Add in-kernel support for NVIDIA Tegra241 (Grace) CMDQV") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202409020406.7ed5uojF-lkp@intel.com/ Signed-off-by: Nicolin Chen Link: https://lore.kernel.org/r/20240902055745.629456-1-nicolinc@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c index 9eb9d959f3e5..03fd13c21dcc 100644 --- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c +++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c @@ -233,7 +233,7 @@ static inline int vintf_write_config(struct tegra241_vintf *vintf, u32 regval) static inline char *lvcmdq_error_header(struct tegra241_vcmdq *vcmdq, char *header, int hlen) { - WARN_ON(hlen < 32); + WARN_ON(hlen < 64); if (WARN_ON(!vcmdq->vintf)) return ""; snprintf(header, hlen, "VINTF%u: VCMDQ%u/LVCMDQ%u: ", @@ -243,7 +243,7 @@ static inline char *lvcmdq_error_header(struct tegra241_vcmdq *vcmdq, static inline int vcmdq_write_config(struct tegra241_vcmdq *vcmdq, u32 regval) { - char header[32], *h = lvcmdq_error_header(vcmdq, header, 32); + char header[64], *h = lvcmdq_error_header(vcmdq, header, 64); return tegra241_cmdqv_write_config(vcmdq->cmdqv, REG_VCMDQ_PAGE0(vcmdq, CONFIG), @@ -354,7 +354,7 @@ tegra241_cmdqv_get_cmdq(struct arm_smmu_device *smmu, static void tegra241_vcmdq_hw_deinit(struct tegra241_vcmdq *vcmdq) { - char header[32], *h = lvcmdq_error_header(vcmdq, header, 32); + char header[64], *h = lvcmdq_error_header(vcmdq, header, 64); u32 gerrorn, gerror; if (vcmdq_write_config(vcmdq, 0)) { @@ -382,7 +382,7 @@ static void tegra241_vcmdq_hw_deinit(struct tegra241_vcmdq *vcmdq) static int tegra241_vcmdq_hw_init(struct tegra241_vcmdq *vcmdq) { - char header[32], *h = lvcmdq_error_header(vcmdq, header, 32); + char header[64], *h = lvcmdq_error_header(vcmdq, header, 64); int ret; /* Reset VCMDQ */ @@ -555,13 +555,13 @@ static int tegra241_vintf_init_lvcmdq(struct tegra241_vintf *vintf, u16 lidx, static void tegra241_vintf_free_lvcmdq(struct tegra241_vintf *vintf, u16 lidx) { struct tegra241_vcmdq *vcmdq = vintf->lvcmdqs[lidx]; - char header[32]; + char header[64]; tegra241_vcmdq_free_smmu_cmdq(vcmdq); tegra241_vintf_deinit_lvcmdq(vintf, lidx); dev_dbg(vintf->cmdqv->dev, - "%sdeallocated\n", lvcmdq_error_header(vcmdq, header, 32)); + "%sdeallocated\n", lvcmdq_error_header(vcmdq, header, 64)); kfree(vcmdq); } @@ -570,7 +570,7 @@ tegra241_vintf_alloc_lvcmdq(struct tegra241_vintf *vintf, u16 lidx) { struct tegra241_cmdqv *cmdqv = vintf->cmdqv; struct tegra241_vcmdq *vcmdq; - char header[32]; + char header[64]; int ret; vcmdq = kzalloc(sizeof(*vcmdq), GFP_KERNEL); @@ -587,7 +587,7 @@ tegra241_vintf_alloc_lvcmdq(struct tegra241_vintf *vintf, u16 lidx) goto deinit_lvcmdq; dev_dbg(cmdqv->dev, - "%sallocated\n", lvcmdq_error_header(vcmdq, header, 32)); + "%sallocated\n", lvcmdq_error_header(vcmdq, header, 64)); return vcmdq; deinit_lvcmdq: From 2ff86df2b6a3b535b9cd4f3a9a37f5e181d3a898 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Mon, 2 Sep 2024 15:57:24 +0800 Subject: [PATCH 309/573] fbdev: pxa3xx-gcu: Convert comma to semicolon Replace a comma between expression statements by a semicolon. Signed-off-by: Chen Ni Signed-off-by: Helge Deller --- drivers/video/fbdev/pxa3xx-gcu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/pxa3xx-gcu.c b/drivers/video/fbdev/pxa3xx-gcu.c index 43c80316d84b..489088b4e467 100644 --- a/drivers/video/fbdev/pxa3xx-gcu.c +++ b/drivers/video/fbdev/pxa3xx-gcu.c @@ -594,8 +594,8 @@ static int pxa3xx_gcu_probe(struct platform_device *pdev) * container_of(). This isn't really necessary as we have a fixed minor * number anyway, but this is to avoid statics. */ - priv->misc_dev.minor = PXA3XX_GCU_MINOR, - priv->misc_dev.name = DRV_NAME, + priv->misc_dev.minor = PXA3XX_GCU_MINOR; + priv->misc_dev.name = DRV_NAME; priv->misc_dev.fops = &pxa3xx_gcu_miscdev_fops; /* handle IO resources */ From 18b0327310efa286e1bf8fca3845cf2d121d36f1 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Mon, 2 Sep 2024 16:47:30 +0800 Subject: [PATCH 310/573] fbdev: imsttfb: convert comma to semicolon Replace a comma between expression statements by a semicolon. Signed-off-by: Chen Ni Signed-off-by: Helge Deller --- drivers/video/fbdev/imsttfb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/imsttfb.c b/drivers/video/fbdev/imsttfb.c index 660499260f46..dc4e659e06af 100644 --- a/drivers/video/fbdev/imsttfb.c +++ b/drivers/video/fbdev/imsttfb.c @@ -995,7 +995,7 @@ imsttfb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) bgc |= (bgc << 8); bgc |= (bgc << 16); - Bpp = info->var.bits_per_pixel >> 3, + Bpp = info->var.bits_per_pixel >> 3; line_pitch = info->fix.line_length; dy = rect->dy * line_pitch; @@ -1036,7 +1036,7 @@ imsttfb_copyarea(struct fb_info *info, const struct fb_copyarea *area) __u32 Bpp, line_pitch, fb_offset_old, fb_offset_new, sp, dp_octl; __u32 cnt, bltctl, sx, sy, dx, dy, height, width; - Bpp = info->var.bits_per_pixel >> 3, + Bpp = info->var.bits_per_pixel >> 3; sx = area->sx * Bpp; sy = area->sy; From 27f22f897095b09df32bf689b63624d23b0c8ebc Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Mon, 2 Sep 2024 15:44:02 +0800 Subject: [PATCH 311/573] fbdev: hyperv_fb: Convert comma to semicolon Replace a comma between expression statements by a semicolon. Fixes: d786e00d19f9 ("drivers: hv, hyperv_fb: Untangle and refactor Hyper-V panic notifiers") Signed-off-by: Chen Ni Signed-off-by: Helge Deller --- drivers/video/fbdev/hyperv_fb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/video/fbdev/hyperv_fb.c b/drivers/video/fbdev/hyperv_fb.c index 8fdccf033b2d..7fdb5edd7e2e 100644 --- a/drivers/video/fbdev/hyperv_fb.c +++ b/drivers/video/fbdev/hyperv_fb.c @@ -1189,7 +1189,7 @@ static int hvfb_probe(struct hv_device *hdev, * which is almost at the end of list, with priority = INT_MIN + 1. */ par->hvfb_panic_nb.notifier_call = hvfb_on_panic; - par->hvfb_panic_nb.priority = INT_MIN + 10, + par->hvfb_panic_nb.priority = INT_MIN + 10; atomic_notifier_chain_register(&panic_notifier_list, &par->hvfb_panic_nb); From 8d2aaf4382b7c2ae4eae17c3eb71474eddbb5c4b Mon Sep 17 00:00:00 2001 From: Rong Qianfeng Date: Tue, 20 Aug 2024 20:16:51 +0800 Subject: [PATCH 312/573] gpio: zynq: Simplify using devm_clk_get_enabled() Use devm_clk_get_enabled() simplify zynq_gpio_probe() and zynq_gpio_remove(). Acked-by: Michal Simek Signed-off-by: Rong Qianfeng Link: https://lore.kernel.org/r/20240820121651.29706-3-rongqianfeng@vivo.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-zynq.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/gpio/gpio-zynq.c b/drivers/gpio/gpio-zynq.c index 466e23031afc..1a42336dfc1d 100644 --- a/drivers/gpio/gpio-zynq.c +++ b/drivers/gpio/gpio-zynq.c @@ -940,16 +940,10 @@ static int zynq_gpio_probe(struct platform_device *pdev) chip->ngpio = gpio->p_data->ngpio; /* Retrieve GPIO clock */ - gpio->clk = devm_clk_get(&pdev->dev, NULL); + gpio->clk = devm_clk_get_enabled(&pdev->dev, NULL); if (IS_ERR(gpio->clk)) return dev_err_probe(&pdev->dev, PTR_ERR(gpio->clk), "input clock not found.\n"); - ret = clk_prepare_enable(gpio->clk); - if (ret) { - dev_err(&pdev->dev, "Unable to enable clock.\n"); - return ret; - } - spin_lock_init(&gpio->dirlock); pm_runtime_set_active(&pdev->dev); @@ -999,7 +993,6 @@ err_pm_put: pm_runtime_put(&pdev->dev); err_pm_dis: pm_runtime_disable(&pdev->dev); - clk_disable_unprepare(gpio->clk); return ret; } @@ -1019,7 +1012,6 @@ static void zynq_gpio_remove(struct platform_device *pdev) if (ret < 0) dev_warn(&pdev->dev, "pm_runtime_get_sync() Failed\n"); gpiochip_remove(&gpio->chip); - clk_disable_unprepare(gpio->clk); device_set_wakeup_capable(&pdev->dev, 0); pm_runtime_disable(&pdev->dev); } From e1df5d0229c37265e4a84a32e71690c5089d2f5b Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 2 Sep 2024 14:12:58 +0200 Subject: [PATCH 313/573] gpio: pch: kerneldoc fixes for excess members Drop kerneldoc description of 'lock' to fix W=1 warning: drivers/gpio/gpio-pch.c:101: warning: Excess struct member 'lock' description in 'pch_gpio' Reviewed-by: Andy Shevchenko Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240902121258.64094-1-krzysztof.kozlowski@linaro.org Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pch.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpio/gpio-pch.c b/drivers/gpio/gpio-pch.c index ee37ecb615cb..63f25c72eac2 100644 --- a/drivers/gpio/gpio-pch.c +++ b/drivers/gpio/gpio-pch.c @@ -84,7 +84,6 @@ struct pch_gpio_reg_data { * @gpio: Data for GPIO infrastructure. * @pch_gpio_reg: Memory mapped Register data is saved here * when suspend. - * @lock: Used for register access protection * @irq_base: Save base of IRQ number for interrupt * @ioh: IOH ID * @spinlock: Used for register access protection From 21d9e6013134998334e162af905322d7da1e5314 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Mon, 2 Sep 2024 09:53:12 +0200 Subject: [PATCH 314/573] hwmon: pmbus: Implement generic bus access delay Some drivers, like the max15301 or zl6100, are intentionally delaying SMBus communications, to prevent transmission errors. As this is necessary on additional PMBus compatible devices, implement a generic delay mechanism in the pmbus core. Introduces two delay settings in the pmbus_driver_info struct, one applies to every SMBus transaction and the other is for write transaction only. Once set by the driver the SMBus traffic, using the generic pmbus access helpers, is automatically delayed when necessary. The two settings are: access_delay: - Unit in microseconds - Stores the accessed timestamp after every SMBus access - Delays when necessary before the next SMBus access write_delay: - Unit in microseconds - Stores the written timestamp after a write SMBus access - Delays when necessary before the next SMBus access This allows to drop the custom delay code from the drivers and easily introduce this feature in additional pmbus drivers. Signed-off-by: Patrick Rudolph Message-ID: <20240902075319.585656-1-patrick.rudolph@9elements.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 10 ++++ drivers/hwmon/pmbus/pmbus_core.c | 92 +++++++++++++++++++++++++++++--- 2 files changed, 96 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index fb442fae7b3e..5d5dc774187b 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -466,6 +466,16 @@ struct pmbus_driver_info { /* custom attributes */ const struct attribute_group **groups; + + /* + * Some chips need a little delay between SMBus communication. When + * set, the generic PMBus helper functions will wait if necessary + * to meet this requirement. The access delay is honored after + * every SMBus operation. The write delay is only honored after + * SMBus write operations. + */ + int access_delay; /* in microseconds */ + int write_delay; /* in microseconds */ }; /* Regulator ops */ diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index cb4c65a7f288..0ea6fe7eb17c 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -108,6 +109,8 @@ struct pmbus_data { int vout_low[PMBUS_PAGES]; /* voltage low margin */ int vout_high[PMBUS_PAGES]; /* voltage high margin */ + ktime_t write_time; /* Last SMBUS write timestamp */ + ktime_t access_time; /* Last SMBUS access timestamp */ }; struct pmbus_debugfs_entry { @@ -158,6 +161,39 @@ void pmbus_set_update(struct i2c_client *client, u8 reg, bool update) } EXPORT_SYMBOL_NS_GPL(pmbus_set_update, PMBUS); +/* Some chips need a delay between accesses. */ +static void pmbus_wait(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + s64 delta; + + if (info->access_delay) { + delta = ktime_us_delta(ktime_get(), data->access_time); + + if (delta < info->access_delay) + fsleep(info->access_delay - delta); + } else if (info->write_delay) { + delta = ktime_us_delta(ktime_get(), data->write_time); + + if (delta < info->write_delay) + fsleep(info->write_delay - delta); + } +} + +/* Sets the last accessed timestamp for pmbus_wait */ +static void pmbus_update_ts(struct i2c_client *client, bool write_op) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + + if (info->access_delay) { + data->access_time = ktime_get(); + } else if (info->write_delay && write_op) { + data->write_time = ktime_get(); + } +} + int pmbus_set_page(struct i2c_client *client, int page, int phase) { struct pmbus_data *data = i2c_get_clientdata(client); @@ -168,11 +204,15 @@ int pmbus_set_page(struct i2c_client *client, int page, int phase) if (!(data->info->func[page] & PMBUS_PAGE_VIRTUAL) && data->info->pages > 1 && page != data->currpage) { + pmbus_wait(client); rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + pmbus_update_ts(client, true); if (rv < 0) return rv; + pmbus_wait(client); rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE); + pmbus_update_ts(client, false); if (rv < 0) return rv; @@ -183,8 +223,10 @@ int pmbus_set_page(struct i2c_client *client, int page, int phase) if (data->info->phases[page] && data->currphase != phase && !(data->info->func[page] & PMBUS_PHASE_VIRTUAL)) { + pmbus_wait(client); rv = i2c_smbus_write_byte_data(client, PMBUS_PHASE, phase); + pmbus_update_ts(client, true); if (rv) return rv; } @@ -202,7 +244,11 @@ int pmbus_write_byte(struct i2c_client *client, int page, u8 value) if (rv < 0) return rv; - return i2c_smbus_write_byte(client, value); + pmbus_wait(client); + rv = i2c_smbus_write_byte(client, value); + pmbus_update_ts(client, true); + + return rv; } EXPORT_SYMBOL_NS_GPL(pmbus_write_byte, PMBUS); @@ -233,7 +279,11 @@ int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, if (rv < 0) return rv; - return i2c_smbus_write_word_data(client, reg, word); + pmbus_wait(client); + rv = i2c_smbus_write_word_data(client, reg, word); + pmbus_update_ts(client, true); + + return rv; } EXPORT_SYMBOL_NS_GPL(pmbus_write_word_data, PMBUS); @@ -351,7 +401,11 @@ int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg) if (rv < 0) return rv; - return i2c_smbus_read_word_data(client, reg); + pmbus_wait(client); + rv = i2c_smbus_read_word_data(client, reg); + pmbus_update_ts(client, false); + + return rv; } EXPORT_SYMBOL_NS_GPL(pmbus_read_word_data, PMBUS); @@ -410,7 +464,11 @@ int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg) if (rv < 0) return rv; - return i2c_smbus_read_byte_data(client, reg); + pmbus_wait(client); + rv = i2c_smbus_read_byte_data(client, reg); + pmbus_update_ts(client, false); + + return rv; } EXPORT_SYMBOL_NS_GPL(pmbus_read_byte_data, PMBUS); @@ -422,7 +480,11 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, u8 value) if (rv < 0) return rv; - return i2c_smbus_write_byte_data(client, reg, value); + pmbus_wait(client); + rv = i2c_smbus_write_byte_data(client, reg, value); + pmbus_update_ts(client, true); + + return rv; } EXPORT_SYMBOL_NS_GPL(pmbus_write_byte_data, PMBUS); @@ -454,7 +516,11 @@ static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, if (rv < 0) return rv; - return i2c_smbus_read_block_data(client, reg, data_buf); + pmbus_wait(client); + rv = i2c_smbus_read_block_data(client, reg, data_buf); + pmbus_update_ts(client, false); + + return rv; } static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page, @@ -2450,9 +2516,11 @@ static int pmbus_read_coefficients(struct i2c_client *client, data.block[1] = attr->reg; data.block[2] = 0x01; + pmbus_wait(client); rv = i2c_smbus_xfer(client->adapter, client->addr, client->flags, I2C_SMBUS_WRITE, PMBUS_COEFFICIENTS, I2C_SMBUS_BLOCK_PROC_CALL, &data); + pmbus_update_ts(client, true); if (rv < 0) return rv; @@ -2604,7 +2672,10 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, /* Enable PEC if the controller and bus supports it */ if (!(data->flags & PMBUS_NO_CAPABILITY)) { + pmbus_wait(client); ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY); + pmbus_update_ts(client, false); + if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) { if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) client->flags |= I2C_CLIENT_PEC; @@ -2617,10 +2688,16 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, * Bail out if both registers are not supported. */ data->read_status = pmbus_read_status_word; + pmbus_wait(client); ret = i2c_smbus_read_word_data(client, PMBUS_STATUS_WORD); + pmbus_update_ts(client, false); + if (ret < 0 || ret == 0xffff) { data->read_status = pmbus_read_status_byte; + pmbus_wait(client); ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE); + pmbus_update_ts(client, false); + if (ret < 0 || ret == 0xff) { dev_err(dev, "PMBus status register not found\n"); return -ENODEV; @@ -2635,7 +2712,10 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, * limit registers need to be disabled. */ if (!(data->flags & PMBUS_NO_WRITE_PROTECT)) { + pmbus_wait(client); ret = i2c_smbus_read_byte_data(client, PMBUS_WRITE_PROTECT); + pmbus_update_ts(client, false); + if (ret > 0 && (ret & PB_WP_ANY)) data->flags |= PMBUS_WRITE_PROTECTED | PMBUS_SKIP_STATUS_CHECK; } From d83219e9fcbd2361ac15fe331480b2f74410cbef Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Mon, 2 Sep 2024 09:53:13 +0200 Subject: [PATCH 315/573] hwmon: pmbus: max15301: Use generic code Use the generic pmbus bus access delay. Signed-off-by: Patrick Rudolph Message-ID: <20240902075319.585656-2-patrick.rudolph@9elements.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max15301.c | 92 +--------------------------------- 1 file changed, 1 insertion(+), 91 deletions(-) diff --git a/drivers/hwmon/pmbus/max15301.c b/drivers/hwmon/pmbus/max15301.c index 986404fe6a31..f5367a7bc0f5 100644 --- a/drivers/hwmon/pmbus/max15301.c +++ b/drivers/hwmon/pmbus/max15301.c @@ -31,8 +31,6 @@ MODULE_DEVICE_TABLE(i2c, max15301_id); struct max15301_data { int id; - ktime_t access; /* Chip access time */ - int delay; /* Delay between chip accesses in us */ struct pmbus_driver_info info; }; @@ -55,89 +53,6 @@ static struct max15301_data max15301_data = { } }; -/* This chip needs a delay between accesses */ -static inline void max15301_wait(const struct max15301_data *data) -{ - if (data->delay) { - s64 delta = ktime_us_delta(ktime_get(), data->access); - - if (delta < data->delay) - udelay(data->delay - delta); - } -} - -static int max15301_read_word_data(struct i2c_client *client, int page, - int phase, int reg) -{ - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct max15301_data *data = to_max15301_data(info); - int ret; - - if (page > 0) - return -ENXIO; - - if (reg >= PMBUS_VIRT_BASE) - return -ENXIO; - - max15301_wait(data); - ret = pmbus_read_word_data(client, page, phase, reg); - data->access = ktime_get(); - - return ret; -} - -static int max15301_read_byte_data(struct i2c_client *client, int page, int reg) -{ - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct max15301_data *data = to_max15301_data(info); - int ret; - - if (page > 0) - return -ENXIO; - - max15301_wait(data); - ret = pmbus_read_byte_data(client, page, reg); - data->access = ktime_get(); - - return ret; -} - -static int max15301_write_word_data(struct i2c_client *client, int page, int reg, - u16 word) -{ - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct max15301_data *data = to_max15301_data(info); - int ret; - - if (page > 0) - return -ENXIO; - - if (reg >= PMBUS_VIRT_BASE) - return -ENXIO; - - max15301_wait(data); - ret = pmbus_write_word_data(client, page, reg, word); - data->access = ktime_get(); - - return ret; -} - -static int max15301_write_byte(struct i2c_client *client, int page, u8 value) -{ - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct max15301_data *data = to_max15301_data(info); - int ret; - - if (page > 0) - return -ENXIO; - - max15301_wait(data); - ret = pmbus_write_byte(client, page, value); - data->access = ktime_get(); - - return ret; -} - static int max15301_probe(struct i2c_client *client) { int status; @@ -164,12 +79,7 @@ static int max15301_probe(struct i2c_client *client) return -ENODEV; } - max15301_data.delay = delay; - - info->read_byte_data = max15301_read_byte_data; - info->read_word_data = max15301_read_word_data; - info->write_byte = max15301_write_byte; - info->write_word_data = max15301_write_word_data; + info->access_delay = delay; return pmbus_do_probe(client, info); } From 106cfea5fa78eea671a6a7900da83238c4c124a5 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Mon, 2 Sep 2024 09:53:14 +0200 Subject: [PATCH 316/573] hwmon: pmbus: ucd9000: Use generic code Use generic pmbus bus write access delay. Signed-off-by: Patrick Rudolph Message-ID: <20240902075319.585656-3-patrick.rudolph@9elements.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ucd9000.c | 64 ++--------------------------------- 1 file changed, 2 insertions(+), 62 deletions(-) diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index d817c719b90b..5d3d1773bf52 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -67,7 +67,6 @@ struct ucd9000_data { struct gpio_chip gpio; #endif struct dentry *debugfs; - ktime_t write_time; }; #define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) @@ -86,63 +85,6 @@ struct ucd9000_debugfs_entry { */ #define UCD90320_WAIT_DELAY_US 500 -static inline void ucd90320_wait(const struct ucd9000_data *data) -{ - s64 delta = ktime_us_delta(ktime_get(), data->write_time); - - if (delta < UCD90320_WAIT_DELAY_US) - udelay(UCD90320_WAIT_DELAY_US - delta); -} - -static int ucd90320_read_word_data(struct i2c_client *client, int page, - int phase, int reg) -{ - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct ucd9000_data *data = to_ucd9000_data(info); - - if (reg >= PMBUS_VIRT_BASE) - return -ENXIO; - - ucd90320_wait(data); - return pmbus_read_word_data(client, page, phase, reg); -} - -static int ucd90320_read_byte_data(struct i2c_client *client, int page, int reg) -{ - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct ucd9000_data *data = to_ucd9000_data(info); - - ucd90320_wait(data); - return pmbus_read_byte_data(client, page, reg); -} - -static int ucd90320_write_word_data(struct i2c_client *client, int page, - int reg, u16 word) -{ - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct ucd9000_data *data = to_ucd9000_data(info); - int ret; - - ucd90320_wait(data); - ret = pmbus_write_word_data(client, page, reg, word); - data->write_time = ktime_get(); - - return ret; -} - -static int ucd90320_write_byte(struct i2c_client *client, int page, u8 value) -{ - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct ucd9000_data *data = to_ucd9000_data(info); - int ret; - - ucd90320_wait(data); - ret = pmbus_write_byte(client, page, value); - data->write_time = ktime_get(); - - return ret; -} - static int ucd9000_get_fan_config(struct i2c_client *client, int fan) { int fan_config = 0; @@ -667,10 +609,8 @@ static int ucd9000_probe(struct i2c_client *client) info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_FAN34 | PMBUS_HAVE_STATUS_FAN34; } else if (mid->driver_data == ucd90320) { - info->read_byte_data = ucd90320_read_byte_data; - info->read_word_data = ucd90320_read_word_data; - info->write_byte = ucd90320_write_byte; - info->write_word_data = ucd90320_write_word_data; + /* Delay SMBus operations after a write */ + info->write_delay = UCD90320_WAIT_DELAY_US; } ucd9000_probe_gpio(client, mid, data); From 9c4e67320f39c793e4abccef6bd5d66d31e4ec0c Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Mon, 2 Sep 2024 09:53:15 +0200 Subject: [PATCH 317/573] hwmon: pmbus: zl6100: Use generic code Use generic pmbus bus access delay. Signed-off-by: Patrick Rudolph Message-ID: <20240902075319.585656-4-patrick.rudolph@9elements.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/zl6100.c | 66 +++++------------------------------- 1 file changed, 8 insertions(+), 58 deletions(-) diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c index 83458df0d0cf..7920a16203e1 100644 --- a/drivers/hwmon/pmbus/zl6100.c +++ b/drivers/hwmon/pmbus/zl6100.c @@ -22,8 +22,6 @@ enum chips { zl2004, zl2005, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105, struct zl6100_data { int id; - ktime_t access; /* chip access time */ - int delay; /* Delay between chip accesses in uS */ struct pmbus_driver_info info; }; @@ -122,16 +120,6 @@ static u16 zl6100_d2l(long val) return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); } -/* Some chips need a delay between accesses */ -static inline void zl6100_wait(const struct zl6100_data *data) -{ - if (data->delay) { - s64 delta = ktime_us_delta(ktime_get(), data->access); - if (delta < data->delay) - udelay(data->delay - delta); - } -} - static int zl6100_read_word_data(struct i2c_client *client, int page, int phase, int reg) { @@ -174,9 +162,7 @@ static int zl6100_read_word_data(struct i2c_client *client, int page, break; } - zl6100_wait(data); ret = pmbus_read_word_data(client, page, phase, vreg); - data->access = ktime_get(); if (ret < 0) return ret; @@ -195,14 +181,11 @@ static int zl6100_read_word_data(struct i2c_client *client, int page, static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg) { const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct zl6100_data *data = to_zl6100_data(info); int ret, status; if (page >= info->pages) return -ENXIO; - zl6100_wait(data); - switch (reg) { case PMBUS_VIRT_STATUS_VMON: ret = pmbus_read_byte_data(client, 0, @@ -225,7 +208,6 @@ static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg) ret = pmbus_read_byte_data(client, page, reg); break; } - data->access = ktime_get(); return ret; } @@ -234,8 +216,7 @@ static int zl6100_write_word_data(struct i2c_client *client, int page, int reg, u16 word) { const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct zl6100_data *data = to_zl6100_data(info); - int ret, vreg; + int vreg; if (page >= info->pages) return -ENXIO; @@ -265,27 +246,7 @@ static int zl6100_write_word_data(struct i2c_client *client, int page, int reg, vreg = reg; } - zl6100_wait(data); - ret = pmbus_write_word_data(client, page, vreg, word); - data->access = ktime_get(); - - return ret; -} - -static int zl6100_write_byte(struct i2c_client *client, int page, u8 value) -{ - const struct pmbus_driver_info *info = pmbus_get_driver_info(client); - struct zl6100_data *data = to_zl6100_data(info); - int ret; - - if (page >= info->pages) - return -ENXIO; - - zl6100_wait(data); - ret = pmbus_write_byte(client, page, value); - data->access = ktime_get(); - - return ret; + return pmbus_write_word_data(client, page, vreg, word); } static const struct i2c_device_id zl6100_id[] = { @@ -363,14 +324,7 @@ static int zl6100_probe(struct i2c_client *client) * supported chips are known to require a wait time between I2C * accesses. */ - data->delay = delay; - - /* - * Since there was a direct I2C device access above, wait before - * accessing the chip again. - */ - data->access = ktime_get(); - zl6100_wait(data); + udelay(delay); info = &data->info; @@ -404,8 +358,7 @@ static int zl6100_probe(struct i2c_client *client) if (ret < 0) return ret; - data->access = ktime_get(); - zl6100_wait(data); + udelay(delay); if (ret & ZL8802_MFR_PHASES_MASK) info->func[1] |= PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; @@ -418,8 +371,7 @@ static int zl6100_probe(struct i2c_client *client) if (ret < 0) return ret; - data->access = ktime_get(); - zl6100_wait(data); + udelay(delay); ret = i2c_smbus_read_word_data(client, ZL8802_MFR_USER_CONFIG); if (ret < 0) @@ -428,8 +380,7 @@ static int zl6100_probe(struct i2c_client *client) if (ret & ZL8802_MFR_XTEMP_ENABLE_2) info->func[i] |= PMBUS_HAVE_TEMP2; - data->access = ktime_get(); - zl6100_wait(data); + udelay(delay); } ret = i2c_smbus_read_word_data(client, ZL8802_MFR_USER_GLOBAL_CONFIG); if (ret < 0) @@ -446,13 +397,12 @@ static int zl6100_probe(struct i2c_client *client) info->func[0] |= PMBUS_HAVE_TEMP2; } - data->access = ktime_get(); - zl6100_wait(data); + udelay(delay); + info->access_delay = delay; info->read_word_data = zl6100_read_word_data; info->read_byte_data = zl6100_read_byte_data; info->write_word_data = zl6100_write_word_data; - info->write_byte = zl6100_write_byte; return pmbus_do_probe(client, info); } From 3cd1ef26b01ea66f1f1c1f088fb41cf1476cb017 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Mon, 2 Sep 2024 09:53:16 +0200 Subject: [PATCH 318/573] hwmon: pmbus: pli12096bc: Add write delay Tests on PLI12096bc showed that sometimes a small delay is necessary after a write operation before a new operation can be processed. If not respected the device will probably NACK the data phase of the SMBus transaction. Tests showed that the probability to observe transaction errors can be raised by either reading sensor data or toggling the regulator enable. Further tests showed that 250 microseconds, as used previously for the CLEAR_FAULTS workaround, is sufficient. Signed-off-by: Patrick Rudolph Message-ID: <20240902075319.585656-5-patrick.rudolph@9elements.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pli1209bc.c | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/drivers/hwmon/pmbus/pli1209bc.c b/drivers/hwmon/pmbus/pli1209bc.c index 2c6c9ec2a652..178e0cdb7887 100644 --- a/drivers/hwmon/pmbus/pli1209bc.c +++ b/drivers/hwmon/pmbus/pli1209bc.c @@ -54,30 +54,6 @@ static int pli1209bc_read_word_data(struct i2c_client *client, int page, } } -static int pli1209bc_write_byte(struct i2c_client *client, int page, u8 reg) -{ - int ret; - - switch (reg) { - case PMBUS_CLEAR_FAULTS: - ret = pmbus_write_byte(client, page, reg); - /* - * PLI1209 takes 230 usec to execute the CLEAR_FAULTS command. - * During that time it's busy and NACKs all requests on the - * SMBUS interface. It also NACKs reads on PMBUS_STATUS_BYTE - * making it impossible to poll the BUSY flag. - * - * Just wait for not BUSY unconditionally. - */ - usleep_range(250, 300); - break; - default: - ret = -ENODATA; - break; - } - return ret; -} - #if IS_ENABLED(CONFIG_SENSORS_PLI1209BC_REGULATOR) static const struct regulator_desc pli1209bc_reg_desc = { .name = "vout2", @@ -127,7 +103,7 @@ static struct pmbus_driver_info pli1209bc_info = { | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT, .read_word_data = pli1209bc_read_word_data, - .write_byte = pli1209bc_write_byte, + .write_delay = 250, #if IS_ENABLED(CONFIG_SENSORS_PLI1209BC_REGULATOR) .num_regulators = 1, .reg_desc = &pli1209bc_reg_desc, From 184bee388d4661c3fea633f135a5c45ff03c7ec6 Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Mon, 2 Sep 2024 10:27:11 +0800 Subject: [PATCH 319/573] iommu/vt-d: Require DMA domain if hardware not support passthrough The iommu core defines the def_domain_type callback to query the iommu driver about hardware capability and quirks. The iommu driver should declare IOMMU_DOMAIN_DMA requirement for hardware lacking pass-through capability. Earlier VT-d hardware implementations did not support pass-through translation mode. The iommu driver relied on a paging domain with all physical system memory addresses identically mapped to the same IOVA to simulate pass-through translation before the def_domain_type was introduced and it has been kept until now. It's time to adjust it now to make the Intel iommu driver follow the def_domain_type semantics. Signed-off-by: Lu Baolu Reviewed-by: Jason Gunthorpe Reviewed-by: Kevin Tian Reviewed-by: Jerry Snitselaar Link: https://lore.kernel.org/r/20240809055431.36513-2-baolu.lu@linux.intel.com Signed-off-by: Joerg Roedel --- drivers/iommu/intel/iommu.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index 9ff8b83c19a3..90ad794a1be7 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -2149,6 +2149,16 @@ static bool device_rmrr_is_relaxable(struct device *dev) static int device_def_domain_type(struct device *dev) { + struct device_domain_info *info = dev_iommu_priv_get(dev); + struct intel_iommu *iommu = info->iommu; + + /* + * Hardware does not support the passthrough translation mode. + * Always use a dynamaic mapping domain. + */ + if (!ecap_pass_through(iommu->ecap)) + return IOMMU_DOMAIN_DMA; + if (dev_is_pci(dev)) { struct pci_dev *pdev = to_pci_dev(dev); From 9e74e1b8198fd07fcbb4266771ca0f5195c71d9c Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Mon, 2 Sep 2024 10:27:12 +0800 Subject: [PATCH 320/573] iommu/vt-d: Remove identity mappings from si_domain As the driver has enforced DMA domains for devices managed by an IOMMU hardware that doesn't support passthrough translation mode, there is no need for static identity mappings in the si_domain. Remove the identity mapping code to avoid dead code. Signed-off-by: Lu Baolu Reviewed-by: Jason Gunthorpe Reviewed-by: Kevin Tian Link: https://lore.kernel.org/r/20240809055431.36513-3-baolu.lu@linux.intel.com Signed-off-by: Joerg Roedel --- drivers/iommu/intel/iommu.c | 122 ++---------------------------------- 1 file changed, 4 insertions(+), 118 deletions(-) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index 90ad794a1be7..723ea9f3f501 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -167,14 +167,7 @@ static void device_rbtree_remove(struct device_domain_info *info) spin_unlock_irqrestore(&iommu->device_rbtree_lock, flags); } -/* - * This domain is a statically identity mapping domain. - * 1. This domain creats a static 1:1 mapping to all usable memory. - * 2. It maps to each iommu if successful. - * 3. Each iommu mapps to this domain if successful. - */ static struct dmar_domain *si_domain; -static int hw_pass_through = 1; struct dmar_rmrr_unit { struct list_head list; /* list of rmrr units */ @@ -1647,7 +1640,7 @@ static int domain_context_mapping_one(struct dmar_domain *domain, struct context_entry *context; int agaw, ret; - if (hw_pass_through && domain_type_is_si(domain)) + if (domain_type_is_si(domain)) translation = CONTEXT_TT_PASS_THROUGH; pr_debug("Set context mapping for %02x:%02x.%d\n", @@ -1998,29 +1991,10 @@ static bool dev_is_real_dma_subdevice(struct device *dev) pci_real_dma_dev(to_pci_dev(dev)) != to_pci_dev(dev); } -static int iommu_domain_identity_map(struct dmar_domain *domain, - unsigned long first_vpfn, - unsigned long last_vpfn) -{ - /* - * RMRR range might have overlap with physical memory range, - * clear it first - */ - dma_pte_clear_range(domain, first_vpfn, last_vpfn); - - return __domain_mapping(domain, first_vpfn, - first_vpfn, last_vpfn - first_vpfn + 1, - DMA_PTE_READ|DMA_PTE_WRITE, GFP_KERNEL); -} - static int md_domain_init(struct dmar_domain *domain, int guest_width); -static int __init si_domain_init(int hw) +static int __init si_domain_init(void) { - struct dmar_rmrr_unit *rmrr; - struct device *dev; - int i, nid, ret; - si_domain = alloc_domain(IOMMU_DOMAIN_IDENTITY); if (!si_domain) return -EFAULT; @@ -2031,44 +2005,6 @@ static int __init si_domain_init(int hw) return -EFAULT; } - if (hw) - return 0; - - for_each_online_node(nid) { - unsigned long start_pfn, end_pfn; - int i; - - for_each_mem_pfn_range(i, nid, &start_pfn, &end_pfn, NULL) { - ret = iommu_domain_identity_map(si_domain, - mm_to_dma_pfn_start(start_pfn), - mm_to_dma_pfn_end(end_pfn-1)); - if (ret) - return ret; - } - } - - /* - * Identity map the RMRRs so that devices with RMRRs could also use - * the si_domain. - */ - for_each_rmrr_units(rmrr) { - for_each_active_dev_scope(rmrr->devices, rmrr->devices_cnt, - i, dev) { - unsigned long long start = rmrr->base_address; - unsigned long long end = rmrr->end_address; - - if (WARN_ON(end < start || - end >> agaw_to_width(si_domain->agaw))) - continue; - - ret = iommu_domain_identity_map(si_domain, - mm_to_dma_pfn_start(start >> PAGE_SHIFT), - mm_to_dma_pfn_end(end >> PAGE_SHIFT)); - if (ret) - return ret; - } - } - return 0; } @@ -2094,7 +2030,7 @@ static int dmar_domain_attach_device(struct dmar_domain *domain, if (!sm_supported(iommu)) ret = domain_context_mapping(domain, dev); - else if (hw_pass_through && domain_type_is_si(domain)) + else if (domain_type_is_si(domain)) ret = intel_pasid_setup_pass_through(iommu, dev, IOMMU_NO_PASID); else if (domain->use_first_level) ret = domain_setup_first_level(iommu, domain, dev, IOMMU_NO_PASID); @@ -2449,8 +2385,6 @@ static int __init init_dmars(void) } } - if (!ecap_pass_through(iommu->ecap)) - hw_pass_through = 0; intel_svm_check(iommu); } @@ -2466,7 +2400,7 @@ static int __init init_dmars(void) check_tylersburg_isoch(); - ret = si_domain_init(hw_pass_through); + ret = si_domain_init(); if (ret) goto free_iommu; @@ -2893,12 +2827,6 @@ static int intel_iommu_add(struct dmar_drhd_unit *dmaru) if (ret) goto out; - if (hw_pass_through && !ecap_pass_through(iommu->ecap)) { - pr_warn("%s: Doesn't support hardware pass through.\n", - iommu->name); - return -ENXIO; - } - sp = domain_update_iommu_superpage(NULL, iommu) - 1; if (sp >= 0 && !(cap_super_page_val(iommu->cap) & (1 << sp))) { pr_warn("%s: Doesn't support large page.\n", @@ -3149,43 +3077,6 @@ int dmar_iommu_notify_scope_dev(struct dmar_pci_notify_info *info) return 0; } -static int intel_iommu_memory_notifier(struct notifier_block *nb, - unsigned long val, void *v) -{ - struct memory_notify *mhp = v; - unsigned long start_vpfn = mm_to_dma_pfn_start(mhp->start_pfn); - unsigned long last_vpfn = mm_to_dma_pfn_end(mhp->start_pfn + - mhp->nr_pages - 1); - - switch (val) { - case MEM_GOING_ONLINE: - if (iommu_domain_identity_map(si_domain, - start_vpfn, last_vpfn)) { - pr_warn("Failed to build identity map for [%lx-%lx]\n", - start_vpfn, last_vpfn); - return NOTIFY_BAD; - } - break; - - case MEM_OFFLINE: - case MEM_CANCEL_ONLINE: - { - LIST_HEAD(freelist); - - domain_unmap(si_domain, start_vpfn, last_vpfn, &freelist); - iommu_put_pages_list(&freelist); - } - break; - } - - return NOTIFY_OK; -} - -static struct notifier_block intel_iommu_memory_nb = { - .notifier_call = intel_iommu_memory_notifier, - .priority = 0 -}; - static void intel_disable_iommus(void) { struct intel_iommu *iommu = NULL; @@ -3482,12 +3373,7 @@ int __init intel_iommu_init(void) iommu_pmu_register(iommu); } - up_read(&dmar_global_lock); - if (si_domain && !hw_pass_through) - register_memory_notifier(&intel_iommu_memory_nb); - - down_read(&dmar_global_lock); if (probe_acpi_namespace_devices()) pr_warn("ACPI name space devices didn't probe correctly\n"); From 2c13012e09190174614fd6901857a1b8c199e17d Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Mon, 2 Sep 2024 10:27:13 +0800 Subject: [PATCH 321/573] iommu/vt-d: Always reserve a domain ID for identity setup We will use a global static identity domain. Reserve a static domain ID for it. Signed-off-by: Lu Baolu Reviewed-by: Jason Gunthorpe Reviewed-by: Kevin Tian Reviewed-by: Jerry Snitselaar Link: https://lore.kernel.org/r/20240809055431.36513-4-baolu.lu@linux.intel.com Signed-off-by: Joerg Roedel --- drivers/iommu/intel/iommu.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index 723ea9f3f501..c019fb3b3e78 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -1440,10 +1440,10 @@ static int iommu_init_domains(struct intel_iommu *iommu) * entry for first-level or pass-through translation modes should * be programmed with a domain id different from those used for * second-level or nested translation. We reserve a domain id for - * this purpose. + * this purpose. This domain id is also used for identity domain + * in legacy mode. */ - if (sm_supported(iommu)) - set_bit(FLPT_DEFAULT_DID, iommu->domain_ids); + set_bit(FLPT_DEFAULT_DID, iommu->domain_ids); return 0; } From 487df6836606dc67cd8e2c26616f581c8800a17a Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Mon, 2 Sep 2024 10:27:14 +0800 Subject: [PATCH 322/573] iommu/vt-d: Remove has_iotlb_device flag The has_iotlb_device flag was used to indicate if a domain had attached devices with ATS enabled. Domains without this flag didn't require device TLB invalidation during unmap operations, optimizing performance by avoiding unnecessary device iteration. With the introduction of cache tags, this flag is no longer needed. The code to iterate over attached devices was removed by commit 06792d067989 ("iommu/vt-d: Cleanup use of iommu_flush_iotlb_psi()"). Remove has_iotlb_device to avoid unnecessary code. Suggested-by: Jason Gunthorpe Signed-off-by: Lu Baolu Reviewed-by: Kevin Tian Link: https://lore.kernel.org/r/20240809055431.36513-5-baolu.lu@linux.intel.com Signed-off-by: Joerg Roedel --- drivers/iommu/intel/iommu.c | 34 +--------------------------------- drivers/iommu/intel/iommu.h | 2 -- drivers/iommu/intel/nested.c | 2 -- 3 files changed, 1 insertion(+), 37 deletions(-) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index c019fb3b3e78..aa8e10a2ad51 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -485,7 +485,6 @@ void domain_update_iommu_cap(struct dmar_domain *domain) domain->domain.geometry.aperture_end = __DOMAIN_MAX_ADDR(domain->gaw); domain->domain.pgsize_bitmap |= domain_super_pgsize_bitmap(domain); - domain_update_iotlb(domain); } struct context_entry *iommu_context_addr(struct intel_iommu *iommu, u8 bus, @@ -1263,32 +1262,6 @@ domain_lookup_dev_info(struct dmar_domain *domain, return NULL; } -void domain_update_iotlb(struct dmar_domain *domain) -{ - struct dev_pasid_info *dev_pasid; - struct device_domain_info *info; - bool has_iotlb_device = false; - unsigned long flags; - - spin_lock_irqsave(&domain->lock, flags); - list_for_each_entry(info, &domain->devices, link) { - if (info->ats_enabled) { - has_iotlb_device = true; - break; - } - } - - list_for_each_entry(dev_pasid, &domain->dev_pasids, link_domain) { - info = dev_iommu_priv_get(dev_pasid->dev); - if (info->ats_enabled) { - has_iotlb_device = true; - break; - } - } - domain->has_iotlb_device = has_iotlb_device; - spin_unlock_irqrestore(&domain->lock, flags); -} - /* * The extra devTLB flush quirk impacts those QAT devices with PCI device * IDs ranging from 0x4940 to 0x4943. It is exempted from risky_device() @@ -1325,10 +1298,8 @@ static void iommu_enable_pci_caps(struct device_domain_info *info) info->pasid_enabled = 1; if (info->ats_supported && pci_ats_page_aligned(pdev) && - !pci_enable_ats(pdev, VTD_PAGE_SHIFT)) { + !pci_enable_ats(pdev, VTD_PAGE_SHIFT)) info->ats_enabled = 1; - domain_update_iotlb(info->domain); - } } static void iommu_disable_pci_caps(struct device_domain_info *info) @@ -1343,7 +1314,6 @@ static void iommu_disable_pci_caps(struct device_domain_info *info) if (info->ats_enabled) { pci_disable_ats(pdev); info->ats_enabled = 0; - domain_update_iotlb(info->domain); } if (info->pasid_enabled) { @@ -1517,7 +1487,6 @@ static struct dmar_domain *alloc_domain(unsigned int type) domain->nid = NUMA_NO_NODE; if (first_level_by_default(type)) domain->use_first_level = true; - domain->has_iotlb_device = false; INIT_LIST_HEAD(&domain->devices); INIT_LIST_HEAD(&domain->dev_pasids); INIT_LIST_HEAD(&domain->cache_tags); @@ -3518,7 +3487,6 @@ static struct dmar_domain *paging_domain_alloc(struct device *dev, bool first_st xa_init(&domain->iommu_array); domain->nid = dev_to_node(dev); - domain->has_iotlb_device = info->ats_enabled; domain->use_first_level = first_stage; /* calculate the address width */ diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h index b67c14da1240..01002ae2a091 100644 --- a/drivers/iommu/intel/iommu.h +++ b/drivers/iommu/intel/iommu.h @@ -588,7 +588,6 @@ struct dmar_domain { int nid; /* node id */ struct xarray iommu_array; /* Attached IOMMU array */ - u8 has_iotlb_device: 1; u8 iommu_coherency: 1; /* indicate coherency of iommu access */ u8 force_snooping : 1; /* Create IOPTEs with snoop control */ u8 set_pte_snp:1; @@ -1104,7 +1103,6 @@ int qi_submit_sync(struct intel_iommu *iommu, struct qi_desc *desc, */ #define QI_OPT_WAIT_DRAIN BIT(0) -void domain_update_iotlb(struct dmar_domain *domain); int domain_attach_iommu(struct dmar_domain *domain, struct intel_iommu *iommu); void domain_detach_iommu(struct dmar_domain *domain, struct intel_iommu *iommu); void device_block_translation(struct device *dev); diff --git a/drivers/iommu/intel/nested.c b/drivers/iommu/intel/nested.c index 16a2bcf5cfeb..36a91b1b52be 100644 --- a/drivers/iommu/intel/nested.c +++ b/drivers/iommu/intel/nested.c @@ -66,8 +66,6 @@ static int intel_nested_attach_dev(struct iommu_domain *domain, list_add(&info->link, &dmar_domain->devices); spin_unlock_irqrestore(&dmar_domain->lock, flags); - domain_update_iotlb(dmar_domain); - return 0; unassign_tag: cache_tag_unassign_domain(dmar_domain, dev, IOMMU_NO_PASID); From c7191984e5aade540f1a3845a116537c89572655 Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Mon, 2 Sep 2024 10:27:15 +0800 Subject: [PATCH 323/573] iommu/vt-d: Factor out helpers from domain_context_mapping_one() Extract common code from domain_context_mapping_one() into new helpers, making it reusable by other functions such as the upcoming identity domain implementation. No intentional functional changes. Signed-off-by: Lu Baolu Reviewed-by: Jason Gunthorpe Reviewed-by: Kevin Tian Reviewed-by: Jerry Snitselaar Link: https://lore.kernel.org/r/20240809055431.36513-6-baolu.lu@linux.intel.com Signed-off-by: Joerg Roedel --- drivers/iommu/intel/iommu.c | 99 ++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index aa8e10a2ad51..7950152bb4e6 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -1597,6 +1597,61 @@ static void domain_exit(struct dmar_domain *domain) kfree(domain); } +/* + * For kdump cases, old valid entries may be cached due to the + * in-flight DMA and copied pgtable, but there is no unmapping + * behaviour for them, thus we need an explicit cache flush for + * the newly-mapped device. For kdump, at this point, the device + * is supposed to finish reset at its driver probe stage, so no + * in-flight DMA will exist, and we don't need to worry anymore + * hereafter. + */ +static void copied_context_tear_down(struct intel_iommu *iommu, + struct context_entry *context, + u8 bus, u8 devfn) +{ + u16 did_old; + + if (!context_copied(iommu, bus, devfn)) + return; + + assert_spin_locked(&iommu->lock); + + did_old = context_domain_id(context); + context_clear_entry(context); + + if (did_old < cap_ndoms(iommu->cap)) { + iommu->flush.flush_context(iommu, did_old, + (((u16)bus) << 8) | devfn, + DMA_CCMD_MASK_NOBIT, + DMA_CCMD_DEVICE_INVL); + iommu->flush.flush_iotlb(iommu, did_old, 0, 0, + DMA_TLB_DSI_FLUSH); + } + + clear_context_copied(iommu, bus, devfn); +} + +/* + * It's a non-present to present mapping. If hardware doesn't cache + * non-present entry we only need to flush the write-buffer. If the + * _does_ cache non-present entries, then it does so in the special + * domain #0, which we have to flush: + */ +static void context_present_cache_flush(struct intel_iommu *iommu, u16 did, + u8 bus, u8 devfn) +{ + if (cap_caching_mode(iommu->cap)) { + iommu->flush.flush_context(iommu, 0, + (((u16)bus) << 8) | devfn, + DMA_CCMD_MASK_NOBIT, + DMA_CCMD_DEVICE_INVL); + iommu->flush.flush_iotlb(iommu, did, 0, 0, DMA_TLB_DSI_FLUSH); + } else { + iommu_flush_write_buffer(iommu); + } +} + static int domain_context_mapping_one(struct dmar_domain *domain, struct intel_iommu *iommu, u8 bus, u8 devfn) @@ -1625,31 +1680,9 @@ static int domain_context_mapping_one(struct dmar_domain *domain, if (context_present(context) && !context_copied(iommu, bus, devfn)) goto out_unlock; - /* - * For kdump cases, old valid entries may be cached due to the - * in-flight DMA and copied pgtable, but there is no unmapping - * behaviour for them, thus we need an explicit cache flush for - * the newly-mapped device. For kdump, at this point, the device - * is supposed to finish reset at its driver probe stage, so no - * in-flight DMA will exist, and we don't need to worry anymore - * hereafter. - */ - if (context_copied(iommu, bus, devfn)) { - u16 did_old = context_domain_id(context); - - if (did_old < cap_ndoms(iommu->cap)) { - iommu->flush.flush_context(iommu, did_old, - (((u16)bus) << 8) | devfn, - DMA_CCMD_MASK_NOBIT, - DMA_CCMD_DEVICE_INVL); - iommu->flush.flush_iotlb(iommu, did_old, 0, 0, - DMA_TLB_DSI_FLUSH); - } - - clear_context_copied(iommu, bus, devfn); - } - + copied_context_tear_down(iommu, context, bus, devfn); context_clear_entry(context); + context_set_domain_id(context, did); if (translation != CONTEXT_TT_PASS_THROUGH) { @@ -1685,23 +1718,7 @@ static int domain_context_mapping_one(struct dmar_domain *domain, context_set_present(context); if (!ecap_coherent(iommu->ecap)) clflush_cache_range(context, sizeof(*context)); - - /* - * It's a non-present to present mapping. If hardware doesn't cache - * non-present entry we only need to flush the write-buffer. If the - * _does_ cache non-present entries, then it does so in the special - * domain #0, which we have to flush: - */ - if (cap_caching_mode(iommu->cap)) { - iommu->flush.flush_context(iommu, 0, - (((u16)bus) << 8) | devfn, - DMA_CCMD_MASK_NOBIT, - DMA_CCMD_DEVICE_INVL); - iommu->flush.flush_iotlb(iommu, did, 0, 0, DMA_TLB_DSI_FLUSH); - } else { - iommu_flush_write_buffer(iommu); - } - + context_present_cache_flush(iommu, did, bus, devfn); ret = 0; out_unlock: From 2031c469f8161abe74189cb74f50da224f340b71 Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Mon, 2 Sep 2024 10:27:16 +0800 Subject: [PATCH 324/573] iommu/vt-d: Add support for static identity domain Software determines VT-d hardware support for passthrough translation by inspecting the capability register. If passthrough translation is not supported, the device is instructed to use DMA domain for its default domain. Add a global static identity domain with guaranteed attach semantics for IOMMUs that support passthrough translation mode. The global static identity domain is a dummy domain without corresponding dmar_domain structure. Consequently, the device's info->domain will be NULL with the identity domain is attached. Refactor the code accordingly. Signed-off-by: Lu Baolu Reviewed-by: Jason Gunthorpe Reviewed-by: Kevin Tian Link: https://lore.kernel.org/r/20240809055431.36513-7-baolu.lu@linux.intel.com Signed-off-by: Joerg Roedel --- drivers/iommu/intel/iommu.c | 114 ++++++++++++++++++++++++++++++++++-- drivers/iommu/intel/svm.c | 2 +- 2 files changed, 111 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index 7950152bb4e6..14f1fcf17152 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -3691,11 +3691,9 @@ int prepare_domain_attach_device(struct iommu_domain *domain, static int intel_iommu_attach_device(struct iommu_domain *domain, struct device *dev) { - struct device_domain_info *info = dev_iommu_priv_get(dev); int ret; - if (info->domain) - device_block_translation(dev); + device_block_translation(dev); ret = prepare_domain_attach_device(domain, dev); if (ret) @@ -4301,11 +4299,17 @@ static void intel_iommu_remove_dev_pasid(struct device *dev, ioasid_t pasid, struct iommu_domain *domain) { struct device_domain_info *info = dev_iommu_priv_get(dev); - struct dmar_domain *dmar_domain = to_dmar_domain(domain); struct dev_pasid_info *curr, *dev_pasid = NULL; struct intel_iommu *iommu = info->iommu; + struct dmar_domain *dmar_domain; unsigned long flags; + if (domain->type == IOMMU_DOMAIN_IDENTITY) { + intel_pasid_tear_down_entry(iommu, dev, pasid, false); + return; + } + + dmar_domain = to_dmar_domain(domain); spin_lock_irqsave(&dmar_domain->lock, flags); list_for_each_entry(curr, &dmar_domain->dev_pasids, link_domain) { if (curr->dev == dev && curr->pasid == pasid) { @@ -4532,9 +4536,111 @@ static const struct iommu_dirty_ops intel_dirty_ops = { .read_and_clear_dirty = intel_iommu_read_and_clear_dirty, }; +static int context_setup_pass_through(struct device *dev, u8 bus, u8 devfn) +{ + struct device_domain_info *info = dev_iommu_priv_get(dev); + struct intel_iommu *iommu = info->iommu; + struct context_entry *context; + + spin_lock(&iommu->lock); + context = iommu_context_addr(iommu, bus, devfn, 1); + if (!context) { + spin_unlock(&iommu->lock); + return -ENOMEM; + } + + if (context_present(context) && !context_copied(iommu, bus, devfn)) { + spin_unlock(&iommu->lock); + return 0; + } + + copied_context_tear_down(iommu, context, bus, devfn); + context_clear_entry(context); + context_set_domain_id(context, FLPT_DEFAULT_DID); + + /* + * In pass through mode, AW must be programmed to indicate the largest + * AGAW value supported by hardware. And ASR is ignored by hardware. + */ + context_set_address_width(context, iommu->msagaw); + context_set_translation_type(context, CONTEXT_TT_PASS_THROUGH); + context_set_fault_enable(context); + context_set_present(context); + if (!ecap_coherent(iommu->ecap)) + clflush_cache_range(context, sizeof(*context)); + context_present_cache_flush(iommu, FLPT_DEFAULT_DID, bus, devfn); + spin_unlock(&iommu->lock); + + return 0; +} + +static int context_setup_pass_through_cb(struct pci_dev *pdev, u16 alias, void *data) +{ + struct device *dev = data; + + if (dev != &pdev->dev) + return 0; + + return context_setup_pass_through(dev, PCI_BUS_NUM(alias), alias & 0xff); +} + +static int device_setup_pass_through(struct device *dev) +{ + struct device_domain_info *info = dev_iommu_priv_get(dev); + + if (!dev_is_pci(dev)) + return context_setup_pass_through(dev, info->bus, info->devfn); + + return pci_for_each_dma_alias(to_pci_dev(dev), + context_setup_pass_through_cb, dev); +} + +static int identity_domain_attach_dev(struct iommu_domain *domain, struct device *dev) +{ + struct device_domain_info *info = dev_iommu_priv_get(dev); + struct intel_iommu *iommu = info->iommu; + int ret; + + device_block_translation(dev); + + if (dev_is_real_dma_subdevice(dev)) + return 0; + + if (sm_supported(iommu)) { + ret = intel_pasid_setup_pass_through(iommu, dev, IOMMU_NO_PASID); + if (!ret) + iommu_enable_pci_caps(info); + } else { + ret = device_setup_pass_through(dev); + } + + return ret; +} + +static int identity_domain_set_dev_pasid(struct iommu_domain *domain, + struct device *dev, ioasid_t pasid) +{ + struct device_domain_info *info = dev_iommu_priv_get(dev); + struct intel_iommu *iommu = info->iommu; + + if (!pasid_supported(iommu) || dev_is_real_dma_subdevice(dev)) + return -EOPNOTSUPP; + + return intel_pasid_setup_pass_through(iommu, dev, pasid); +} + +static struct iommu_domain identity_domain = { + .type = IOMMU_DOMAIN_IDENTITY, + .ops = &(const struct iommu_domain_ops) { + .attach_dev = identity_domain_attach_dev, + .set_dev_pasid = identity_domain_set_dev_pasid, + }, +}; + const struct iommu_ops intel_iommu_ops = { .blocked_domain = &blocking_domain, .release_domain = &blocking_domain, + .identity_domain = &identity_domain, .capable = intel_iommu_capable, .hw_info = intel_iommu_hw_info, .domain_alloc = intel_iommu_domain_alloc, diff --git a/drivers/iommu/intel/svm.c b/drivers/iommu/intel/svm.c index 0e3a9b38bef2..ef12e95e400a 100644 --- a/drivers/iommu/intel/svm.c +++ b/drivers/iommu/intel/svm.c @@ -311,7 +311,7 @@ void intel_drain_pasid_prq(struct device *dev, u32 pasid) domain = info->domain; pdev = to_pci_dev(dev); sid = PCI_DEVID(info->bus, info->devfn); - did = domain_id_iommu(domain, iommu); + did = domain ? domain_id_iommu(domain, iommu) : FLPT_DEFAULT_DID; qdep = pci_ats_queue_depth(pdev); /* From 50a7e2c6c3b6ea2439aa2e2e392c0ca2ef567fcf Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Mon, 2 Sep 2024 10:27:17 +0800 Subject: [PATCH 325/573] iommu/vt-d: Cleanup si_domain The static identity domain has been introduced, rendering the si_domain obsolete. Remove si_domain and cleanup the code accordingly. Signed-off-by: Lu Baolu Reviewed-by: Jason Gunthorpe Reviewed-by: Kevin Tian Link: https://lore.kernel.org/r/20240809055431.36513-8-baolu.lu@linux.intel.com Signed-off-by: Joerg Roedel --- drivers/iommu/intel/iommu.c | 91 ++++++++----------------------------- 1 file changed, 19 insertions(+), 72 deletions(-) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index 14f1fcf17152..159da629349c 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -167,8 +167,6 @@ static void device_rbtree_remove(struct device_domain_info *info) spin_unlock_irqrestore(&iommu->device_rbtree_lock, flags); } -static struct dmar_domain *si_domain; - struct dmar_rmrr_unit { struct list_head list; /* list of rmrr units */ struct acpi_dmar_header *hdr; /* ACPI header */ @@ -286,11 +284,6 @@ static int __init intel_iommu_setup(char *str) } __setup("intel_iommu=", intel_iommu_setup); -static int domain_type_is_si(struct dmar_domain *domain) -{ - return domain->domain.type == IOMMU_DOMAIN_IDENTITY; -} - static int domain_pfn_supported(struct dmar_domain *domain, unsigned long pfn) { int addr_width = agaw_to_width(domain->agaw) - VTD_PAGE_SHIFT; @@ -1664,9 +1657,6 @@ static int domain_context_mapping_one(struct dmar_domain *domain, struct context_entry *context; int agaw, ret; - if (domain_type_is_si(domain)) - translation = CONTEXT_TT_PASS_THROUGH; - pr_debug("Set context mapping for %02x:%02x.%d\n", bus, PCI_SLOT(devfn), PCI_FUNC(devfn)); @@ -1685,34 +1675,24 @@ static int domain_context_mapping_one(struct dmar_domain *domain, context_set_domain_id(context, did); - if (translation != CONTEXT_TT_PASS_THROUGH) { - /* - * Skip top levels of page tables for iommu which has - * less agaw than default. Unnecessary for PT mode. - */ - for (agaw = domain->agaw; agaw > iommu->agaw; agaw--) { - ret = -ENOMEM; - pgd = phys_to_virt(dma_pte_addr(pgd)); - if (!dma_pte_present(pgd)) - goto out_unlock; - } - - if (info && info->ats_supported) - translation = CONTEXT_TT_DEV_IOTLB; - else - translation = CONTEXT_TT_MULTI_LEVEL; - - context_set_address_root(context, virt_to_phys(pgd)); - context_set_address_width(context, agaw); - } else { - /* - * In pass through mode, AW must be programmed to - * indicate the largest AGAW value supported by - * hardware. And ASR is ignored by hardware. - */ - context_set_address_width(context, iommu->msagaw); + /* + * Skip top levels of page tables for iommu which has + * less agaw than default. Unnecessary for PT mode. + */ + for (agaw = domain->agaw; agaw > iommu->agaw; agaw--) { + ret = -ENOMEM; + pgd = phys_to_virt(dma_pte_addr(pgd)); + if (!dma_pte_present(pgd)) + goto out_unlock; } + if (info && info->ats_supported) + translation = CONTEXT_TT_DEV_IOTLB; + else + translation = CONTEXT_TT_MULTI_LEVEL; + + context_set_address_root(context, virt_to_phys(pgd)); + context_set_address_width(context, agaw); context_set_translation_type(context, translation); context_set_fault_enable(context); context_set_present(context); @@ -1977,23 +1957,6 @@ static bool dev_is_real_dma_subdevice(struct device *dev) pci_real_dma_dev(to_pci_dev(dev)) != to_pci_dev(dev); } -static int md_domain_init(struct dmar_domain *domain, int guest_width); - -static int __init si_domain_init(void) -{ - si_domain = alloc_domain(IOMMU_DOMAIN_IDENTITY); - if (!si_domain) - return -EFAULT; - - if (md_domain_init(si_domain, DEFAULT_DOMAIN_ADDRESS_WIDTH)) { - domain_exit(si_domain); - si_domain = NULL; - return -EFAULT; - } - - return 0; -} - static int dmar_domain_attach_device(struct dmar_domain *domain, struct device *dev) { @@ -2016,8 +1979,6 @@ static int dmar_domain_attach_device(struct dmar_domain *domain, if (!sm_supported(iommu)) ret = domain_context_mapping(domain, dev); - else if (domain_type_is_si(domain)) - ret = intel_pasid_setup_pass_through(iommu, dev, IOMMU_NO_PASID); else if (domain->use_first_level) ret = domain_setup_first_level(iommu, domain, dev, IOMMU_NO_PASID); else @@ -2026,8 +1987,7 @@ static int dmar_domain_attach_device(struct dmar_domain *domain, if (ret) goto out_block_translation; - if (sm_supported(info->iommu) || !domain_type_is_si(info->domain)) - iommu_enable_pci_caps(info); + iommu_enable_pci_caps(info); ret = cache_tag_assign_domain(domain, dev, IOMMU_NO_PASID); if (ret) @@ -2386,10 +2346,6 @@ static int __init init_dmars(void) check_tylersburg_isoch(); - ret = si_domain_init(); - if (ret) - goto free_iommu; - /* * for each drhd * enable fault log @@ -2435,10 +2391,6 @@ free_iommu: disable_dmar_iommu(iommu); free_dmar_iommu(iommu); } - if (si_domain) { - domain_exit(si_domain); - si_domain = NULL; - } return ret; } @@ -3572,8 +3524,6 @@ static struct iommu_domain *intel_iommu_domain_alloc(unsigned type) domain->geometry.force_aperture = true; return domain; - case IOMMU_DOMAIN_IDENTITY: - return &si_domain->domain; default: return NULL; } @@ -3640,8 +3590,7 @@ static void intel_iommu_domain_free(struct iommu_domain *domain) WARN_ON(dmar_domain->nested_parent && !list_empty(&dmar_domain->s1_domains)); - if (domain != &si_domain->domain) - domain_exit(dmar_domain); + domain_exit(dmar_domain); } int prepare_domain_attach_device(struct iommu_domain *domain, @@ -4364,9 +4313,7 @@ static int intel_iommu_set_dev_pasid(struct iommu_domain *domain, if (ret) goto out_detach_iommu; - if (domain_type_is_si(dmar_domain)) - ret = intel_pasid_setup_pass_through(iommu, dev, pasid); - else if (dmar_domain->use_first_level) + if (dmar_domain->use_first_level) ret = domain_setup_first_level(iommu, dmar_domain, dev, pasid); else From 3cf74230c139f208b7fb313ae0054386eee31a81 Mon Sep 17 00:00:00 2001 From: Sanjay K Kumar Date: Mon, 2 Sep 2024 10:27:18 +0800 Subject: [PATCH 326/573] iommu/vt-d: Fix potential lockup if qi_submit_sync called with 0 count If qi_submit_sync() is invoked with 0 invalidation descriptors (for instance, for DMA draining purposes), we can run into a bug where a submitting thread fails to detect the completion of invalidation_wait. Subsequently, this led to a soft lockup. Currently, there is no impact by this bug on the existing users because no callers are submitting invalidations with 0 descriptors. This fix will enable future users (such as DMA drain) calling qi_submit_sync() with 0 count. Suppose thread T1 invokes qi_submit_sync() with non-zero descriptors, while concurrently, thread T2 calls qi_submit_sync() with zero descriptors. Both threads then enter a while loop, waiting for their respective descriptors to complete. T1 detects its completion (i.e., T1's invalidation_wait status changes to QI_DONE by HW) and proceeds to call reclaim_free_desc() to reclaim all descriptors, potentially including adjacent ones of other threads that are also marked as QI_DONE. During this time, while T2 is waiting to acquire the qi->q_lock, the IOMMU hardware may complete the invalidation for T2, setting its status to QI_DONE. However, if T1's execution of reclaim_free_desc() frees T2's invalidation_wait descriptor and changes its status to QI_FREE, T2 will not observe the QI_DONE status for its invalidation_wait and will indefinitely remain stuck. This soft lockup does not occur when only non-zero descriptors are submitted.In such cases, invalidation descriptors are interspersed among wait descriptors with the status QI_IN_USE, acting as barriers. These barriers prevent the reclaim code from mistakenly freeing descriptors belonging to other submitters. Considered the following example timeline: T1 T2 ======================================== ID1 WD1 while(WD1!=QI_DONE) unlock lock WD1=QI_DONE* WD2 while(WD2!=QI_DONE) unlock lock WD1==QI_DONE? ID1=QI_DONE WD2=DONE* reclaim() ID1=FREE WD1=FREE WD2=FREE unlock soft lockup! T2 never sees QI_DONE in WD2 Where: ID = invalidation descriptor WD = wait descriptor * Written by hardware The root of the problem is that the descriptor status QI_DONE flag is used for two conflicting purposes: 1. signal a descriptor is ready for reclaim (to be freed) 2. signal by the hardware that a wait descriptor is complete The solution (in this patch) is state separation by using QI_FREE flag for #1. Once a thread's invalidation descriptors are complete, their status would be set to QI_FREE. The reclaim_free_desc() function would then only free descriptors marked as QI_FREE instead of those marked as QI_DONE. This change ensures that T2 (from the previous example) will correctly observe the completion of its invalidation_wait (marked as QI_DONE). Signed-off-by: Sanjay K Kumar Signed-off-by: Jacob Pan Reviewed-by: Kevin Tian Link: https://lore.kernel.org/r/20240728210059.1964602-1-jacob.jun.pan@linux.intel.com Signed-off-by: Lu Baolu Signed-off-by: Joerg Roedel --- drivers/iommu/intel/dmar.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/intel/dmar.c b/drivers/iommu/intel/dmar.c index 1c8d3141cb55..01e157d89a16 100644 --- a/drivers/iommu/intel/dmar.c +++ b/drivers/iommu/intel/dmar.c @@ -1204,9 +1204,7 @@ static void free_iommu(struct intel_iommu *iommu) */ static inline void reclaim_free_desc(struct q_inval *qi) { - while (qi->desc_status[qi->free_tail] == QI_DONE || - qi->desc_status[qi->free_tail] == QI_ABORT) { - qi->desc_status[qi->free_tail] = QI_FREE; + while (qi->desc_status[qi->free_tail] == QI_FREE && qi->free_tail != qi->free_head) { qi->free_tail = (qi->free_tail + 1) % QI_LENGTH; qi->free_cnt++; } @@ -1463,8 +1461,16 @@ restart: raw_spin_lock(&qi->q_lock); } - for (i = 0; i < count; i++) - qi->desc_status[(index + i) % QI_LENGTH] = QI_DONE; + /* + * The reclaim code can free descriptors from multiple submissions + * starting from the tail of the queue. When count == 0, the + * status of the standalone wait descriptor at the tail of the queue + * must be set to QI_FREE to allow the reclaim code to proceed. + * It is also possible that descriptors from one of the previous + * submissions has to be reclaimed by a subsequent submission. + */ + for (i = 0; i <= count; i++) + qi->desc_status[(index + i) % QI_LENGTH] = QI_FREE; reclaim_free_desc(qi); raw_spin_unlock_irqrestore(&qi->q_lock, flags); From ccb02b27bb50c0f5a8f6fd745aecf4ac4beda73f Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Mon, 2 Sep 2024 10:27:19 +0800 Subject: [PATCH 327/573] iommu/vt-d: Move PCI PASID enablement to probe path Currently, PCI PASID is enabled alongside PCI ATS when an iommu domain is attached to the device and disabled when the device transitions to block translation mode. This approach is inappropriate as PCI PASID is a device feature independent of the type of the attached domain. Enable PCI PASID during the IOMMU device probe and disables it during the release path. Suggested-by: Yi Liu Signed-off-by: Lu Baolu Reviewed-by: Yi Liu Tested-by: Yi Liu Reviewed-by: Jason Gunthorpe Link: https://lore.kernel.org/r/20240819051805.116936-1-baolu.lu@linux.intel.com Signed-off-by: Joerg Roedel --- drivers/iommu/intel/iommu.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index 159da629349c..10468c871fe0 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -1281,15 +1281,6 @@ static void iommu_enable_pci_caps(struct device_domain_info *info) return; pdev = to_pci_dev(info->dev); - - /* The PCIe spec, in its wisdom, declares that the behaviour of - the device if you enable PASID support after ATS support is - undefined. So always enable PASID support on devices which - have it, even if we can't yet know if we're ever going to - use it. */ - if (info->pasid_supported && !pci_enable_pasid(pdev, info->pasid_supported & ~1)) - info->pasid_enabled = 1; - if (info->ats_supported && pci_ats_page_aligned(pdev) && !pci_enable_ats(pdev, VTD_PAGE_SHIFT)) info->ats_enabled = 1; @@ -1308,11 +1299,6 @@ static void iommu_disable_pci_caps(struct device_domain_info *info) pci_disable_ats(pdev); info->ats_enabled = 0; } - - if (info->pasid_enabled) { - pci_disable_pasid(pdev); - info->pasid_enabled = 0; - } } static void intel_flush_iotlb_all(struct iommu_domain *domain) @@ -3940,6 +3926,16 @@ static struct iommu_device *intel_iommu_probe_device(struct device *dev) intel_iommu_debugfs_create_dev(info); + /* + * The PCIe spec, in its wisdom, declares that the behaviour of the + * device is undefined if you enable PASID support after ATS support. + * So always enable PASID support on devices which have it, even if + * we can't yet know if we're ever going to use it. + */ + if (info->pasid_supported && + !pci_enable_pasid(pdev, info->pasid_supported & ~1)) + info->pasid_enabled = 1; + return &iommu->iommu; free_table: intel_pasid_free_table(dev); @@ -3956,6 +3952,11 @@ static void intel_iommu_release_device(struct device *dev) struct device_domain_info *info = dev_iommu_priv_get(dev); struct intel_iommu *iommu = info->iommu; + if (info->pasid_enabled) { + pci_disable_pasid(to_pci_dev(dev)); + info->pasid_enabled = 0; + } + mutex_lock(&iommu->iopf_lock); if (dev_is_pci(dev) && pci_ats_supported(to_pci_dev(dev))) device_rbtree_remove(info); From 1f5e307ca16c0c19186cbd56ac460a687e6daba0 Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Mon, 2 Sep 2024 10:27:20 +0800 Subject: [PATCH 328/573] iommu/vt-d: Unconditionally flush device TLB for pasid table updates The caching mode of an IOMMU is irrelevant to the behavior of the device TLB. Previously, commit <304b3bde24b5> ("iommu/vt-d: Remove caching mode check before device TLB flush") removed this redundant check in the domain unmap path. Checking the caching mode before flushing the device TLB after a pasid table entry is updated is unnecessary and can lead to inconsistent behavior. Extends this consistency by removing the caching mode check in the pasid table update path. Suggested-by: Yi Liu Signed-off-by: Lu Baolu Link: https://lore.kernel.org/r/20240820030208.20020-1-baolu.lu@linux.intel.com Signed-off-by: Joerg Roedel --- drivers/iommu/intel/pasid.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/iommu/intel/pasid.c b/drivers/iommu/intel/pasid.c index 5792c817cefa..dc00eac6be31 100644 --- a/drivers/iommu/intel/pasid.c +++ b/drivers/iommu/intel/pasid.c @@ -264,9 +264,7 @@ void intel_pasid_tear_down_entry(struct intel_iommu *iommu, struct device *dev, else iommu->flush.flush_iotlb(iommu, did, 0, 0, DMA_TLB_DSI_FLUSH); - /* Device IOTLB doesn't need to be flushed in caching mode. */ - if (!cap_caching_mode(iommu->cap)) - devtlb_invalidation_with_pasid(iommu, dev, pasid); + devtlb_invalidation_with_pasid(iommu, dev, pasid); } /* @@ -493,9 +491,7 @@ int intel_pasid_setup_dirty_tracking(struct intel_iommu *iommu, iommu->flush.flush_iotlb(iommu, did, 0, 0, DMA_TLB_DSI_FLUSH); - /* Device IOTLB doesn't need to be flushed in caching mode. */ - if (!cap_caching_mode(iommu->cap)) - devtlb_invalidation_with_pasid(iommu, dev, pasid); + devtlb_invalidation_with_pasid(iommu, dev, pasid); return 0; } @@ -572,9 +568,7 @@ void intel_pasid_setup_page_snoop_control(struct intel_iommu *iommu, pasid_cache_invalidation_with_pasid(iommu, did, pasid); qi_flush_piotlb(iommu, did, pasid, 0, -1, 0); - /* Device IOTLB doesn't need to be flushed in caching mode. */ - if (!cap_caching_mode(iommu->cap)) - devtlb_invalidation_with_pasid(iommu, dev, pasid); + devtlb_invalidation_with_pasid(iommu, dev, pasid); } /** From f701c9f36bcb7940f9c53413b508de8c9cb0321c Mon Sep 17 00:00:00 2001 From: Tina Zhang Date: Mon, 2 Sep 2024 10:27:21 +0800 Subject: [PATCH 329/573] iommu/vt-d: Factor out invalidation descriptor composition Separate the logic for constructing IOTLB and device TLB invalidation descriptors from the qi_flush interfaces. New helpers, qi_desc(), are introduced to encapsulate this common functionality. Moving descriptor composition code to new helpers enables its reuse in the upcoming qi_batch interfaces. No functional changes are intended. Signed-off-by: Tina Zhang Link: https://lore.kernel.org/r/20240815065221.50328-2-tina.zhang@intel.com Signed-off-by: Lu Baolu Signed-off-by: Joerg Roedel --- drivers/iommu/intel/dmar.c | 93 ++---------------------------- drivers/iommu/intel/iommu.h | 109 ++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 87 deletions(-) diff --git a/drivers/iommu/intel/dmar.c b/drivers/iommu/intel/dmar.c index 01e157d89a16..eaf862e8dea1 100644 --- a/drivers/iommu/intel/dmar.c +++ b/drivers/iommu/intel/dmar.c @@ -1526,24 +1526,9 @@ void qi_flush_context(struct intel_iommu *iommu, u16 did, u16 sid, u8 fm, void qi_flush_iotlb(struct intel_iommu *iommu, u16 did, u64 addr, unsigned int size_order, u64 type) { - u8 dw = 0, dr = 0; - struct qi_desc desc; - int ih = 0; - - if (cap_write_drain(iommu->cap)) - dw = 1; - - if (cap_read_drain(iommu->cap)) - dr = 1; - - desc.qw0 = QI_IOTLB_DID(did) | QI_IOTLB_DR(dr) | QI_IOTLB_DW(dw) - | QI_IOTLB_GRAN(type) | QI_IOTLB_TYPE; - desc.qw1 = QI_IOTLB_ADDR(addr) | QI_IOTLB_IH(ih) - | QI_IOTLB_AM(size_order); - desc.qw2 = 0; - desc.qw3 = 0; + qi_desc_iotlb(iommu, did, addr, size_order, type, &desc); qi_submit_sync(iommu, &desc, 1, 0); } @@ -1561,20 +1546,7 @@ void qi_flush_dev_iotlb(struct intel_iommu *iommu, u16 sid, u16 pfsid, if (!(iommu->gcmd & DMA_GCMD_TE)) return; - if (mask) { - addr |= (1ULL << (VTD_PAGE_SHIFT + mask - 1)) - 1; - desc.qw1 = QI_DEV_IOTLB_ADDR(addr) | QI_DEV_IOTLB_SIZE; - } else - desc.qw1 = QI_DEV_IOTLB_ADDR(addr); - - if (qdep >= QI_DEV_IOTLB_MAX_INVS) - qdep = 0; - - desc.qw0 = QI_DEV_IOTLB_SID(sid) | QI_DEV_IOTLB_QDEP(qdep) | - QI_DIOTLB_TYPE | QI_DEV_IOTLB_PFSID(pfsid); - desc.qw2 = 0; - desc.qw3 = 0; - + qi_desc_dev_iotlb(sid, pfsid, qdep, addr, mask, &desc); qi_submit_sync(iommu, &desc, 1, 0); } @@ -1594,28 +1566,7 @@ void qi_flush_piotlb(struct intel_iommu *iommu, u16 did, u32 pasid, u64 addr, return; } - if (npages == -1) { - desc.qw0 = QI_EIOTLB_PASID(pasid) | - QI_EIOTLB_DID(did) | - QI_EIOTLB_GRAN(QI_GRAN_NONG_PASID) | - QI_EIOTLB_TYPE; - desc.qw1 = 0; - } else { - int mask = ilog2(__roundup_pow_of_two(npages)); - unsigned long align = (1ULL << (VTD_PAGE_SHIFT + mask)); - - if (WARN_ON_ONCE(!IS_ALIGNED(addr, align))) - addr = ALIGN_DOWN(addr, align); - - desc.qw0 = QI_EIOTLB_PASID(pasid) | - QI_EIOTLB_DID(did) | - QI_EIOTLB_GRAN(QI_GRAN_PSI_PASID) | - QI_EIOTLB_TYPE; - desc.qw1 = QI_EIOTLB_ADDR(addr) | - QI_EIOTLB_IH(ih) | - QI_EIOTLB_AM(mask); - } - + qi_desc_piotlb(did, pasid, addr, npages, ih, &desc); qi_submit_sync(iommu, &desc, 1, 0); } @@ -1623,7 +1574,6 @@ void qi_flush_piotlb(struct intel_iommu *iommu, u16 did, u32 pasid, u64 addr, void qi_flush_dev_iotlb_pasid(struct intel_iommu *iommu, u16 sid, u16 pfsid, u32 pasid, u16 qdep, u64 addr, unsigned int size_order) { - unsigned long mask = 1UL << (VTD_PAGE_SHIFT + size_order - 1); struct qi_desc desc = {.qw1 = 0, .qw2 = 0, .qw3 = 0}; /* @@ -1635,40 +1585,9 @@ void qi_flush_dev_iotlb_pasid(struct intel_iommu *iommu, u16 sid, u16 pfsid, if (!(iommu->gcmd & DMA_GCMD_TE)) return; - desc.qw0 = QI_DEV_EIOTLB_PASID(pasid) | QI_DEV_EIOTLB_SID(sid) | - QI_DEV_EIOTLB_QDEP(qdep) | QI_DEIOTLB_TYPE | - QI_DEV_IOTLB_PFSID(pfsid); - - /* - * If S bit is 0, we only flush a single page. If S bit is set, - * The least significant zero bit indicates the invalidation address - * range. VT-d spec 6.5.2.6. - * e.g. address bit 12[0] indicates 8KB, 13[0] indicates 16KB. - * size order = 0 is PAGE_SIZE 4KB - * Max Invs Pending (MIP) is set to 0 for now until we have DIT in - * ECAP. - */ - if (!IS_ALIGNED(addr, VTD_PAGE_SIZE << size_order)) - pr_warn_ratelimited("Invalidate non-aligned address %llx, order %d\n", - addr, size_order); - - /* Take page address */ - desc.qw1 = QI_DEV_EIOTLB_ADDR(addr); - - if (size_order) { - /* - * Existing 0s in address below size_order may be the least - * significant bit, we must set them to 1s to avoid having - * smaller size than desired. - */ - desc.qw1 |= GENMASK_ULL(size_order + VTD_PAGE_SHIFT - 1, - VTD_PAGE_SHIFT); - /* Clear size_order bit to indicate size */ - desc.qw1 &= ~mask; - /* Set the S bit to indicate flushing more than 1 page */ - desc.qw1 |= QI_DEV_EIOTLB_SIZE; - } - + qi_desc_dev_iotlb_pasid(sid, pfsid, pasid, + qdep, addr, size_order, + &desc); qi_submit_sync(iommu, &desc, 1, 0); } diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h index 01002ae2a091..e297a322ba2d 100644 --- a/drivers/iommu/intel/iommu.h +++ b/drivers/iommu/intel/iommu.h @@ -1066,6 +1066,115 @@ static inline unsigned long nrpages_to_size(unsigned long npages) return npages << VTD_PAGE_SHIFT; } +static inline void qi_desc_iotlb(struct intel_iommu *iommu, u16 did, u64 addr, + unsigned int size_order, u64 type, + struct qi_desc *desc) +{ + u8 dw = 0, dr = 0; + int ih = 0; + + if (cap_write_drain(iommu->cap)) + dw = 1; + + if (cap_read_drain(iommu->cap)) + dr = 1; + + desc->qw0 = QI_IOTLB_DID(did) | QI_IOTLB_DR(dr) | QI_IOTLB_DW(dw) + | QI_IOTLB_GRAN(type) | QI_IOTLB_TYPE; + desc->qw1 = QI_IOTLB_ADDR(addr) | QI_IOTLB_IH(ih) + | QI_IOTLB_AM(size_order); + desc->qw2 = 0; + desc->qw3 = 0; +} + +static inline void qi_desc_dev_iotlb(u16 sid, u16 pfsid, u16 qdep, u64 addr, + unsigned int mask, struct qi_desc *desc) +{ + if (mask) { + addr |= (1ULL << (VTD_PAGE_SHIFT + mask - 1)) - 1; + desc->qw1 = QI_DEV_IOTLB_ADDR(addr) | QI_DEV_IOTLB_SIZE; + } else { + desc->qw1 = QI_DEV_IOTLB_ADDR(addr); + } + + if (qdep >= QI_DEV_IOTLB_MAX_INVS) + qdep = 0; + + desc->qw0 = QI_DEV_IOTLB_SID(sid) | QI_DEV_IOTLB_QDEP(qdep) | + QI_DIOTLB_TYPE | QI_DEV_IOTLB_PFSID(pfsid); + desc->qw2 = 0; + desc->qw3 = 0; +} + +static inline void qi_desc_piotlb(u16 did, u32 pasid, u64 addr, + unsigned long npages, bool ih, + struct qi_desc *desc) +{ + if (npages == -1) { + desc->qw0 = QI_EIOTLB_PASID(pasid) | + QI_EIOTLB_DID(did) | + QI_EIOTLB_GRAN(QI_GRAN_NONG_PASID) | + QI_EIOTLB_TYPE; + desc->qw1 = 0; + } else { + int mask = ilog2(__roundup_pow_of_two(npages)); + unsigned long align = (1ULL << (VTD_PAGE_SHIFT + mask)); + + if (WARN_ON_ONCE(!IS_ALIGNED(addr, align))) + addr = ALIGN_DOWN(addr, align); + + desc->qw0 = QI_EIOTLB_PASID(pasid) | + QI_EIOTLB_DID(did) | + QI_EIOTLB_GRAN(QI_GRAN_PSI_PASID) | + QI_EIOTLB_TYPE; + desc->qw1 = QI_EIOTLB_ADDR(addr) | + QI_EIOTLB_IH(ih) | + QI_EIOTLB_AM(mask); + } +} + +static inline void qi_desc_dev_iotlb_pasid(u16 sid, u16 pfsid, u32 pasid, + u16 qdep, u64 addr, + unsigned int size_order, + struct qi_desc *desc) +{ + unsigned long mask = 1UL << (VTD_PAGE_SHIFT + size_order - 1); + + desc->qw0 = QI_DEV_EIOTLB_PASID(pasid) | QI_DEV_EIOTLB_SID(sid) | + QI_DEV_EIOTLB_QDEP(qdep) | QI_DEIOTLB_TYPE | + QI_DEV_IOTLB_PFSID(pfsid); + + /* + * If S bit is 0, we only flush a single page. If S bit is set, + * The least significant zero bit indicates the invalidation address + * range. VT-d spec 6.5.2.6. + * e.g. address bit 12[0] indicates 8KB, 13[0] indicates 16KB. + * size order = 0 is PAGE_SIZE 4KB + * Max Invs Pending (MIP) is set to 0 for now until we have DIT in + * ECAP. + */ + if (!IS_ALIGNED(addr, VTD_PAGE_SIZE << size_order)) + pr_warn_ratelimited("Invalidate non-aligned address %llx, order %d\n", + addr, size_order); + + /* Take page address */ + desc->qw1 = QI_DEV_EIOTLB_ADDR(addr); + + if (size_order) { + /* + * Existing 0s in address below size_order may be the least + * significant bit, we must set them to 1s to avoid having + * smaller size than desired. + */ + desc->qw1 |= GENMASK_ULL(size_order + VTD_PAGE_SHIFT - 1, + VTD_PAGE_SHIFT); + /* Clear size_order bit to indicate size */ + desc->qw1 &= ~mask; + /* Set the S bit to indicate flushing more than 1 page */ + desc->qw1 |= QI_DEV_EIOTLB_SIZE; + } +} + /* Convert value to context PASID directory size field coding. */ #define context_pdts(pds) (((pds) & 0x7) << 9) From 3297d047cd7f502ea7bd949fe070bf01c02aec3e Mon Sep 17 00:00:00 2001 From: Tina Zhang Date: Mon, 2 Sep 2024 10:27:22 +0800 Subject: [PATCH 330/573] iommu/vt-d: Refactor IOTLB and Dev-IOTLB flush for batching Extracts IOTLB and Dev-IOTLB invalidation logic from cache tag flush interfaces into dedicated helper functions. It prepares the codebase for upcoming changes to support batched cache invalidations. To enable direct use of qi_flush helpers in the new functions, iommu->flush.flush_iotlb and quirk_extra_dev_tlb_flush() are opened up. No functional changes are intended. Co-developed-by: Lu Baolu Signed-off-by: Lu Baolu Signed-off-by: Tina Zhang Link: https://lore.kernel.org/r/20240815065221.50328-3-tina.zhang@intel.com Signed-off-by: Joerg Roedel --- drivers/iommu/intel/cache.c | 142 ++++++++++++++++++++---------------- drivers/iommu/intel/iommu.c | 5 +- drivers/iommu/intel/iommu.h | 3 + 3 files changed, 83 insertions(+), 67 deletions(-) diff --git a/drivers/iommu/intel/cache.c b/drivers/iommu/intel/cache.c index 44e92638c0cd..08f7ce2c16c3 100644 --- a/drivers/iommu/intel/cache.c +++ b/drivers/iommu/intel/cache.c @@ -255,6 +255,78 @@ static unsigned long calculate_psi_aligned_address(unsigned long start, return ALIGN_DOWN(start, VTD_PAGE_SIZE << mask); } +static void cache_tag_flush_iotlb(struct dmar_domain *domain, struct cache_tag *tag, + unsigned long addr, unsigned long pages, + unsigned long mask, int ih) +{ + struct intel_iommu *iommu = tag->iommu; + u64 type = DMA_TLB_PSI_FLUSH; + + if (domain->use_first_level) { + qi_flush_piotlb(iommu, tag->domain_id, tag->pasid, addr, pages, ih); + return; + } + + /* + * Fallback to domain selective flush if no PSI support or the size + * is too big. + */ + if (!cap_pgsel_inv(iommu->cap) || + mask > cap_max_amask_val(iommu->cap) || pages == -1) { + addr = 0; + mask = 0; + ih = 0; + type = DMA_TLB_DSI_FLUSH; + } + + if (ecap_qis(iommu->ecap)) + qi_flush_iotlb(iommu, tag->domain_id, addr | ih, mask, type); + else + __iommu_flush_iotlb(iommu, tag->domain_id, addr | ih, mask, type); +} + +static void cache_tag_flush_devtlb_psi(struct dmar_domain *domain, struct cache_tag *tag, + unsigned long addr, unsigned long mask) +{ + struct intel_iommu *iommu = tag->iommu; + struct device_domain_info *info; + u16 sid; + + info = dev_iommu_priv_get(tag->dev); + sid = PCI_DEVID(info->bus, info->devfn); + + if (tag->pasid == IOMMU_NO_PASID) { + qi_flush_dev_iotlb(iommu, sid, info->pfsid, info->ats_qdep, + addr, mask); + if (info->dtlb_extra_inval) + qi_flush_dev_iotlb(iommu, sid, info->pfsid, + info->ats_qdep, addr, mask); + return; + } + + qi_flush_dev_iotlb_pasid(iommu, sid, info->pfsid, tag->pasid, + info->ats_qdep, addr, mask); + if (info->dtlb_extra_inval) + qi_flush_dev_iotlb_pasid(iommu, sid, info->pfsid, tag->pasid, + info->ats_qdep, addr, mask); +} + +static void cache_tag_flush_devtlb_all(struct dmar_domain *domain, struct cache_tag *tag) +{ + struct intel_iommu *iommu = tag->iommu; + struct device_domain_info *info; + u16 sid; + + info = dev_iommu_priv_get(tag->dev); + sid = PCI_DEVID(info->bus, info->devfn); + + qi_flush_dev_iotlb(iommu, sid, info->pfsid, info->ats_qdep, 0, + MAX_AGAW_PFN_WIDTH); + if (info->dtlb_extra_inval) + qi_flush_dev_iotlb(iommu, sid, info->pfsid, info->ats_qdep, 0, + MAX_AGAW_PFN_WIDTH); +} + /* * Invalidates a range of IOVA from @start (inclusive) to @end (inclusive) * when the memory mappings in the target domain have been modified. @@ -270,30 +342,10 @@ void cache_tag_flush_range(struct dmar_domain *domain, unsigned long start, spin_lock_irqsave(&domain->cache_lock, flags); list_for_each_entry(tag, &domain->cache_tags, node) { - struct intel_iommu *iommu = tag->iommu; - struct device_domain_info *info; - u16 sid; - switch (tag->type) { case CACHE_TAG_IOTLB: case CACHE_TAG_NESTING_IOTLB: - if (domain->use_first_level) { - qi_flush_piotlb(iommu, tag->domain_id, - tag->pasid, addr, pages, ih); - } else { - /* - * Fallback to domain selective flush if no - * PSI support or the size is too big. - */ - if (!cap_pgsel_inv(iommu->cap) || - mask > cap_max_amask_val(iommu->cap)) - iommu->flush.flush_iotlb(iommu, tag->domain_id, - 0, 0, DMA_TLB_DSI_FLUSH); - else - iommu->flush.flush_iotlb(iommu, tag->domain_id, - addr | ih, mask, - DMA_TLB_PSI_FLUSH); - } + cache_tag_flush_iotlb(domain, tag, addr, pages, mask, ih); break; case CACHE_TAG_NESTING_DEVTLB: /* @@ -307,18 +359,7 @@ void cache_tag_flush_range(struct dmar_domain *domain, unsigned long start, mask = MAX_AGAW_PFN_WIDTH; fallthrough; case CACHE_TAG_DEVTLB: - info = dev_iommu_priv_get(tag->dev); - sid = PCI_DEVID(info->bus, info->devfn); - - if (tag->pasid == IOMMU_NO_PASID) - qi_flush_dev_iotlb(iommu, sid, info->pfsid, - info->ats_qdep, addr, mask); - else - qi_flush_dev_iotlb_pasid(iommu, sid, info->pfsid, - tag->pasid, info->ats_qdep, - addr, mask); - - quirk_extra_dev_tlb_flush(info, addr, mask, tag->pasid, info->ats_qdep); + cache_tag_flush_devtlb_psi(domain, tag, addr, mask); break; } @@ -338,29 +379,14 @@ void cache_tag_flush_all(struct dmar_domain *domain) spin_lock_irqsave(&domain->cache_lock, flags); list_for_each_entry(tag, &domain->cache_tags, node) { - struct intel_iommu *iommu = tag->iommu; - struct device_domain_info *info; - u16 sid; - switch (tag->type) { case CACHE_TAG_IOTLB: case CACHE_TAG_NESTING_IOTLB: - if (domain->use_first_level) - qi_flush_piotlb(iommu, tag->domain_id, - tag->pasid, 0, -1, 0); - else - iommu->flush.flush_iotlb(iommu, tag->domain_id, - 0, 0, DMA_TLB_DSI_FLUSH); + cache_tag_flush_iotlb(domain, tag, 0, -1, 0, 0); break; case CACHE_TAG_DEVTLB: case CACHE_TAG_NESTING_DEVTLB: - info = dev_iommu_priv_get(tag->dev); - sid = PCI_DEVID(info->bus, info->devfn); - - qi_flush_dev_iotlb(iommu, sid, info->pfsid, info->ats_qdep, - 0, MAX_AGAW_PFN_WIDTH); - quirk_extra_dev_tlb_flush(info, 0, MAX_AGAW_PFN_WIDTH, - IOMMU_NO_PASID, info->ats_qdep); + cache_tag_flush_devtlb_all(domain, tag); break; } @@ -399,20 +425,8 @@ void cache_tag_flush_range_np(struct dmar_domain *domain, unsigned long start, } if (tag->type == CACHE_TAG_IOTLB || - tag->type == CACHE_TAG_NESTING_IOTLB) { - /* - * Fallback to domain selective flush if no - * PSI support or the size is too big. - */ - if (!cap_pgsel_inv(iommu->cap) || - mask > cap_max_amask_val(iommu->cap)) - iommu->flush.flush_iotlb(iommu, tag->domain_id, - 0, 0, DMA_TLB_DSI_FLUSH); - else - iommu->flush.flush_iotlb(iommu, tag->domain_id, - addr, mask, - DMA_TLB_PSI_FLUSH); - } + tag->type == CACHE_TAG_NESTING_IOTLB) + cache_tag_flush_iotlb(domain, tag, addr, pages, mask, 0); trace_cache_tag_flush_range_np(tag, start, end, addr, pages, mask); } diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index 10468c871fe0..dfd33adffb14 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -1184,9 +1184,8 @@ static void __iommu_flush_context(struct intel_iommu *iommu, raw_spin_unlock_irqrestore(&iommu->register_lock, flag); } -/* return value determine if we need a write buffer flush */ -static void __iommu_flush_iotlb(struct intel_iommu *iommu, u16 did, - u64 addr, unsigned int size_order, u64 type) +void __iommu_flush_iotlb(struct intel_iommu *iommu, u16 did, u64 addr, + unsigned int size_order, u64 type) { int tlb_offset = ecap_iotlb_offset(iommu->ecap); u64 val = 0, val_iva = 0; diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h index e297a322ba2d..74634805abd1 100644 --- a/drivers/iommu/intel/iommu.h +++ b/drivers/iommu/intel/iommu.h @@ -1206,6 +1206,9 @@ void qi_flush_pasid_cache(struct intel_iommu *iommu, u16 did, u64 granu, int qi_submit_sync(struct intel_iommu *iommu, struct qi_desc *desc, unsigned int count, unsigned long options); + +void __iommu_flush_iotlb(struct intel_iommu *iommu, u16 did, u64 addr, + unsigned int size_order, u64 type); /* * Options used in qi_submit_sync: * QI_OPT_WAIT_DRAIN - Wait for PRQ drain completion, spec 6.5.2.8. From 777cdd853434849cc98ef94787538b1eb9f492d9 Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Mon, 2 Sep 2024 10:27:23 +0800 Subject: [PATCH 331/573] iommu/vt-d: Add qi_batch for dmar_domain Introduces a qi_batch structure to hold batched cache invalidation descriptors on a per-dmar_domain basis. A fixed-size descriptor array is used for simplicity. The qi_batch is allocated when the first cache tag is added to the domain and freed during iommu_free_domain(). Signed-off-by: Lu Baolu Signed-off-by: Tina Zhang Link: https://lore.kernel.org/r/20240815065221.50328-4-tina.zhang@intel.com Signed-off-by: Joerg Roedel --- drivers/iommu/intel/cache.c | 7 +++++++ drivers/iommu/intel/iommu.c | 1 + drivers/iommu/intel/iommu.h | 14 ++++++++++++++ drivers/iommu/intel/nested.c | 1 + drivers/iommu/intel/svm.c | 5 ++++- 5 files changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/intel/cache.c b/drivers/iommu/intel/cache.c index 08f7ce2c16c3..2e997d782beb 100644 --- a/drivers/iommu/intel/cache.c +++ b/drivers/iommu/intel/cache.c @@ -190,6 +190,13 @@ int cache_tag_assign_domain(struct dmar_domain *domain, u16 did = domain_get_id_for_dev(domain, dev); int ret; + /* domain->qi_bach will be freed in iommu_free_domain() path. */ + if (!domain->qi_batch) { + domain->qi_batch = kzalloc(sizeof(*domain->qi_batch), GFP_KERNEL); + if (!domain->qi_batch) + return -ENOMEM; + } + ret = __cache_tag_assign_domain(domain, did, dev, pasid); if (ret || domain->domain.type != IOMMU_DOMAIN_NESTED) return ret; diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index dfd33adffb14..038a81efaaf7 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -1572,6 +1572,7 @@ static void domain_exit(struct dmar_domain *domain) if (WARN_ON(!list_empty(&domain->devices))) return; + kfree(domain->qi_batch); kfree(domain); } diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h index 74634805abd1..d21eca94cb8f 100644 --- a/drivers/iommu/intel/iommu.h +++ b/drivers/iommu/intel/iommu.h @@ -584,6 +584,19 @@ struct iommu_domain_info { * to VT-d spec, section 9.3 */ }; +/* + * We start simply by using a fixed size for the batched descriptors. This + * size is currently sufficient for our needs. Future improvements could + * involve dynamically allocating the batch buffer based on actual demand, + * allowing us to adjust the batch size for optimal performance in different + * scenarios. + */ +#define QI_MAX_BATCHED_DESC_COUNT 16 +struct qi_batch { + struct qi_desc descs[QI_MAX_BATCHED_DESC_COUNT]; + unsigned int index; +}; + struct dmar_domain { int nid; /* node id */ struct xarray iommu_array; /* Attached IOMMU array */ @@ -608,6 +621,7 @@ struct dmar_domain { spinlock_t cache_lock; /* Protect the cache tag list */ struct list_head cache_tags; /* Cache tag list */ + struct qi_batch *qi_batch; /* Batched QI descriptors */ int iommu_superpage;/* Level of superpages supported: 0 == 4KiB (no superpages), 1 == 2MiB, diff --git a/drivers/iommu/intel/nested.c b/drivers/iommu/intel/nested.c index 36a91b1b52be..433c58944401 100644 --- a/drivers/iommu/intel/nested.c +++ b/drivers/iommu/intel/nested.c @@ -83,6 +83,7 @@ static void intel_nested_domain_free(struct iommu_domain *domain) spin_lock(&s2_domain->s1_lock); list_del(&dmar_domain->s2_link); spin_unlock(&s2_domain->s1_lock); + kfree(dmar_domain->qi_batch); kfree(dmar_domain); } diff --git a/drivers/iommu/intel/svm.c b/drivers/iommu/intel/svm.c index ef12e95e400a..078d1e32a24e 100644 --- a/drivers/iommu/intel/svm.c +++ b/drivers/iommu/intel/svm.c @@ -184,7 +184,10 @@ static void intel_mm_release(struct mmu_notifier *mn, struct mm_struct *mm) static void intel_mm_free_notifier(struct mmu_notifier *mn) { - kfree(container_of(mn, struct dmar_domain, notifier)); + struct dmar_domain *domain = container_of(mn, struct dmar_domain, notifier); + + kfree(domain->qi_batch); + kfree(domain); } static const struct mmu_notifier_ops intel_mmuops = { From 705c1cdf1e73c4c727bbfc8775434e6dd36e8baf Mon Sep 17 00:00:00 2001 From: Tina Zhang Date: Mon, 2 Sep 2024 10:27:24 +0800 Subject: [PATCH 332/573] iommu/vt-d: Introduce batched cache invalidation Converts IOTLB and Dev-IOTLB invalidation to a batched model. Cache tag invalidation requests for a domain are now accumulated in a qi_batch structure before being flushed in bulk. It replaces the previous per- request qi_flush approach with a more efficient batching mechanism. Co-developed-by: Lu Baolu Signed-off-by: Lu Baolu Signed-off-by: Tina Zhang Link: https://lore.kernel.org/r/20240815065221.50328-5-tina.zhang@intel.com Signed-off-by: Joerg Roedel --- drivers/iommu/intel/cache.c | 122 +++++++++++++++++++++++++++++++----- 1 file changed, 107 insertions(+), 15 deletions(-) diff --git a/drivers/iommu/intel/cache.c b/drivers/iommu/intel/cache.c index 2e997d782beb..e5b89f728ad3 100644 --- a/drivers/iommu/intel/cache.c +++ b/drivers/iommu/intel/cache.c @@ -262,6 +262,79 @@ static unsigned long calculate_psi_aligned_address(unsigned long start, return ALIGN_DOWN(start, VTD_PAGE_SIZE << mask); } +static void qi_batch_flush_descs(struct intel_iommu *iommu, struct qi_batch *batch) +{ + if (!iommu || !batch->index) + return; + + qi_submit_sync(iommu, batch->descs, batch->index, 0); + + /* Reset the index value and clean the whole batch buffer. */ + memset(batch, 0, sizeof(*batch)); +} + +static void qi_batch_increment_index(struct intel_iommu *iommu, struct qi_batch *batch) +{ + if (++batch->index == QI_MAX_BATCHED_DESC_COUNT) + qi_batch_flush_descs(iommu, batch); +} + +static void qi_batch_add_iotlb(struct intel_iommu *iommu, u16 did, u64 addr, + unsigned int size_order, u64 type, + struct qi_batch *batch) +{ + qi_desc_iotlb(iommu, did, addr, size_order, type, &batch->descs[batch->index]); + qi_batch_increment_index(iommu, batch); +} + +static void qi_batch_add_dev_iotlb(struct intel_iommu *iommu, u16 sid, u16 pfsid, + u16 qdep, u64 addr, unsigned int mask, + struct qi_batch *batch) +{ + /* + * According to VT-d spec, software is recommended to not submit any Device-TLB + * invalidation requests while address remapping hardware is disabled. + */ + if (!(iommu->gcmd & DMA_GCMD_TE)) + return; + + qi_desc_dev_iotlb(sid, pfsid, qdep, addr, mask, &batch->descs[batch->index]); + qi_batch_increment_index(iommu, batch); +} + +static void qi_batch_add_piotlb(struct intel_iommu *iommu, u16 did, u32 pasid, + u64 addr, unsigned long npages, bool ih, + struct qi_batch *batch) +{ + /* + * npages == -1 means a PASID-selective invalidation, otherwise, + * a positive value for Page-selective-within-PASID invalidation. + * 0 is not a valid input. + */ + if (!npages) + return; + + qi_desc_piotlb(did, pasid, addr, npages, ih, &batch->descs[batch->index]); + qi_batch_increment_index(iommu, batch); +} + +static void qi_batch_add_pasid_dev_iotlb(struct intel_iommu *iommu, u16 sid, u16 pfsid, + u32 pasid, u16 qdep, u64 addr, + unsigned int size_order, struct qi_batch *batch) +{ + /* + * According to VT-d spec, software is recommended to not submit any + * Device-TLB invalidation requests while address remapping hardware + * is disabled. + */ + if (!(iommu->gcmd & DMA_GCMD_TE)) + return; + + qi_desc_dev_iotlb_pasid(sid, pfsid, pasid, qdep, addr, size_order, + &batch->descs[batch->index]); + qi_batch_increment_index(iommu, batch); +} + static void cache_tag_flush_iotlb(struct dmar_domain *domain, struct cache_tag *tag, unsigned long addr, unsigned long pages, unsigned long mask, int ih) @@ -270,7 +343,8 @@ static void cache_tag_flush_iotlb(struct dmar_domain *domain, struct cache_tag * u64 type = DMA_TLB_PSI_FLUSH; if (domain->use_first_level) { - qi_flush_piotlb(iommu, tag->domain_id, tag->pasid, addr, pages, ih); + qi_batch_add_piotlb(iommu, tag->domain_id, tag->pasid, addr, + pages, ih, domain->qi_batch); return; } @@ -287,7 +361,8 @@ static void cache_tag_flush_iotlb(struct dmar_domain *domain, struct cache_tag * } if (ecap_qis(iommu->ecap)) - qi_flush_iotlb(iommu, tag->domain_id, addr | ih, mask, type); + qi_batch_add_iotlb(iommu, tag->domain_id, addr | ih, mask, type, + domain->qi_batch); else __iommu_flush_iotlb(iommu, tag->domain_id, addr | ih, mask, type); } @@ -303,19 +378,20 @@ static void cache_tag_flush_devtlb_psi(struct dmar_domain *domain, struct cache_ sid = PCI_DEVID(info->bus, info->devfn); if (tag->pasid == IOMMU_NO_PASID) { - qi_flush_dev_iotlb(iommu, sid, info->pfsid, info->ats_qdep, - addr, mask); + qi_batch_add_dev_iotlb(iommu, sid, info->pfsid, info->ats_qdep, + addr, mask, domain->qi_batch); if (info->dtlb_extra_inval) - qi_flush_dev_iotlb(iommu, sid, info->pfsid, - info->ats_qdep, addr, mask); + qi_batch_add_dev_iotlb(iommu, sid, info->pfsid, info->ats_qdep, + addr, mask, domain->qi_batch); return; } - qi_flush_dev_iotlb_pasid(iommu, sid, info->pfsid, tag->pasid, - info->ats_qdep, addr, mask); + qi_batch_add_pasid_dev_iotlb(iommu, sid, info->pfsid, tag->pasid, + info->ats_qdep, addr, mask, domain->qi_batch); if (info->dtlb_extra_inval) - qi_flush_dev_iotlb_pasid(iommu, sid, info->pfsid, tag->pasid, - info->ats_qdep, addr, mask); + qi_batch_add_pasid_dev_iotlb(iommu, sid, info->pfsid, tag->pasid, + info->ats_qdep, addr, mask, + domain->qi_batch); } static void cache_tag_flush_devtlb_all(struct dmar_domain *domain, struct cache_tag *tag) @@ -327,11 +403,11 @@ static void cache_tag_flush_devtlb_all(struct dmar_domain *domain, struct cache_ info = dev_iommu_priv_get(tag->dev); sid = PCI_DEVID(info->bus, info->devfn); - qi_flush_dev_iotlb(iommu, sid, info->pfsid, info->ats_qdep, 0, - MAX_AGAW_PFN_WIDTH); + qi_batch_add_dev_iotlb(iommu, sid, info->pfsid, info->ats_qdep, 0, + MAX_AGAW_PFN_WIDTH, domain->qi_batch); if (info->dtlb_extra_inval) - qi_flush_dev_iotlb(iommu, sid, info->pfsid, info->ats_qdep, 0, - MAX_AGAW_PFN_WIDTH); + qi_batch_add_dev_iotlb(iommu, sid, info->pfsid, info->ats_qdep, 0, + MAX_AGAW_PFN_WIDTH, domain->qi_batch); } /* @@ -341,6 +417,7 @@ static void cache_tag_flush_devtlb_all(struct dmar_domain *domain, struct cache_ void cache_tag_flush_range(struct dmar_domain *domain, unsigned long start, unsigned long end, int ih) { + struct intel_iommu *iommu = NULL; unsigned long pages, mask, addr; struct cache_tag *tag; unsigned long flags; @@ -349,6 +426,10 @@ void cache_tag_flush_range(struct dmar_domain *domain, unsigned long start, spin_lock_irqsave(&domain->cache_lock, flags); list_for_each_entry(tag, &domain->cache_tags, node) { + if (iommu && iommu != tag->iommu) + qi_batch_flush_descs(iommu, domain->qi_batch); + iommu = tag->iommu; + switch (tag->type) { case CACHE_TAG_IOTLB: case CACHE_TAG_NESTING_IOTLB: @@ -372,6 +453,7 @@ void cache_tag_flush_range(struct dmar_domain *domain, unsigned long start, trace_cache_tag_flush_range(tag, start, end, addr, pages, mask); } + qi_batch_flush_descs(iommu, domain->qi_batch); spin_unlock_irqrestore(&domain->cache_lock, flags); } @@ -381,11 +463,16 @@ void cache_tag_flush_range(struct dmar_domain *domain, unsigned long start, */ void cache_tag_flush_all(struct dmar_domain *domain) { + struct intel_iommu *iommu = NULL; struct cache_tag *tag; unsigned long flags; spin_lock_irqsave(&domain->cache_lock, flags); list_for_each_entry(tag, &domain->cache_tags, node) { + if (iommu && iommu != tag->iommu) + qi_batch_flush_descs(iommu, domain->qi_batch); + iommu = tag->iommu; + switch (tag->type) { case CACHE_TAG_IOTLB: case CACHE_TAG_NESTING_IOTLB: @@ -399,6 +486,7 @@ void cache_tag_flush_all(struct dmar_domain *domain) trace_cache_tag_flush_all(tag); } + qi_batch_flush_descs(iommu, domain->qi_batch); spin_unlock_irqrestore(&domain->cache_lock, flags); } @@ -416,6 +504,7 @@ void cache_tag_flush_all(struct dmar_domain *domain) void cache_tag_flush_range_np(struct dmar_domain *domain, unsigned long start, unsigned long end) { + struct intel_iommu *iommu = NULL; unsigned long pages, mask, addr; struct cache_tag *tag; unsigned long flags; @@ -424,7 +513,9 @@ void cache_tag_flush_range_np(struct dmar_domain *domain, unsigned long start, spin_lock_irqsave(&domain->cache_lock, flags); list_for_each_entry(tag, &domain->cache_tags, node) { - struct intel_iommu *iommu = tag->iommu; + if (iommu && iommu != tag->iommu) + qi_batch_flush_descs(iommu, domain->qi_batch); + iommu = tag->iommu; if (!cap_caching_mode(iommu->cap) || domain->use_first_level) { iommu_flush_write_buffer(iommu); @@ -437,5 +528,6 @@ void cache_tag_flush_range_np(struct dmar_domain *domain, unsigned long start, trace_cache_tag_flush_range_np(tag, start, end, addr, pages, mask); } + qi_batch_flush_descs(iommu, domain->qi_batch); spin_unlock_irqrestore(&domain->cache_lock, flags); } From 59090e479ac78ae18facd4c58eb332562a23020e Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 29 Aug 2024 11:29:11 +0800 Subject: [PATCH 333/573] mm, slub: avoid zeroing kmalloc redzone Since commit 946fa0dbf2d8 ("mm/slub: extend redzone check to extra allocated kmalloc space than requested"), setting orig_size treats the wasted space (object_size - orig_size) as a redzone. However with init_on_free=1 we clear the full object->size, including the redzone. Additionally we clear the object metadata, including the stored orig_size, making it zero, which makes check_object() treat the whole object as a redzone. These issues lead to the following BUG report with "slub_debug=FUZ init_on_free=1": [ 0.000000] ============================================================================= [ 0.000000] BUG kmalloc-8 (Not tainted): kmalloc Redzone overwritten [ 0.000000] ----------------------------------------------------------------------------- [ 0.000000] [ 0.000000] 0xffff000010032858-0xffff00001003285f @offset=2136. First byte 0x0 instead of 0xcc [ 0.000000] FIX kmalloc-8: Restoring kmalloc Redzone 0xffff000010032858-0xffff00001003285f=0xcc [ 0.000000] Slab 0xfffffdffc0400c80 objects=36 used=23 fp=0xffff000010032a18 flags=0x3fffe0000000200(workingset|node=0|zone=0|lastcpupid=0x1ffff) [ 0.000000] Object 0xffff000010032858 @offset=2136 fp=0xffff0000100328c8 [ 0.000000] [ 0.000000] Redzone ffff000010032850: cc cc cc cc cc cc cc cc ........ [ 0.000000] Object ffff000010032858: cc cc cc cc cc cc cc cc ........ [ 0.000000] Redzone ffff000010032860: cc cc cc cc cc cc cc cc ........ [ 0.000000] Padding ffff0000100328b4: 00 00 00 00 00 00 00 00 00 00 00 00 ............ [ 0.000000] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 6.11.0-rc3-next-20240814-00004-g61844c55c3f4 #144 [ 0.000000] Hardware name: NXP i.MX95 19X19 board (DT) [ 0.000000] Call trace: [ 0.000000] dump_backtrace+0x90/0xe8 [ 0.000000] show_stack+0x18/0x24 [ 0.000000] dump_stack_lvl+0x74/0x8c [ 0.000000] dump_stack+0x18/0x24 [ 0.000000] print_trailer+0x150/0x218 [ 0.000000] check_object+0xe4/0x454 [ 0.000000] free_to_partial_list+0x2f8/0x5ec To address the issue, use orig_size to clear the used area. And restore the value of orig_size after clear the remaining area. When CONFIG_SLUB_DEBUG not defined, (get_orig_size()' directly returns s->object_size. So when using memset to init the area, the size can simply be orig_size, as orig_size returns object_size when CONFIG_SLUB_DEBUG not enabled. And orig_size can never be bigger than object_size. Fixes: 946fa0dbf2d8 ("mm/slub: extend redzone check to extra allocated kmalloc space than requested") Cc: Reviewed-by: Feng Tang Acked-by: David Rientjes Signed-off-by: Peng Fan Signed-off-by: Vlastimil Babka --- mm/slub.c | 100 +++++++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 47 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 60004bfc2dc2..d52c88f29f69 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -756,6 +756,50 @@ static inline bool slab_update_freelist(struct kmem_cache *s, struct slab *slab, return false; } +/* + * kmalloc caches has fixed sizes (mostly power of 2), and kmalloc() API + * family will round up the real request size to these fixed ones, so + * there could be an extra area than what is requested. Save the original + * request size in the meta data area, for better debug and sanity check. + */ +static inline void set_orig_size(struct kmem_cache *s, + void *object, unsigned int orig_size) +{ + void *p = kasan_reset_tag(object); + unsigned int kasan_meta_size; + + if (!slub_debug_orig_size(s)) + return; + + /* + * KASAN can save its free meta data inside of the object at offset 0. + * If this meta data size is larger than 'orig_size', it will overlap + * the data redzone in [orig_size+1, object_size]. Thus, we adjust + * 'orig_size' to be as at least as big as KASAN's meta data. + */ + kasan_meta_size = kasan_metadata_size(s, true); + if (kasan_meta_size > orig_size) + orig_size = kasan_meta_size; + + p += get_info_end(s); + p += sizeof(struct track) * 2; + + *(unsigned int *)p = orig_size; +} + +static inline unsigned int get_orig_size(struct kmem_cache *s, void *object) +{ + void *p = kasan_reset_tag(object); + + if (!slub_debug_orig_size(s)) + return s->object_size; + + p += get_info_end(s); + p += sizeof(struct track) * 2; + + return *(unsigned int *)p; +} + #ifdef CONFIG_SLUB_DEBUG static unsigned long object_map[BITS_TO_LONGS(MAX_OBJS_PER_PAGE)]; static DEFINE_SPINLOCK(object_map_lock); @@ -985,50 +1029,6 @@ static void print_slab_info(const struct slab *slab) &slab->__page_flags); } -/* - * kmalloc caches has fixed sizes (mostly power of 2), and kmalloc() API - * family will round up the real request size to these fixed ones, so - * there could be an extra area than what is requested. Save the original - * request size in the meta data area, for better debug and sanity check. - */ -static inline void set_orig_size(struct kmem_cache *s, - void *object, unsigned int orig_size) -{ - void *p = kasan_reset_tag(object); - unsigned int kasan_meta_size; - - if (!slub_debug_orig_size(s)) - return; - - /* - * KASAN can save its free meta data inside of the object at offset 0. - * If this meta data size is larger than 'orig_size', it will overlap - * the data redzone in [orig_size+1, object_size]. Thus, we adjust - * 'orig_size' to be as at least as big as KASAN's meta data. - */ - kasan_meta_size = kasan_metadata_size(s, true); - if (kasan_meta_size > orig_size) - orig_size = kasan_meta_size; - - p += get_info_end(s); - p += sizeof(struct track) * 2; - - *(unsigned int *)p = orig_size; -} - -static inline unsigned int get_orig_size(struct kmem_cache *s, void *object) -{ - void *p = kasan_reset_tag(object); - - if (!slub_debug_orig_size(s)) - return s->object_size; - - p += get_info_end(s); - p += sizeof(struct track) * 2; - - return *(unsigned int *)p; -} - void skip_orig_size_check(struct kmem_cache *s, const void *object) { set_orig_size(s, (void *)object, s->object_size); @@ -1894,7 +1894,6 @@ static inline void inc_slabs_node(struct kmem_cache *s, int node, int objects) {} static inline void dec_slabs_node(struct kmem_cache *s, int node, int objects) {} - #ifndef CONFIG_SLUB_TINY static bool freelist_corrupted(struct kmem_cache *s, struct slab *slab, void **freelist, void *nextfree) @@ -2239,14 +2238,21 @@ bool slab_free_hook(struct kmem_cache *s, void *x, bool init) */ if (unlikely(init)) { int rsize; - unsigned int inuse; + unsigned int inuse, orig_size; inuse = get_info_end(s); + orig_size = get_orig_size(s, x); if (!kasan_has_integrated_init()) - memset(kasan_reset_tag(x), 0, s->object_size); + memset(kasan_reset_tag(x), 0, orig_size); rsize = (s->flags & SLAB_RED_ZONE) ? s->red_left_pad : 0; memset((char *)kasan_reset_tag(x) + inuse, 0, s->size - inuse - rsize); + /* + * Restore orig_size, otherwize kmalloc redzone overwritten + * would be reported + */ + set_orig_size(s, x, orig_size); + } /* KASAN might put x into memory quarantine, delaying its reuse. */ return !kasan_slab_free(s, x, init); From ee601384079cf683b4750a2ea9edbe653e444ad8 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Wed, 28 Aug 2024 15:24:53 +0000 Subject: [PATCH 334/573] dt-bindings: mmc: Add support for rk3576 dw-mshc Add the compatible string for rockchip,rk3576-dw-mshc in its own new block, for devices that have internal phase settings instead of external clocks. Signed-off-by: Detlev Casanova Acked-by: Krzysztof Kozlowski Reviewed-by: Heiko Stuebner Link: https://lore.kernel.org/r/010201919996f687-08c1988a-f588-46fa-ad82-023068c316ba-000000@eu-west-1.amazonses.com Signed-off-by: Ulf Hansson --- Documentation/devicetree/bindings/mmc/rockchip-dw-mshc.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/rockchip-dw-mshc.yaml b/Documentation/devicetree/bindings/mmc/rockchip-dw-mshc.yaml index 211cd0b0bc5f..06df1269f247 100644 --- a/Documentation/devicetree/bindings/mmc/rockchip-dw-mshc.yaml +++ b/Documentation/devicetree/bindings/mmc/rockchip-dw-mshc.yaml @@ -43,6 +43,8 @@ properties: - rockchip,rv1108-dw-mshc - rockchip,rv1126-dw-mshc - const: rockchip,rk3288-dw-mshc + # for Rockchip RK3576 with phase tuning inside the controller + - const: rockchip,rk3576-dw-mshc reg: maxItems: 1 From 59903441f5e49d46478fefcccec41e2ba896b740 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Wed, 28 Aug 2024 15:24:55 +0000 Subject: [PATCH 335/573] mmc: dw_mmc-rockchip: Add internal phase support Some Rockchip devices put the phase settings into the dw_mmc controller. When the feature is present, the ciu-drive and ciu-sample clocks are not used and the phase configuration is done directly through the mmc controller. Signed-off-by: Shawn Lin Signed-off-by: Detlev Casanova Acked-by: Shawn Lin Reviewed-by: Heiko Stuebner Link: https://lore.kernel.org/r/010201919996fdae-8a9f843e-00a8-4131-98bf-a9da4ed04bfd-000000@eu-west-1.amazonses.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc-rockchip.c | 171 +++++++++++++++++++++++++++-- 1 file changed, 160 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/host/dw_mmc-rockchip.c b/drivers/mmc/host/dw_mmc-rockchip.c index b07190ba4b7a..75e9ac4bcd08 100644 --- a/drivers/mmc/host/dw_mmc-rockchip.c +++ b/drivers/mmc/host/dw_mmc-rockchip.c @@ -15,7 +15,17 @@ #include "dw_mmc.h" #include "dw_mmc-pltfm.h" -#define RK3288_CLKGEN_DIV 2 +#define RK3288_CLKGEN_DIV 2 +#define SDMMC_TIMING_CON0 0x130 +#define SDMMC_TIMING_CON1 0x134 +#define ROCKCHIP_MMC_DELAY_SEL BIT(10) +#define ROCKCHIP_MMC_DEGREE_MASK 0x3 +#define ROCKCHIP_MMC_DEGREE_OFFSET 1 +#define ROCKCHIP_MMC_DELAYNUM_OFFSET 2 +#define ROCKCHIP_MMC_DELAYNUM_MASK (0xff << ROCKCHIP_MMC_DELAYNUM_OFFSET) +#define ROCKCHIP_MMC_DELAY_ELEMENT_PSEC 60 +#define HIWORD_UPDATE(val, mask, shift) \ + ((val) << (shift) | (mask) << ((shift) + 16)) static const unsigned int freqs[] = { 100000, 200000, 300000, 400000 }; @@ -24,8 +34,143 @@ struct dw_mci_rockchip_priv_data { struct clk *sample_clk; int default_sample_phase; int num_phases; + bool internal_phase; }; +/* + * Each fine delay is between 44ps-77ps. Assume each fine delay is 60ps to + * simplify calculations. So 45degs could be anywhere between 33deg and 57.8deg. + */ +static int rockchip_mmc_get_internal_phase(struct dw_mci *host, bool sample) +{ + unsigned long rate = clk_get_rate(host->ciu_clk); + u32 raw_value; + u16 degrees; + u32 delay_num = 0; + + /* Constant signal, no measurable phase shift */ + if (!rate) + return 0; + + if (sample) + raw_value = mci_readl(host, TIMING_CON1); + else + raw_value = mci_readl(host, TIMING_CON0); + + raw_value >>= ROCKCHIP_MMC_DEGREE_OFFSET; + degrees = (raw_value & ROCKCHIP_MMC_DEGREE_MASK) * 90; + + if (raw_value & ROCKCHIP_MMC_DELAY_SEL) { + /* degrees/delaynum * 1000000 */ + unsigned long factor = (ROCKCHIP_MMC_DELAY_ELEMENT_PSEC / 10) * + 36 * (rate / 10000); + + delay_num = (raw_value & ROCKCHIP_MMC_DELAYNUM_MASK); + delay_num >>= ROCKCHIP_MMC_DELAYNUM_OFFSET; + degrees += DIV_ROUND_CLOSEST(delay_num * factor, 1000000); + } + + return degrees % 360; +} + +static int rockchip_mmc_get_phase(struct dw_mci *host, bool sample) +{ + struct dw_mci_rockchip_priv_data *priv = host->priv; + struct clk *clock = sample ? priv->sample_clk : priv->drv_clk; + + if (priv->internal_phase) + return rockchip_mmc_get_internal_phase(host, sample); + else + return clk_get_phase(clock); +} + +static int rockchip_mmc_set_internal_phase(struct dw_mci *host, bool sample, int degrees) +{ + unsigned long rate = clk_get_rate(host->ciu_clk); + u8 nineties, remainder; + u8 delay_num; + u32 raw_value; + u32 delay; + + /* + * The below calculation is based on the output clock from + * MMC host to the card, which expects the phase clock inherits + * the clock rate from its parent, namely the output clock + * provider of MMC host. However, things may go wrong if + * (1) It is orphan. + * (2) It is assigned to the wrong parent. + * + * This check help debug the case (1), which seems to be the + * most likely problem we often face and which makes it difficult + * for people to debug unstable mmc tuning results. + */ + if (!rate) { + dev_err(host->dev, "%s: invalid clk rate\n", __func__); + return -EINVAL; + } + + nineties = degrees / 90; + remainder = (degrees % 90); + + /* + * Due to the inexact nature of the "fine" delay, we might + * actually go non-monotonic. We don't go _too_ monotonic + * though, so we should be OK. Here are options of how we may + * work: + * + * Ideally we end up with: + * 1.0, 2.0, ..., 69.0, 70.0, ..., 89.0, 90.0 + * + * On one extreme (if delay is actually 44ps): + * .73, 1.5, ..., 50.6, 51.3, ..., 65.3, 90.0 + * The other (if delay is actually 77ps): + * 1.3, 2.6, ..., 88.6. 89.8, ..., 114.0, 90 + * + * It's possible we might make a delay that is up to 25 + * degrees off from what we think we're making. That's OK + * though because we should be REALLY far from any bad range. + */ + + /* + * Convert to delay; do a little extra work to make sure we + * don't overflow 32-bit / 64-bit numbers. + */ + delay = 10000000; /* PSECS_PER_SEC / 10000 / 10 */ + delay *= remainder; + delay = DIV_ROUND_CLOSEST(delay, + (rate / 1000) * 36 * + (ROCKCHIP_MMC_DELAY_ELEMENT_PSEC / 10)); + + delay_num = (u8) min_t(u32, delay, 255); + + raw_value = delay_num ? ROCKCHIP_MMC_DELAY_SEL : 0; + raw_value |= delay_num << ROCKCHIP_MMC_DELAYNUM_OFFSET; + raw_value |= nineties; + + if (sample) + mci_writel(host, TIMING_CON1, HIWORD_UPDATE(raw_value, 0x07ff, 1)); + else + mci_writel(host, TIMING_CON0, HIWORD_UPDATE(raw_value, 0x07ff, 1)); + + dev_dbg(host->dev, "set %s_phase(%d) delay_nums=%u actual_degrees=%d\n", + sample ? "sample" : "drv", degrees, delay_num, + rockchip_mmc_get_phase(host, sample) + ); + + return 0; +} + +static int rockchip_mmc_set_phase(struct dw_mci *host, bool sample, int degrees) +{ + struct dw_mci_rockchip_priv_data *priv = host->priv; + struct clk *clock = sample ? priv->sample_clk : priv->drv_clk; + + if (priv->internal_phase) + return rockchip_mmc_set_internal_phase(host, sample, degrees); + else + return clk_set_phase(clock, degrees); +} + static void dw_mci_rk3288_set_ios(struct dw_mci *host, struct mmc_ios *ios) { struct dw_mci_rockchip_priv_data *priv = host->priv; @@ -64,7 +209,7 @@ static void dw_mci_rk3288_set_ios(struct dw_mci *host, struct mmc_ios *ios) /* Make sure we use phases which we can enumerate with */ if (!IS_ERR(priv->sample_clk) && ios->timing <= MMC_TIMING_SD_HS) - clk_set_phase(priv->sample_clk, priv->default_sample_phase); + rockchip_mmc_set_phase(host, true, priv->default_sample_phase); /* * Set the drive phase offset based on speed mode to achieve hold times. @@ -127,7 +272,7 @@ static void dw_mci_rk3288_set_ios(struct dw_mci *host, struct mmc_ios *ios) break; } - clk_set_phase(priv->drv_clk, phase); + rockchip_mmc_set_phase(host, false, phase); } } @@ -151,6 +296,7 @@ static int dw_mci_rk3288_execute_tuning(struct dw_mci_slot *slot, u32 opcode) int longest_range_len = -1; int longest_range = -1; int middle_phase; + int phase; if (IS_ERR(priv->sample_clk)) { dev_err(host->dev, "Tuning clock (sample_clk) not defined.\n"); @@ -164,8 +310,10 @@ static int dw_mci_rk3288_execute_tuning(struct dw_mci_slot *slot, u32 opcode) /* Try each phase and extract good ranges */ for (i = 0; i < priv->num_phases; ) { - clk_set_phase(priv->sample_clk, - TUNING_ITERATION_TO_PHASE(i, priv->num_phases)); + rockchip_mmc_set_phase(host, true, + TUNING_ITERATION_TO_PHASE( + i, + priv->num_phases)); v = !mmc_send_tuning(mmc, opcode, NULL); @@ -211,7 +359,8 @@ static int dw_mci_rk3288_execute_tuning(struct dw_mci_slot *slot, u32 opcode) } if (ranges[0].start == 0 && ranges[0].end == priv->num_phases - 1) { - clk_set_phase(priv->sample_clk, priv->default_sample_phase); + rockchip_mmc_set_phase(host, true, priv->default_sample_phase); + dev_info(host->dev, "All phases work, using default phase %d.", priv->default_sample_phase); goto free; @@ -248,12 +397,10 @@ static int dw_mci_rk3288_execute_tuning(struct dw_mci_slot *slot, u32 opcode) middle_phase = ranges[longest_range].start + longest_range_len / 2; middle_phase %= priv->num_phases; - dev_info(host->dev, "Successfully tuned phase to %d\n", - TUNING_ITERATION_TO_PHASE(middle_phase, priv->num_phases)); + phase = TUNING_ITERATION_TO_PHASE(middle_phase, priv->num_phases); + dev_info(host->dev, "Successfully tuned phase to %d\n", phase); - clk_set_phase(priv->sample_clk, - TUNING_ITERATION_TO_PHASE(middle_phase, - priv->num_phases)); + rockchip_mmc_set_phase(host, true, phase); free: kfree(ranges); @@ -287,6 +434,8 @@ static int dw_mci_rk3288_parse_dt(struct dw_mci *host) host->priv = priv; + priv->internal_phase = false; + return 0; } From 73abb1f16e28d5a41d0abea779a3f0b75cf8823e Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Wed, 28 Aug 2024 15:24:56 +0000 Subject: [PATCH 336/573] mmc: dw_mmc-rockchip: Add support for rk3576 SoCs On rk3576 the tunable clocks are inside the controller itself, removing the need for the "ciu-drive" and "ciu-sample" clocks. That makes it a new type of controller that has its own dt_parse function. Signed-off-by: Detlev Casanova Reviewed-by: Heiko Stuebner Link: https://lore.kernel.org/r/010201919997044d-c3a008d1-afbc-462f-a928-fc1ece785bdb-000000@eu-west-1.amazonses.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/dw_mmc-rockchip.c | 48 ++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/dw_mmc-rockchip.c b/drivers/mmc/host/dw_mmc-rockchip.c index 75e9ac4bcd08..f96260fd143b 100644 --- a/drivers/mmc/host/dw_mmc-rockchip.c +++ b/drivers/mmc/host/dw_mmc-rockchip.c @@ -407,7 +407,7 @@ free: return ret; } -static int dw_mci_rk3288_parse_dt(struct dw_mci *host) +static int dw_mci_common_parse_dt(struct dw_mci *host) { struct device_node *np = host->dev->of_node; struct dw_mci_rockchip_priv_data *priv; @@ -417,13 +417,29 @@ static int dw_mci_rk3288_parse_dt(struct dw_mci *host) return -ENOMEM; if (of_property_read_u32(np, "rockchip,desired-num-phases", - &priv->num_phases)) + &priv->num_phases)) priv->num_phases = 360; if (of_property_read_u32(np, "rockchip,default-sample-phase", - &priv->default_sample_phase)) + &priv->default_sample_phase)) priv->default_sample_phase = 0; + host->priv = priv; + + return 0; +} + +static int dw_mci_rk3288_parse_dt(struct dw_mci *host) +{ + struct dw_mci_rockchip_priv_data *priv; + int err; + + err = dw_mci_common_parse_dt(host); + if (err) + return err; + + priv = host->priv; + priv->drv_clk = devm_clk_get(host->dev, "ciu-drive"); if (IS_ERR(priv->drv_clk)) dev_dbg(host->dev, "ciu-drive not available\n"); @@ -432,13 +448,25 @@ static int dw_mci_rk3288_parse_dt(struct dw_mci *host) if (IS_ERR(priv->sample_clk)) dev_dbg(host->dev, "ciu-sample not available\n"); - host->priv = priv; - priv->internal_phase = false; return 0; } +static int dw_mci_rk3576_parse_dt(struct dw_mci *host) +{ + struct dw_mci_rockchip_priv_data *priv; + int err = dw_mci_common_parse_dt(host); + if (err) + return err; + + priv = host->priv; + + priv->internal_phase = true; + + return 0; +} + static int dw_mci_rockchip_init(struct dw_mci *host) { int ret, i; @@ -480,11 +508,21 @@ static const struct dw_mci_drv_data rk3288_drv_data = { .init = dw_mci_rockchip_init, }; +static const struct dw_mci_drv_data rk3576_drv_data = { + .common_caps = MMC_CAP_CMD23, + .set_ios = dw_mci_rk3288_set_ios, + .execute_tuning = dw_mci_rk3288_execute_tuning, + .parse_dt = dw_mci_rk3576_parse_dt, + .init = dw_mci_rockchip_init, +}; + static const struct of_device_id dw_mci_rockchip_match[] = { { .compatible = "rockchip,rk2928-dw-mshc", .data = &rk2928_drv_data }, { .compatible = "rockchip,rk3288-dw-mshc", .data = &rk3288_drv_data }, + { .compatible = "rockchip,rk3576-dw-mshc", + .data = &rk3576_drv_data }, {}, }; MODULE_DEVICE_TABLE(of, dw_mci_rockchip_match); From 4c0a6a0ac902dbf184ae7be1f1fce225cc0b7380 Mon Sep 17 00:00:00 2001 From: Chanwoo Lee Date: Thu, 29 Aug 2024 11:47:09 +0900 Subject: [PATCH 337/573] mmc: core: Replace the argument of mmc_sd_switch() with defines Replace with already defined values for readability. While at it, let's also change the mode-parameter from an int to bool, as the only used values are 0 or 1. Signed-off-by: Chanwoo Lee Link: https://lore.kernel.org/r/20240829024709.402285-1-cw9316.lee@samsung.com Signed-off-by: Ulf Hansson --- drivers/mmc/core/sd.c | 13 ++++++++----- drivers/mmc/core/sd_ops.c | 3 +-- include/linux/mmc/host.h | 3 ++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index ee37ad14e79e..d7318c2647da 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -346,7 +346,7 @@ static int mmc_read_switch(struct mmc_card *card) * The argument does not matter, as the support bits do not * change with the arguments. */ - err = mmc_sd_switch(card, 0, 0, 0, status); + err = mmc_sd_switch(card, SD_SWITCH_CHECK, 0, 0, status); if (err) { /* * If the host or the card can't do the switch, @@ -402,7 +402,8 @@ int mmc_sd_switch_hs(struct mmc_card *card) if (!status) return -ENOMEM; - err = mmc_sd_switch(card, 1, 0, HIGH_SPEED_BUS_SPEED, status); + err = mmc_sd_switch(card, SD_SWITCH_SET, 0, + HIGH_SPEED_BUS_SPEED, status); if (err) goto out; @@ -434,7 +435,8 @@ static int sd_select_driver_type(struct mmc_card *card, u8 *status) card_drv_type, &drv_type); if (drive_strength) { - err = mmc_sd_switch(card, 1, 2, drive_strength, status); + err = mmc_sd_switch(card, SD_SWITCH_SET, 2, + drive_strength, status); if (err) return err; if ((status[15] & 0xF) != drive_strength) { @@ -514,7 +516,7 @@ static int sd_set_bus_speed_mode(struct mmc_card *card, u8 *status) return 0; } - err = mmc_sd_switch(card, 1, 0, card->sd_bus_speed, status); + err = mmc_sd_switch(card, SD_SWITCH_SET, 0, card->sd_bus_speed, status); if (err) return err; @@ -605,7 +607,8 @@ static int sd_set_current_limit(struct mmc_card *card, u8 *status) current_limit = SD_SET_CURRENT_LIMIT_200; if (current_limit != SD_SET_CURRENT_NO_CHANGE) { - err = mmc_sd_switch(card, 1, 3, current_limit, status); + err = mmc_sd_switch(card, SD_SWITCH_SET, 3, + current_limit, status); if (err) return err; diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c index 8b9b34286ef3..f93c392040ae 100644 --- a/drivers/mmc/core/sd_ops.c +++ b/drivers/mmc/core/sd_ops.c @@ -336,14 +336,13 @@ int mmc_app_send_scr(struct mmc_card *card) return 0; } -int mmc_sd_switch(struct mmc_card *card, int mode, int group, +int mmc_sd_switch(struct mmc_card *card, bool mode, int group, u8 value, u8 *resp) { u32 cmd_args; /* NOTE: caller guarantees resp is heap-allocated */ - mode = !!mode; value &= 0xF; cmd_args = mode << 31 | 0x00FFFFFF; cmd_args &= ~(0xF << (group * 4)); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index ac01cd1622ef..6a31ed02d3ff 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -648,7 +648,8 @@ static inline void mmc_debugfs_err_stats_inc(struct mmc_host *host, host->err_stats[stat] += 1; } -int mmc_sd_switch(struct mmc_card *card, int mode, int group, u8 value, u8 *resp); +int mmc_sd_switch(struct mmc_card *card, bool mode, int group, + u8 value, u8 *resp); int mmc_send_status(struct mmc_card *card, u32 *status); int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error); int mmc_send_abort_tuning(struct mmc_host *host, u32 opcode); From 03117a4934d6ea3cc9c84baf80c05322485a2b5a Mon Sep 17 00:00:00 2001 From: Seunghwan Baek Date: Thu, 29 Aug 2024 15:18:23 +0900 Subject: [PATCH 338/573] mmc: cqhci: Make use of cqhci_halted() routine Make use of cqhci_halted() in couple places to avoid open-coding. Signed-off-by: Seunghwan Baek Reviewed-by: Ritesh Harjani Acked-by: Adrian Hunter Link: https://lore.kernel.org/r/20240829061823.3718-3-sh8267.baek@samsung.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/cqhci-core.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/cqhci-core.c b/drivers/mmc/host/cqhci-core.c index a02da26a1efd..178277d90c31 100644 --- a/drivers/mmc/host/cqhci-core.c +++ b/drivers/mmc/host/cqhci-core.c @@ -33,6 +33,11 @@ struct cqhci_slot { #define CQHCI_HOST_OTHER BIT(4) }; +static bool cqhci_halted(struct cqhci_host *cq_host) +{ + return cqhci_readl(cq_host, CQHCI_CTL) & CQHCI_HALT; +} + static inline u8 *get_desc(struct cqhci_host *cq_host, u8 tag) { return cq_host->desc_base + (tag * cq_host->slot_sz); @@ -282,7 +287,7 @@ static void __cqhci_enable(struct cqhci_host *cq_host) cqhci_writel(cq_host, cqcfg, CQHCI_CFG); - if (cqhci_readl(cq_host, CQHCI_CTL) & CQHCI_HALT) + if (cqhci_halted(cq_host)) cqhci_writel(cq_host, 0, CQHCI_CTL); mmc->cqe_on = true; @@ -617,7 +622,7 @@ static int cqhci_request(struct mmc_host *mmc, struct mmc_request *mrq) cqhci_writel(cq_host, 0, CQHCI_CTL); mmc->cqe_on = true; pr_debug("%s: cqhci: CQE on\n", mmc_hostname(mmc)); - if (cqhci_readl(cq_host, CQHCI_CTL) & CQHCI_HALT) { + if (cqhci_halted(cq_host)) { pr_err("%s: cqhci: CQE failed to exit halt state\n", mmc_hostname(mmc)); } @@ -953,11 +958,6 @@ static bool cqhci_clear_all_tasks(struct mmc_host *mmc, unsigned int timeout) return ret; } -static bool cqhci_halted(struct cqhci_host *cq_host) -{ - return cqhci_readl(cq_host, CQHCI_CTL) & CQHCI_HALT; -} - static bool cqhci_halt(struct mmc_host *mmc, unsigned int timeout) { struct cqhci_host *cq_host = mmc->cqe_private; From d2253bfa830e907eddad13d9de99d7b6df8585a1 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Sun, 1 Sep 2024 23:03:09 +0530 Subject: [PATCH 339/573] mmc: core: Calculate size from pointer Calculate the size from pointer instead of struct to adhere to linux kernel coding style. Issue reported by checkpatch. This commit has no functional changes. Signed-off-by: Riyan Dhiman Link: https://lore.kernel.org/r/20240901173309.7124-1-riyandhiman14@gmail.com Signed-off-by: Ulf Hansson --- drivers/mmc/core/block.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index cc7318089cf2..2ff053f2be11 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -2530,7 +2530,7 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, return ERR_PTR(devidx); } - md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL); + md = kzalloc(sizeof(*md), GFP_KERNEL); if (!md) { ret = -ENOMEM; goto out; From 6f25e5deca7739a27462ef26fb5c07cfb926c4e4 Mon Sep 17 00:00:00 2001 From: Riyan Dhiman Date: Sun, 1 Sep 2024 23:52:44 +0530 Subject: [PATCH 340/573] mmc: core: Convert simple_stroul to kstroul simple_strtoul() is obsolete and lacks proper error handling, making it unsafe for converting strings to unsigned long values. Replace it with kstrtoul(), which provides robust error checking and better safety. This change improves the reliability of the string-to-integer conversion and aligns with current kernel coding standards. Error handling is added to catch conversion failures, returning -EINVAL when input is invalid. Issue reported by checkpatch: - WARNING: simple_strtoul is obsolete, use kstrtoul instead Signed-off-by: Riyan Dhiman Link: https://lore.kernel.org/r/20240901182244.45543-1-riyandhiman14@gmail.com Signed-off-by: Ulf Hansson --- drivers/mmc/core/block.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index 2ff053f2be11..f58bea534004 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -353,10 +353,10 @@ static ssize_t force_ro_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; - char *end; struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); - unsigned long set = simple_strtoul(buf, &end, 0); - if (end == buf) { + unsigned long set; + + if (kstrtoul(buf, 0, &set)) { ret = -EINVAL; goto out; } From 1c97ea115f89d096ec403f0827cc01671e3daba3 Mon Sep 17 00:00:00 2001 From: Dharma Balasubiramani Date: Mon, 2 Sep 2024 16:27:09 +0530 Subject: [PATCH 341/573] dt-bindings: mmc: sdhci-atmel: Convert to json schema Convert sdhci-atmel documentation to yaml format. The new file will inherit from sdhci-common.yaml. Note: Add microchip,sama7g5-sdhci to compatible list as we already use it in the DT. Signed-off-by: Dharma Balasubiramani Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240902-atmel-sdhci-v4-1-96912fab6b2d@microchip.com Signed-off-by: Ulf Hansson --- .../bindings/mmc/atmel,sama5d2-sdhci.yaml | 92 +++++++++++++++++++ .../devicetree/bindings/mmc/sdhci-atmel.txt | 35 ------- 2 files changed, 92 insertions(+), 35 deletions(-) create mode 100644 Documentation/devicetree/bindings/mmc/atmel,sama5d2-sdhci.yaml delete mode 100644 Documentation/devicetree/bindings/mmc/sdhci-atmel.txt diff --git a/Documentation/devicetree/bindings/mmc/atmel,sama5d2-sdhci.yaml b/Documentation/devicetree/bindings/mmc/atmel,sama5d2-sdhci.yaml new file mode 100644 index 000000000000..8c8ade88e8fe --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/atmel,sama5d2-sdhci.yaml @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mmc/atmel,sama5d2-sdhci.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Atmel SDHCI controller + +maintainers: + - Aubin Constans + - Nicolas Ferre + +description: + Bindings for the SDHCI controller found in Atmel/Microchip SoCs. + +properties: + compatible: + oneOf: + - enum: + - atmel,sama5d2-sdhci + - microchip,sam9x60-sdhci + - items: + - enum: + - microchip,sam9x7-sdhci + - microchip,sama7g5-sdhci + - const: microchip,sam9x60-sdhci + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: hclock + - description: multclk + - description: baseclk + minItems: 2 + + clock-names: + items: + - const: hclock + - const: multclk + - const: baseclk + minItems: 2 + + microchip,sdcal-inverted: + type: boolean + description: + When present, polarity on the SDCAL SoC pin is inverted. The default + polarity for this signal is described in the datasheet. For instance on + SAMA5D2, the pin is usually tied to the GND with a resistor and a + capacitor (see "SDMMC I/O Calibration" chapter). + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + +allOf: + - $ref: sdhci-common.yaml# + - if: + properties: + compatible: + contains: + enum: + - atmel,sama5d2-sdhci + then: + properties: + clocks: + minItems: 3 + clock-names: + minItems: 3 + +unevaluatedProperties: false + +examples: + - | + #include + #include + mmc@a0000000 { + compatible = "atmel,sama5d2-sdhci"; + reg = <0xa0000000 0x300>; + interrupts = <31 IRQ_TYPE_LEVEL_HIGH 0>; + clocks = <&sdmmc0_hclk>, <&sdmmc0_gclk>, <&main>; + clock-names = "hclock", "multclk", "baseclk"; + assigned-clocks = <&sdmmc0_gclk>; + assigned-clock-rates = <480000000>; + }; diff --git a/Documentation/devicetree/bindings/mmc/sdhci-atmel.txt b/Documentation/devicetree/bindings/mmc/sdhci-atmel.txt deleted file mode 100644 index a9fb0a91245f..000000000000 --- a/Documentation/devicetree/bindings/mmc/sdhci-atmel.txt +++ /dev/null @@ -1,35 +0,0 @@ -* Atmel SDHCI controller - -This file documents the differences between the core properties in -Documentation/devicetree/bindings/mmc/mmc.txt and the properties used by the -sdhci-of-at91 driver. - -Required properties: -- compatible: Must be "atmel,sama5d2-sdhci" or "microchip,sam9x60-sdhci" - or "microchip,sam9x7-sdhci", "microchip,sam9x60-sdhci". -- clocks: Phandlers to the clocks. -- clock-names: Must be "hclock", "multclk", "baseclk" for - "atmel,sama5d2-sdhci". - Must be "hclock", "multclk" for "microchip,sam9x60-sdhci". - Must be "hclock", "multclk" for "microchip,sam9x7-sdhci". - -Optional properties: -- assigned-clocks: The same with "multclk". -- assigned-clock-rates The rate of "multclk" in order to not rely on the - gck configuration set by previous components. -- microchip,sdcal-inverted: when present, polarity on the SDCAL SoC pin is - inverted. The default polarity for this signal is described in the datasheet. - For instance on SAMA5D2, the pin is usually tied to the GND with a resistor - and a capacitor (see "SDMMC I/O Calibration" chapter). - -Example: - -mmc0: sdio-host@a0000000 { - compatible = "atmel,sama5d2-sdhci"; - reg = <0xa0000000 0x300>; - interrupts = <31 IRQ_TYPE_LEVEL_HIGH 0>; - clocks = <&sdmmc0_hclk>, <&sdmmc0_gclk>, <&main>; - clock-names = "hclock", "multclk", "baseclk"; - assigned-clocks = <&sdmmc0_gclk>; - assigned-clock-rates = <480000000>; -}; From 38fb699795f0f24f1dcbe175537ec9c2ba500fa4 Mon Sep 17 00:00:00 2001 From: Avri Altman Date: Mon, 2 Sep 2024 15:33:31 +0300 Subject: [PATCH 342/573] mmc: core Convert UNSTUFF_BITS macro to inline function The UNSTUFF_BITS macro, which is defined in both drivers/mmc/core/mmc.c and drivers/mmc/core/sd.c, has been converted to an inline function to improve readability, maintainability, and type safety. Signed-off-by: Avri Altman Link: https://lore.kernel.org/r/20240902123331.3566447-1-avri.altman@wdc.com Signed-off-by: Ulf Hansson --- drivers/mmc/core/mmc.c | 106 ++++++++++++++------------------ drivers/mmc/core/mmc_ops.h | 14 +++++ drivers/mmc/core/sd.c | 120 ++++++++++++++++--------------------- 3 files changed, 113 insertions(+), 127 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 5b2f7c285461..6a23be214543 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -51,20 +51,6 @@ static const unsigned int taac_mant[] = { 35, 40, 45, 50, 55, 60, 70, 80, }; -#define UNSTUFF_BITS(resp,start,size) \ - ({ \ - const int __size = size; \ - const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \ - const int __off = 3 - ((start) / 32); \ - const int __shft = (start) & 31; \ - u32 __res; \ - \ - __res = resp[__off] >> __shft; \ - if (__size + __shft > 32) \ - __res |= resp[__off-1] << ((32 - __shft) % 32); \ - __res & __mask; \ - }) - /* * Given the decoded CSD structure, decode the raw CID to our CID structure. */ @@ -85,36 +71,36 @@ static int mmc_decode_cid(struct mmc_card *card) switch (card->csd.mmca_vsn) { case 0: /* MMC v1.0 - v1.2 */ case 1: /* MMC v1.4 */ - card->cid.manfid = UNSTUFF_BITS(resp, 104, 24); - card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8); - card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8); - card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8); - card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8); - card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8); - card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8); - card->cid.prod_name[6] = UNSTUFF_BITS(resp, 48, 8); - card->cid.hwrev = UNSTUFF_BITS(resp, 44, 4); - card->cid.fwrev = UNSTUFF_BITS(resp, 40, 4); - card->cid.serial = UNSTUFF_BITS(resp, 16, 24); - card->cid.month = UNSTUFF_BITS(resp, 12, 4); - card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997; + card->cid.manfid = unstuff_bits(resp, 104, 24); + card->cid.prod_name[0] = unstuff_bits(resp, 96, 8); + card->cid.prod_name[1] = unstuff_bits(resp, 88, 8); + card->cid.prod_name[2] = unstuff_bits(resp, 80, 8); + card->cid.prod_name[3] = unstuff_bits(resp, 72, 8); + card->cid.prod_name[4] = unstuff_bits(resp, 64, 8); + card->cid.prod_name[5] = unstuff_bits(resp, 56, 8); + card->cid.prod_name[6] = unstuff_bits(resp, 48, 8); + card->cid.hwrev = unstuff_bits(resp, 44, 4); + card->cid.fwrev = unstuff_bits(resp, 40, 4); + card->cid.serial = unstuff_bits(resp, 16, 24); + card->cid.month = unstuff_bits(resp, 12, 4); + card->cid.year = unstuff_bits(resp, 8, 4) + 1997; break; case 2: /* MMC v2.0 - v2.2 */ case 3: /* MMC v3.1 - v3.3 */ case 4: /* MMC v4 */ - card->cid.manfid = UNSTUFF_BITS(resp, 120, 8); - card->cid.oemid = UNSTUFF_BITS(resp, 104, 16); - card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8); - card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8); - card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8); - card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8); - card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8); - card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8); - card->cid.prv = UNSTUFF_BITS(resp, 48, 8); - card->cid.serial = UNSTUFF_BITS(resp, 16, 32); - card->cid.month = UNSTUFF_BITS(resp, 12, 4); - card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997; + card->cid.manfid = unstuff_bits(resp, 120, 8); + card->cid.oemid = unstuff_bits(resp, 104, 16); + card->cid.prod_name[0] = unstuff_bits(resp, 96, 8); + card->cid.prod_name[1] = unstuff_bits(resp, 88, 8); + card->cid.prod_name[2] = unstuff_bits(resp, 80, 8); + card->cid.prod_name[3] = unstuff_bits(resp, 72, 8); + card->cid.prod_name[4] = unstuff_bits(resp, 64, 8); + card->cid.prod_name[5] = unstuff_bits(resp, 56, 8); + card->cid.prv = unstuff_bits(resp, 48, 8); + card->cid.serial = unstuff_bits(resp, 16, 32); + card->cid.month = unstuff_bits(resp, 12, 4); + card->cid.year = unstuff_bits(resp, 8, 4) + 1997; break; default: @@ -161,43 +147,43 @@ static int mmc_decode_csd(struct mmc_card *card) * v1.2 has extra information in bits 15, 11 and 10. * We also support eMMC v4.4 & v4.41. */ - csd->structure = UNSTUFF_BITS(resp, 126, 2); + csd->structure = unstuff_bits(resp, 126, 2); if (csd->structure == 0) { pr_err("%s: unrecognised CSD structure version %d\n", mmc_hostname(card->host), csd->structure); return -EINVAL; } - csd->mmca_vsn = UNSTUFF_BITS(resp, 122, 4); - m = UNSTUFF_BITS(resp, 115, 4); - e = UNSTUFF_BITS(resp, 112, 3); + csd->mmca_vsn = unstuff_bits(resp, 122, 4); + m = unstuff_bits(resp, 115, 4); + e = unstuff_bits(resp, 112, 3); csd->taac_ns = (taac_exp[e] * taac_mant[m] + 9) / 10; - csd->taac_clks = UNSTUFF_BITS(resp, 104, 8) * 100; + csd->taac_clks = unstuff_bits(resp, 104, 8) * 100; - m = UNSTUFF_BITS(resp, 99, 4); - e = UNSTUFF_BITS(resp, 96, 3); + m = unstuff_bits(resp, 99, 4); + e = unstuff_bits(resp, 96, 3); csd->max_dtr = tran_exp[e] * tran_mant[m]; - csd->cmdclass = UNSTUFF_BITS(resp, 84, 12); + csd->cmdclass = unstuff_bits(resp, 84, 12); - e = UNSTUFF_BITS(resp, 47, 3); - m = UNSTUFF_BITS(resp, 62, 12); + e = unstuff_bits(resp, 47, 3); + m = unstuff_bits(resp, 62, 12); csd->capacity = (1 + m) << (e + 2); - csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4); - csd->read_partial = UNSTUFF_BITS(resp, 79, 1); - csd->write_misalign = UNSTUFF_BITS(resp, 78, 1); - csd->read_misalign = UNSTUFF_BITS(resp, 77, 1); - csd->dsr_imp = UNSTUFF_BITS(resp, 76, 1); - csd->r2w_factor = UNSTUFF_BITS(resp, 26, 3); - csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4); - csd->write_partial = UNSTUFF_BITS(resp, 21, 1); + csd->read_blkbits = unstuff_bits(resp, 80, 4); + csd->read_partial = unstuff_bits(resp, 79, 1); + csd->write_misalign = unstuff_bits(resp, 78, 1); + csd->read_misalign = unstuff_bits(resp, 77, 1); + csd->dsr_imp = unstuff_bits(resp, 76, 1); + csd->r2w_factor = unstuff_bits(resp, 26, 3); + csd->write_blkbits = unstuff_bits(resp, 22, 4); + csd->write_partial = unstuff_bits(resp, 21, 1); if (csd->write_blkbits >= 9) { - a = UNSTUFF_BITS(resp, 42, 5); - b = UNSTUFF_BITS(resp, 37, 5); + a = unstuff_bits(resp, 42, 5); + b = unstuff_bits(resp, 37, 5); csd->erase_size = (a + 1) * (b + 1); csd->erase_size <<= csd->write_blkbits - 9; - csd->wp_grp_size = UNSTUFF_BITS(resp, 32, 5); + csd->wp_grp_size = unstuff_bits(resp, 32, 5); } return 0; diff --git a/drivers/mmc/core/mmc_ops.h b/drivers/mmc/core/mmc_ops.h index 92d4194c7893..06017110e1b0 100644 --- a/drivers/mmc/core/mmc_ops.h +++ b/drivers/mmc/core/mmc_ops.h @@ -56,5 +56,19 @@ int mmc_cmdq_enable(struct mmc_card *card); int mmc_cmdq_disable(struct mmc_card *card); int mmc_sanitize(struct mmc_card *card, unsigned int timeout_ms); +static inline u32 unstuff_bits(const u32 *resp, int start, int size) +{ + const int __size = size; + const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; + const int __off = 3 - (start / 32); + const int __shft = start & 31; + u32 __res = resp[__off] >> __shft; + + if (__size + __shft > 32) + __res |= resp[__off - 1] << ((32 - __shft) % 32); + + return __res & __mask; +} + #endif diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index d7318c2647da..12fe282bea77 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -56,20 +56,6 @@ static const unsigned int sd_au_size[] = { SZ_16M / 512, (SZ_16M + SZ_8M) / 512, SZ_32M / 512, SZ_64M / 512, }; -#define UNSTUFF_BITS(resp,start,size) \ - ({ \ - const int __size = size; \ - const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \ - const int __off = 3 - ((start) / 32); \ - const int __shft = (start) & 31; \ - u32 __res; \ - \ - __res = resp[__off] >> __shft; \ - if (__size + __shft > 32) \ - __res |= resp[__off-1] << ((32 - __shft) % 32); \ - __res & __mask; \ - }) - #define SD_POWEROFF_NOTIFY_TIMEOUT_MS 1000 #define SD_WRITE_EXTR_SINGLE_TIMEOUT_MS 1000 @@ -95,18 +81,18 @@ void mmc_decode_cid(struct mmc_card *card) * SD doesn't currently have a version field so we will * have to assume we can parse this. */ - card->cid.manfid = UNSTUFF_BITS(resp, 120, 8); - card->cid.oemid = UNSTUFF_BITS(resp, 104, 16); - card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8); - card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8); - card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8); - card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8); - card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8); - card->cid.hwrev = UNSTUFF_BITS(resp, 60, 4); - card->cid.fwrev = UNSTUFF_BITS(resp, 56, 4); - card->cid.serial = UNSTUFF_BITS(resp, 24, 32); - card->cid.year = UNSTUFF_BITS(resp, 12, 8); - card->cid.month = UNSTUFF_BITS(resp, 8, 4); + card->cid.manfid = unstuff_bits(resp, 120, 8); + card->cid.oemid = unstuff_bits(resp, 104, 16); + card->cid.prod_name[0] = unstuff_bits(resp, 96, 8); + card->cid.prod_name[1] = unstuff_bits(resp, 88, 8); + card->cid.prod_name[2] = unstuff_bits(resp, 80, 8); + card->cid.prod_name[3] = unstuff_bits(resp, 72, 8); + card->cid.prod_name[4] = unstuff_bits(resp, 64, 8); + card->cid.hwrev = unstuff_bits(resp, 60, 4); + card->cid.fwrev = unstuff_bits(resp, 56, 4); + card->cid.serial = unstuff_bits(resp, 24, 32); + card->cid.year = unstuff_bits(resp, 12, 8); + card->cid.month = unstuff_bits(resp, 8, 4); card->cid.year += 2000; /* SD cards year offset */ } @@ -120,41 +106,41 @@ static int mmc_decode_csd(struct mmc_card *card) unsigned int e, m, csd_struct; u32 *resp = card->raw_csd; - csd_struct = UNSTUFF_BITS(resp, 126, 2); + csd_struct = unstuff_bits(resp, 126, 2); switch (csd_struct) { case 0: - m = UNSTUFF_BITS(resp, 115, 4); - e = UNSTUFF_BITS(resp, 112, 3); + m = unstuff_bits(resp, 115, 4); + e = unstuff_bits(resp, 112, 3); csd->taac_ns = (taac_exp[e] * taac_mant[m] + 9) / 10; - csd->taac_clks = UNSTUFF_BITS(resp, 104, 8) * 100; + csd->taac_clks = unstuff_bits(resp, 104, 8) * 100; - m = UNSTUFF_BITS(resp, 99, 4); - e = UNSTUFF_BITS(resp, 96, 3); + m = unstuff_bits(resp, 99, 4); + e = unstuff_bits(resp, 96, 3); csd->max_dtr = tran_exp[e] * tran_mant[m]; - csd->cmdclass = UNSTUFF_BITS(resp, 84, 12); + csd->cmdclass = unstuff_bits(resp, 84, 12); - e = UNSTUFF_BITS(resp, 47, 3); - m = UNSTUFF_BITS(resp, 62, 12); + e = unstuff_bits(resp, 47, 3); + m = unstuff_bits(resp, 62, 12); csd->capacity = (1 + m) << (e + 2); - csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4); - csd->read_partial = UNSTUFF_BITS(resp, 79, 1); - csd->write_misalign = UNSTUFF_BITS(resp, 78, 1); - csd->read_misalign = UNSTUFF_BITS(resp, 77, 1); - csd->dsr_imp = UNSTUFF_BITS(resp, 76, 1); - csd->r2w_factor = UNSTUFF_BITS(resp, 26, 3); - csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4); - csd->write_partial = UNSTUFF_BITS(resp, 21, 1); + csd->read_blkbits = unstuff_bits(resp, 80, 4); + csd->read_partial = unstuff_bits(resp, 79, 1); + csd->write_misalign = unstuff_bits(resp, 78, 1); + csd->read_misalign = unstuff_bits(resp, 77, 1); + csd->dsr_imp = unstuff_bits(resp, 76, 1); + csd->r2w_factor = unstuff_bits(resp, 26, 3); + csd->write_blkbits = unstuff_bits(resp, 22, 4); + csd->write_partial = unstuff_bits(resp, 21, 1); - if (UNSTUFF_BITS(resp, 46, 1)) { + if (unstuff_bits(resp, 46, 1)) { csd->erase_size = 1; } else if (csd->write_blkbits >= 9) { - csd->erase_size = UNSTUFF_BITS(resp, 39, 7) + 1; + csd->erase_size = unstuff_bits(resp, 39, 7) + 1; csd->erase_size <<= csd->write_blkbits - 9; } - if (UNSTUFF_BITS(resp, 13, 1)) + if (unstuff_bits(resp, 13, 1)) mmc_card_set_readonly(card); break; case 1: @@ -169,17 +155,17 @@ static int mmc_decode_csd(struct mmc_card *card) csd->taac_ns = 0; /* Unused */ csd->taac_clks = 0; /* Unused */ - m = UNSTUFF_BITS(resp, 99, 4); - e = UNSTUFF_BITS(resp, 96, 3); + m = unstuff_bits(resp, 99, 4); + e = unstuff_bits(resp, 96, 3); csd->max_dtr = tran_exp[e] * tran_mant[m]; - csd->cmdclass = UNSTUFF_BITS(resp, 84, 12); - csd->c_size = UNSTUFF_BITS(resp, 48, 22); + csd->cmdclass = unstuff_bits(resp, 84, 12); + csd->c_size = unstuff_bits(resp, 48, 22); /* SDXC cards have a minimum C_SIZE of 0x00FFFF */ if (csd->c_size >= 0xFFFF) mmc_card_set_ext_capacity(card); - m = UNSTUFF_BITS(resp, 48, 22); + m = unstuff_bits(resp, 48, 22); csd->capacity = (1 + m) << 10; csd->read_blkbits = 9; @@ -191,7 +177,7 @@ static int mmc_decode_csd(struct mmc_card *card) csd->write_partial = 0; csd->erase_size = 1; - if (UNSTUFF_BITS(resp, 13, 1)) + if (unstuff_bits(resp, 13, 1)) mmc_card_set_readonly(card); break; default: @@ -217,33 +203,33 @@ static int mmc_decode_scr(struct mmc_card *card) resp[3] = card->raw_scr[1]; resp[2] = card->raw_scr[0]; - scr_struct = UNSTUFF_BITS(resp, 60, 4); + scr_struct = unstuff_bits(resp, 60, 4); if (scr_struct != 0) { pr_err("%s: unrecognised SCR structure version %d\n", mmc_hostname(card->host), scr_struct); return -EINVAL; } - scr->sda_vsn = UNSTUFF_BITS(resp, 56, 4); - scr->bus_widths = UNSTUFF_BITS(resp, 48, 4); + scr->sda_vsn = unstuff_bits(resp, 56, 4); + scr->bus_widths = unstuff_bits(resp, 48, 4); if (scr->sda_vsn == SCR_SPEC_VER_2) /* Check if Physical Layer Spec v3.0 is supported */ - scr->sda_spec3 = UNSTUFF_BITS(resp, 47, 1); + scr->sda_spec3 = unstuff_bits(resp, 47, 1); if (scr->sda_spec3) { - scr->sda_spec4 = UNSTUFF_BITS(resp, 42, 1); - scr->sda_specx = UNSTUFF_BITS(resp, 38, 4); + scr->sda_spec4 = unstuff_bits(resp, 42, 1); + scr->sda_specx = unstuff_bits(resp, 38, 4); } - if (UNSTUFF_BITS(resp, 55, 1)) + if (unstuff_bits(resp, 55, 1)) card->erased_byte = 0xFF; else card->erased_byte = 0x0; if (scr->sda_spec4) - scr->cmds = UNSTUFF_BITS(resp, 32, 4); + scr->cmds = unstuff_bits(resp, 32, 4); else if (scr->sda_spec3) - scr->cmds = UNSTUFF_BITS(resp, 32, 2); + scr->cmds = unstuff_bits(resp, 32, 2); /* SD Spec says: any SD Card shall set at least bits 0 and 2 */ if (!(scr->bus_widths & SD_SCR_BUS_WIDTH_1) || @@ -289,17 +275,17 @@ static int mmc_read_ssr(struct mmc_card *card) kfree(raw_ssr); /* - * UNSTUFF_BITS only works with four u32s so we have to offset the + * unstuff_bits only works with four u32s so we have to offset the * bitfield positions accordingly. */ - au = UNSTUFF_BITS(card->raw_ssr, 428 - 384, 4); + au = unstuff_bits(card->raw_ssr, 428 - 384, 4); if (au) { if (au <= 9 || card->scr.sda_spec3) { card->ssr.au = sd_au_size[au]; - es = UNSTUFF_BITS(card->raw_ssr, 408 - 384, 16); - et = UNSTUFF_BITS(card->raw_ssr, 402 - 384, 6); + es = unstuff_bits(card->raw_ssr, 408 - 384, 16); + et = unstuff_bits(card->raw_ssr, 402 - 384, 6); if (es && et) { - eo = UNSTUFF_BITS(card->raw_ssr, 400 - 384, 2); + eo = unstuff_bits(card->raw_ssr, 400 - 384, 2); card->ssr.erase_timeout = (et * 1000) / es; card->ssr.erase_offset = eo * 1000; } @@ -313,7 +299,7 @@ static int mmc_read_ssr(struct mmc_card *card) * starting SD5.1 discard is supported if DISCARD_SUPPORT (b313) is set */ resp[3] = card->raw_ssr[6]; - discard_support = UNSTUFF_BITS(resp, 313 - 288, 1); + discard_support = unstuff_bits(resp, 313 - 288, 1); card->erase_arg = (card->scr.sda_specx && discard_support) ? SD_DISCARD_ARG : SD_ERASE_ARG; From b6c41df38c24f42482b5c5cd05220b8eb32ff6ff Mon Sep 17 00:00:00 2001 From: Jens Wiklander Date: Mon, 2 Sep 2024 17:12:30 +0200 Subject: [PATCH 343/573] mmc: block: add RPMB dependency Prevent build error when CONFIG_RPMB=m and CONFIG_MMC_BLOCK=y by adding a dependency to CONFIG_RPMB for CONFIG_MMC_BLOCK block so the RPMB subsystem always is reachable if configured. This means that CONFIG_MMC_BLOCK automatically becomes compiled as a module if CONFIG_RPMB is compiled as a module. If CONFIG_RPMB isn't configured or is configured as built-in, CONFIG_MMC_BLOCK will remain unchanged. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202409021448.RSvcBPzt-lkp@intel.com/ Fixes: 7852028a35f0 ("mmc: block: register RPMB partition with the RPMB subsystem") Signed-off-by: Jens Wiklander Acked-by: Arnd Bergmann Link: https://lore.kernel.org/r/20240902151231.3705204-1-jens.wiklander@linaro.org Signed-off-by: Ulf Hansson --- drivers/mmc/core/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig index bf4e29ef023c..14d2ecbb04d3 100644 --- a/drivers/mmc/core/Kconfig +++ b/drivers/mmc/core/Kconfig @@ -37,6 +37,7 @@ config PWRSEQ_SIMPLE config MMC_BLOCK tristate "MMC block device driver" depends on BLOCK + depends on RPMB || !RPMB imply IOSCHED_BFQ default y help From edd3183c5c5f9fe7aede49a41678556bc8bf618f Mon Sep 17 00:00:00 2001 From: Jens Wiklander Date: Mon, 2 Sep 2024 17:12:31 +0200 Subject: [PATCH 344/573] optee: add RPMB dependency Prevent build error when CONFIG_RPMB=m and CONFIG_OPTEE=y by adding a dependency to CONFIG_RPMB for CONFIG_OPTEE so the RPMB subsystem always is reachable if configured. This means that CONFIG_OPTEE automatically becomes compiled as a module if CONFIG_RPMB is compiled as a module. If CONFIG_RPMB isn't configured or is configured as built-in, CONFIG_OPTEE will remain unchanged. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202409021448.RSvcBPzt-lkp@intel.com/ Fixes: f0c8431568ee ("optee: probe RPMB device using RPMB subsystem") Signed-off-by: Jens Wiklander Link: https://lore.kernel.org/r/20240902151231.3705204-2-jens.wiklander@linaro.org Signed-off-by: Ulf Hansson --- drivers/tee/optee/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/tee/optee/Kconfig b/drivers/tee/optee/Kconfig index 976928641aa6..7bb7990d0b07 100644 --- a/drivers/tee/optee/Kconfig +++ b/drivers/tee/optee/Kconfig @@ -4,6 +4,7 @@ config OPTEE tristate "OP-TEE" depends on HAVE_ARM_SMCCC depends on MMU + depends on RPMB || !RPMB help This implements the OP-TEE Trusted Execution Environment (TEE) driver. From 47271a9356192bf911a9f32de9236425063ed6d7 Mon Sep 17 00:00:00 2001 From: Dimitri Fedrau Date: Tue, 3 Sep 2024 08:35:26 +0200 Subject: [PATCH 345/573] power: supply: max1720x: add read support for nvmem ModelGauge m5 and device configuration values are stored in nonvolatile memory to prevent data loss if the IC loses power. Add read support for the nonvolatile memory on MAX1720X devices. Signed-off-by: Dimitri Fedrau Link: https://lore.kernel.org/r/20240903063526.222890-1-dima.fedrau@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/max1720x_battery.c | 210 ++++++++++++++++++++++-- 1 file changed, 197 insertions(+), 13 deletions(-) diff --git a/drivers/power/supply/max1720x_battery.c b/drivers/power/supply/max1720x_battery.c index edc262f0a62f..3e84e70340e4 100644 --- a/drivers/power/supply/max1720x_battery.c +++ b/drivers/power/supply/max1720x_battery.c @@ -10,13 +10,16 @@ #include #include #include +#include #include #include #include /* Nonvolatile registers */ +#define MAX1720X_NXTABLE0 0x80 #define MAX1720X_NRSENSE 0xCF /* RSense in 10^-5 Ohm */ +#define MAX1720X_NDEVICE_NAME4 0xDF /* ModelGauge m5 */ #define MAX172XX_STATUS 0x00 /* Status */ @@ -46,6 +49,8 @@ static const char *const max17205_model = "MAX17205"; struct max1720x_device_info { struct regmap *regmap; + struct regmap *regmap_nv; + struct i2c_client *ancillary; int rsense; }; @@ -106,6 +111,134 @@ static const struct regmap_config max1720x_regmap_cfg = { .cache_type = REGCACHE_RBTREE, }; +static const struct regmap_range max1720x_nvmem_allow[] = { + regmap_reg_range(MAX1720X_NXTABLE0, MAX1720X_NDEVICE_NAME4), +}; + +static const struct regmap_range max1720x_nvmem_deny[] = { + regmap_reg_range(0x00, 0x7F), + regmap_reg_range(0xE0, 0xFF), +}; + +static const struct regmap_access_table max1720x_nvmem_regs = { + .yes_ranges = max1720x_nvmem_allow, + .n_yes_ranges = ARRAY_SIZE(max1720x_nvmem_allow), + .no_ranges = max1720x_nvmem_deny, + .n_no_ranges = ARRAY_SIZE(max1720x_nvmem_deny), +}; + +static const struct regmap_config max1720x_nvmem_regmap_cfg = { + .reg_bits = 8, + .val_bits = 16, + .max_register = MAX1720X_NDEVICE_NAME4, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .rd_table = &max1720x_nvmem_regs, +}; + +static const struct nvmem_cell_info max1720x_nvmem_cells[] = { + { .name = "nXTable0", .offset = 0, .bytes = 2, }, + { .name = "nXTable1", .offset = 2, .bytes = 2, }, + { .name = "nXTable2", .offset = 4, .bytes = 2, }, + { .name = "nXTable3", .offset = 6, .bytes = 2, }, + { .name = "nXTable4", .offset = 8, .bytes = 2, }, + { .name = "nXTable5", .offset = 10, .bytes = 2, }, + { .name = "nXTable6", .offset = 12, .bytes = 2, }, + { .name = "nXTable7", .offset = 14, .bytes = 2, }, + { .name = "nXTable8", .offset = 16, .bytes = 2, }, + { .name = "nXTable9", .offset = 18, .bytes = 2, }, + { .name = "nXTable10", .offset = 20, .bytes = 2, }, + { .name = "nXTable11", .offset = 22, .bytes = 2, }, + { .name = "nUser18C", .offset = 24, .bytes = 2, }, + { .name = "nUser18D", .offset = 26, .bytes = 2, }, + { .name = "nODSCTh", .offset = 28, .bytes = 2, }, + { .name = "nODSCCfg", .offset = 30, .bytes = 2, }, + + { .name = "nOCVTable0", .offset = 32, .bytes = 2, }, + { .name = "nOCVTable1", .offset = 34, .bytes = 2, }, + { .name = "nOCVTable2", .offset = 36, .bytes = 2, }, + { .name = "nOCVTable3", .offset = 38, .bytes = 2, }, + { .name = "nOCVTable4", .offset = 40, .bytes = 2, }, + { .name = "nOCVTable5", .offset = 42, .bytes = 2, }, + { .name = "nOCVTable6", .offset = 44, .bytes = 2, }, + { .name = "nOCVTable7", .offset = 46, .bytes = 2, }, + { .name = "nOCVTable8", .offset = 48, .bytes = 2, }, + { .name = "nOCVTable9", .offset = 50, .bytes = 2, }, + { .name = "nOCVTable10", .offset = 52, .bytes = 2, }, + { .name = "nOCVTable11", .offset = 54, .bytes = 2, }, + { .name = "nIChgTerm", .offset = 56, .bytes = 2, }, + { .name = "nFilterCfg", .offset = 58, .bytes = 2, }, + { .name = "nVEmpty", .offset = 60, .bytes = 2, }, + { .name = "nLearnCfg", .offset = 62, .bytes = 2, }, + + { .name = "nQRTable00", .offset = 64, .bytes = 2, }, + { .name = "nQRTable10", .offset = 66, .bytes = 2, }, + { .name = "nQRTable20", .offset = 68, .bytes = 2, }, + { .name = "nQRTable30", .offset = 70, .bytes = 2, }, + { .name = "nCycles", .offset = 72, .bytes = 2, }, + { .name = "nFullCapNom", .offset = 74, .bytes = 2, }, + { .name = "nRComp0", .offset = 76, .bytes = 2, }, + { .name = "nTempCo", .offset = 78, .bytes = 2, }, + { .name = "nIAvgEmpty", .offset = 80, .bytes = 2, }, + { .name = "nFullCapRep", .offset = 82, .bytes = 2, }, + { .name = "nVoltTemp", .offset = 84, .bytes = 2, }, + { .name = "nMaxMinCurr", .offset = 86, .bytes = 2, }, + { .name = "nMaxMinVolt", .offset = 88, .bytes = 2, }, + { .name = "nMaxMinTemp", .offset = 90, .bytes = 2, }, + { .name = "nSOC", .offset = 92, .bytes = 2, }, + { .name = "nTimerH", .offset = 94, .bytes = 2, }, + + { .name = "nConfig", .offset = 96, .bytes = 2, }, + { .name = "nRippleCfg", .offset = 98, .bytes = 2, }, + { .name = "nMiscCfg", .offset = 100, .bytes = 2, }, + { .name = "nDesignCap", .offset = 102, .bytes = 2, }, + { .name = "nHibCfg", .offset = 104, .bytes = 2, }, + { .name = "nPackCfg", .offset = 106, .bytes = 2, }, + { .name = "nRelaxCfg", .offset = 108, .bytes = 2, }, + { .name = "nConvgCfg", .offset = 110, .bytes = 2, }, + { .name = "nNVCfg0", .offset = 112, .bytes = 2, }, + { .name = "nNVCfg1", .offset = 114, .bytes = 2, }, + { .name = "nNVCfg2", .offset = 116, .bytes = 2, }, + { .name = "nSBSCfg", .offset = 118, .bytes = 2, }, + { .name = "nROMID0", .offset = 120, .bytes = 2, }, + { .name = "nROMID1", .offset = 122, .bytes = 2, }, + { .name = "nROMID2", .offset = 124, .bytes = 2, }, + { .name = "nROMID3", .offset = 126, .bytes = 2, }, + + { .name = "nVAlrtTh", .offset = 128, .bytes = 2, }, + { .name = "nTAlrtTh", .offset = 130, .bytes = 2, }, + { .name = "nSAlrtTh", .offset = 132, .bytes = 2, }, + { .name = "nIAlrtTh", .offset = 134, .bytes = 2, }, + { .name = "nUser1C4", .offset = 136, .bytes = 2, }, + { .name = "nUser1C5", .offset = 138, .bytes = 2, }, + { .name = "nFullSOCThr", .offset = 140, .bytes = 2, }, + { .name = "nTTFCfg", .offset = 142, .bytes = 2, }, + { .name = "nCGain", .offset = 144, .bytes = 2, }, + { .name = "nTCurve", .offset = 146, .bytes = 2, }, + { .name = "nTGain", .offset = 148, .bytes = 2, }, + { .name = "nTOff", .offset = 150, .bytes = 2, }, + { .name = "nManfctrName0", .offset = 152, .bytes = 2, }, + { .name = "nManfctrName1", .offset = 154, .bytes = 2, }, + { .name = "nManfctrName2", .offset = 156, .bytes = 2, }, + { .name = "nRSense", .offset = 158, .bytes = 2, }, + + { .name = "nUser1D0", .offset = 160, .bytes = 2, }, + { .name = "nUser1D1", .offset = 162, .bytes = 2, }, + { .name = "nAgeFcCfg", .offset = 164, .bytes = 2, }, + { .name = "nDesignVoltage", .offset = 166, .bytes = 2, }, + { .name = "nUser1D4", .offset = 168, .bytes = 2, }, + { .name = "nRFastVShdn", .offset = 170, .bytes = 2, }, + { .name = "nManfctrDate", .offset = 172, .bytes = 2, }, + { .name = "nFirstUsed", .offset = 174, .bytes = 2, }, + { .name = "nSerialNumber0", .offset = 176, .bytes = 2, }, + { .name = "nSerialNumber1", .offset = 178, .bytes = 2, }, + { .name = "nSerialNumber2", .offset = 180, .bytes = 2, }, + { .name = "nDeviceName0", .offset = 182, .bytes = 2, }, + { .name = "nDeviceName1", .offset = 184, .bytes = 2, }, + { .name = "nDeviceName2", .offset = 186, .bytes = 2, }, + { .name = "nDeviceName3", .offset = 188, .bytes = 2, }, + { .name = "nDeviceName4", .offset = 190, .bytes = 2, }, +}; + static const enum power_supply_property max1720x_battery_props[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_CAPACITY, @@ -249,30 +382,81 @@ static int max1720x_battery_get_property(struct power_supply *psy, return ret; } -static int max1720x_probe_sense_resistor(struct i2c_client *client, - struct max1720x_device_info *info) +static +int max1720x_nvmem_reg_read(void *priv, unsigned int off, void *val, size_t len) +{ + struct max1720x_device_info *info = priv; + unsigned int reg = MAX1720X_NXTABLE0 + (off / 2); + + return regmap_bulk_read(info->regmap_nv, reg, val, len / 2); +} + +static void max1720x_unregister_ancillary(void *data) +{ + struct max1720x_device_info *info = data; + + i2c_unregister_device(info->ancillary); +} + +static int max1720x_probe_nvmem(struct i2c_client *client, + struct max1720x_device_info *info) { struct device *dev = &client->dev; - struct i2c_client *ancillary; + struct nvmem_config nvmem_config = { + .dev = dev, + .name = "max1720x_nvmem", + .cells = max1720x_nvmem_cells, + .ncells = ARRAY_SIZE(max1720x_nvmem_cells), + .read_only = true, + .root_only = true, + .reg_read = max1720x_nvmem_reg_read, + .size = ARRAY_SIZE(max1720x_nvmem_cells) * 2, + .word_size = 2, + .stride = 2, + .priv = info, + }; + struct nvmem_device *nvmem; + unsigned int val; int ret; - ancillary = i2c_new_ancillary_device(client, "nvmem", 0xb); - if (IS_ERR(ancillary)) { + info->ancillary = i2c_new_ancillary_device(client, "nvmem", 0xb); + if (IS_ERR(info->ancillary)) { dev_err(dev, "Failed to initialize ancillary i2c device\n"); - return PTR_ERR(ancillary); + return PTR_ERR(info->ancillary); } - ret = i2c_smbus_read_word_data(ancillary, MAX1720X_NRSENSE); - i2c_unregister_device(ancillary); - if (ret < 0) + ret = devm_add_action_or_reset(dev, max1720x_unregister_ancillary, info); + if (ret) { + i2c_unregister_device(info->ancillary); + dev_err(dev, "Failed to add unregister callback\n"); return ret; + } - info->rsense = ret; + info->regmap_nv = devm_regmap_init_i2c(info->ancillary, + &max1720x_nvmem_regmap_cfg); + if (IS_ERR(info->regmap_nv)) { + dev_err(dev, "regmap initialization of nvmem failed\n"); + return PTR_ERR(info->regmap_nv); + } + + ret = regmap_read(info->regmap_nv, MAX1720X_NRSENSE, &val); + if (ret < 0) { + dev_err(dev, "Failed to read sense resistor value\n"); + return ret; + } + + info->rsense = val; if (!info->rsense) { dev_warn(dev, "RSense not calibrated, set 10 mOhms!\n"); info->rsense = 1000; /* in regs in 10^-5 */ } + nvmem = devm_nvmem_register(dev, &nvmem_config); + if (IS_ERR(nvmem)) { + dev_err(dev, "Could not register nvmem!"); + return PTR_ERR(nvmem); + } + return 0; } @@ -299,15 +483,15 @@ static int max1720x_probe(struct i2c_client *client) psy_cfg.drv_data = info; psy_cfg.fwnode = dev_fwnode(dev); + i2c_set_clientdata(client, info); info->regmap = devm_regmap_init_i2c(client, &max1720x_regmap_cfg); if (IS_ERR(info->regmap)) return dev_err_probe(dev, PTR_ERR(info->regmap), "regmap initialization failed\n"); - ret = max1720x_probe_sense_resistor(client, info); + ret = max1720x_probe_nvmem(client, info); if (ret) - return dev_err_probe(dev, ret, - "Failed to read sense resistor value\n"); + return dev_err_probe(dev, ret, "Failed to probe nvmem\n"); bat = devm_power_supply_register(dev, &max1720x_bat_desc, &psy_cfg); if (IS_ERR(bat)) From 0d9af1e1c93b6a89f3fb6dcbafa5bc78892cb94f Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 31 Aug 2024 16:20:34 +0200 Subject: [PATCH 346/573] power: supply: "usb_type" property may be written to According to Documentation/ABI/testing/sysfs-class-power the "usb_type" property is Read-Only. For power-supplies which consume USB power such as battery charger chips, this is correct. But the UCS1002 USB Port Power Controller driver which is a driver for a chip which is a power-source for USB-A charging ports "usb_type" is actually writable to configure the type of USB charger emulated by the USB-A port. Adjust the docs and the power_supply_sysfs.c code to adjust for this new writeable use of "usb_type": 1. Update Documentation/ABI/testing/sysfs-class-power to document that "usb_type" may be writable 2. Change the power_supply_attr type in power_supply_sysfs.c from POWER_SUPPLY_ATTR() into POWER_SUPPLY_ENUM_ATTR() so that the various usb_type string values from POWER_SUPPLY_TYPE_TEXT[] such as e.g. "SDP" and "USB_PD" can be written to the "usb_type" attribute instead of only accepting integer values. Cc: Enric Balletbo Serra Cc: Andrey Smirnov Signed-off-by: Hans de Goede Reviewed-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20240831142039.28830-2-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- Documentation/ABI/testing/sysfs-class-power | 7 ++++++- drivers/power/supply/power_supply_sysfs.c | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 7c81f0a25a48..2230a207c187 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -592,7 +592,12 @@ Description: the supply, for example it can show if USB-PD capable source is attached. - Access: Read-Only + Access: For power-supplies which consume USB power such + as battery charger chips, this indicates the type of + the connected USB power source and is Read-Only. + + For power-supplies which act as a USB power-source such as + e.g. the UCS1002 USB Port Power Controller this is writable. Valid values: "Unknown", "SDP", "DCP", "CDP", "ACA", "C", "PD", diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index 3e63d165b2f7..ff7e423edd57 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -209,7 +209,7 @@ static struct power_supply_attr power_supply_attrs[] = { POWER_SUPPLY_ATTR(TIME_TO_FULL_NOW), POWER_SUPPLY_ATTR(TIME_TO_FULL_AVG), POWER_SUPPLY_ENUM_ATTR(TYPE), - POWER_SUPPLY_ATTR(USB_TYPE), + POWER_SUPPLY_ENUM_ATTR(USB_TYPE), POWER_SUPPLY_ENUM_ATTR(SCOPE), POWER_SUPPLY_ATTR(PRECHARGE_CURRENT), POWER_SUPPLY_ATTR(CHARGE_TERM_CURRENT), From 83a4c42df75e8f6ff671fa9fbe7d4c79b98626de Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 31 Aug 2024 16:20:35 +0200 Subject: [PATCH 347/573] power: supply: ucs1002: Adjust ucs1002_set_usb_type() to accept string values power_supply_sysfs.c accept wrrites of strings to "usb_type" for strings values matching an entry in POWER_SUPPLY_USB_TYPE_TEXT[]. If such a string value is written then the int value passed to ucs1002_set_property() will be an enum power_supply_usb_type value. Before this change ucs1002_set_usb_type() expected the value to be an index into ucs1002_usb_types[]. Adjust ucs1002_set_usb_type() to use the enum value directly so that writing string values works. The list of supported types in ucs1002_usb_types[] is: PD, SDP, DCP, CDP. The [POWER_SUPPLY_USB_TYPE_]SDP, DCP and CDP enum labels have a value of 1, 2 and 3. So userspace selecting SDP, DCP or CDP by writing 1, 2 or 3 will keep working. POWER_SUPPLY_USB_TYPE_PD which is mapped to the ucs1002 dedicated mode however has a value of 6. Before this change writing 0 would select the dedicated mode. To preserve userspace API compatibility also map POWER_SUPPLY_USB_TYPE_UNKNOWN (which is 0) to the dedicated mode. Cc: Enric Balletbo Serra Cc: Andrey Smirnov Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240831142039.28830-3-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/ucs1002_power.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c index 7970843a4f48..b67d5b03d93e 100644 --- a/drivers/power/supply/ucs1002_power.c +++ b/drivers/power/supply/ucs1002_power.c @@ -308,10 +308,13 @@ static int ucs1002_set_usb_type(struct ucs1002_info *info, int val) { unsigned int mode; - if (val < 0 || val >= ARRAY_SIZE(ucs1002_usb_types)) - return -EINVAL; - - switch (ucs1002_usb_types[val]) { + switch (val) { + /* + * POWER_SUPPLY_USB_TYPE_UNKNOWN == 0, map this to dedicated for + * userspace API compatibility with older versions of this driver + * which mapped 0 to dedicated. + */ + case POWER_SUPPLY_USB_TYPE_UNKNOWN: case POWER_SUPPLY_USB_TYPE_PD: mode = V_SET_ACTIVE_MODE_DEDICATED; break; From 03ec41c1670aedfbd126f541c4acbb8e69d4cd0c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 31 Aug 2024 16:20:36 +0200 Subject: [PATCH 348/573] power: supply: rt9467-charger: Remove "usb_type" property write support The "usb_type" property must be read-only for charger power-supply devices, see: Documentation/ABI/testing/sysfs-class-power. But the rt9467 driver allows writing 0/1 to it to disable/enable charging. Other charger drivers use the "status" property for this and the rt9467 code also allows writing 0/1 to its "status" property and this does the exact same thing as writing 0/1 to its "usb_type" property. Drop write support for the "usb_type" property making it readonly to match the ABI documentation. If userspace wants to disable/enable charging it can use the "status" property for this. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240831142039.28830-4-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/rt9467-charger.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/power/supply/rt9467-charger.c b/drivers/power/supply/rt9467-charger.c index fdfdc83ab045..f935bd761ac1 100644 --- a/drivers/power/supply/rt9467-charger.c +++ b/drivers/power/supply/rt9467-charger.c @@ -745,8 +745,6 @@ static int rt9467_psy_set_property(struct power_supply *psy, RT9467_RANGE_IPREC, val->intval); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return rt9467_psy_set_ieoc(data, val->intval); - case POWER_SUPPLY_PROP_USB_TYPE: - return regmap_field_write(data->rm_field[F_USBCHGEN], val->intval); default: return -EINVAL; } @@ -764,7 +762,6 @@ static int rt9467_chg_prop_is_writeable(struct power_supply *psy, case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: - case POWER_SUPPLY_PROP_USB_TYPE: return 1; default: return 0; From a6456d43e9abebb5d7866e5edae3024188273306 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 31 Aug 2024 16:20:37 +0200 Subject: [PATCH 349/573] power: supply: sysfs: Add power_supply_show_enum_with_available() helper Turn power_supply_charge_behaviour_show() into a generic function for showing enum values with their available (for writing) values shown and the current value shown surrounded by sqaure-brackets like the show() output for "usb_type" and "charge_behaviour". This is a preparation patch for refactoring the "usb_type" property handling to use a bitmask indicating available usb-types + this new generic function. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240831142039.28830-5-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_sysfs.c | 32 ++++++++++++++--------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index ff7e423edd57..9f21b0b54caf 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -518,31 +518,28 @@ out: return ret; } -ssize_t power_supply_charge_behaviour_show(struct device *dev, - unsigned int available_behaviours, - enum power_supply_charge_behaviour current_behaviour, - char *buf) +static ssize_t power_supply_show_enum_with_available( + struct device *dev, const char * const labels[], int label_count, + unsigned int available_values, int value, char *buf) { bool match = false, available, active; ssize_t count = 0; int i; - for (i = 0; i < ARRAY_SIZE(POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT); i++) { - available = available_behaviours & BIT(i); - active = i == current_behaviour; + for (i = 0; i < label_count; i++) { + available = available_values & BIT(i); + active = i == value; if (available && active) { - count += sysfs_emit_at(buf, count, "[%s] ", - POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[i]); + count += sysfs_emit_at(buf, count, "[%s] ", labels[i]); match = true; } else if (available) { - count += sysfs_emit_at(buf, count, "%s ", - POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[i]); + count += sysfs_emit_at(buf, count, "%s ", labels[i]); } } if (!match) { - dev_warn(dev, "driver reporting unsupported charge behaviour\n"); + dev_warn(dev, "driver reporting unavailable enum value %d\n", value); return -EINVAL; } @@ -551,6 +548,17 @@ ssize_t power_supply_charge_behaviour_show(struct device *dev, return count; } + +ssize_t power_supply_charge_behaviour_show(struct device *dev, + unsigned int available_behaviours, + enum power_supply_charge_behaviour current_behaviour, + char *buf) +{ + return power_supply_show_enum_with_available( + dev, POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT, + ARRAY_SIZE(POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT), + available_behaviours, current_behaviour, buf); +} EXPORT_SYMBOL_GPL(power_supply_charge_behaviour_show); int power_supply_charge_behaviour_parse(unsigned int available_behaviours, const char *buf) From 322900ac7d82be0475466f81946b6484cd1936bd Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 31 Aug 2024 16:20:38 +0200 Subject: [PATCH 350/573] power: supply: sysfs: Move power_supply_show_enum_with_available() up Move power_supply_show_enum_with_available() higher up in the power_supply_sysfs.c file. This is a preparation patch to avoid needing a forward declaration when replacing power_supply_show_usb_type() with it later on. This commit only moves the function, there are no changes to it. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240831142039.28830-6-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_sysfs.c | 62 +++++++++++------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index 9f21b0b54caf..c98a6de59d3b 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -237,6 +237,37 @@ static enum power_supply_property dev_attr_psp(struct device_attribute *attr) return to_ps_attr(attr) - power_supply_attrs; } +static ssize_t power_supply_show_enum_with_available( + struct device *dev, const char * const labels[], int label_count, + unsigned int available_values, int value, char *buf) +{ + bool match = false, available, active; + ssize_t count = 0; + int i; + + for (i = 0; i < label_count; i++) { + available = available_values & BIT(i); + active = i == value; + + if (available && active) { + count += sysfs_emit_at(buf, count, "[%s] ", labels[i]); + match = true; + } else if (available) { + count += sysfs_emit_at(buf, count, "%s ", labels[i]); + } + } + + if (!match) { + dev_warn(dev, "driver reporting unavailable enum value %d\n", value); + return -EINVAL; + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} + static ssize_t power_supply_show_usb_type(struct device *dev, const struct power_supply_desc *desc, union power_supply_propval *value, @@ -518,37 +549,6 @@ out: return ret; } -static ssize_t power_supply_show_enum_with_available( - struct device *dev, const char * const labels[], int label_count, - unsigned int available_values, int value, char *buf) -{ - bool match = false, available, active; - ssize_t count = 0; - int i; - - for (i = 0; i < label_count; i++) { - available = available_values & BIT(i); - active = i == value; - - if (available && active) { - count += sysfs_emit_at(buf, count, "[%s] ", labels[i]); - match = true; - } else if (available) { - count += sysfs_emit_at(buf, count, "%s ", labels[i]); - } - } - - if (!match) { - dev_warn(dev, "driver reporting unavailable enum value %d\n", value); - return -EINVAL; - } - - if (count) - buf[count - 1] = '\n'; - - return count; -} - ssize_t power_supply_charge_behaviour_show(struct device *dev, unsigned int available_behaviours, enum power_supply_charge_behaviour current_behaviour, From 364ea7ccaef917a3068236a19a4b31a0623b561a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 31 Aug 2024 16:20:39 +0200 Subject: [PATCH 351/573] power: supply: Change usb_types from an array into a bitmask The bit_types array just hold a list of valid enum power_supply_usb_type values which map to 0 - 9. This can easily be represented as a bitmap. This reduces the size of struct power_supply_desc and further reduces the data section size by drivers no longer needing to store the array. This also unifies how usb_types are handled with charge_behaviours, which allows power_supply_show_usb_type() to be removed. Signed-off-by: Hans de Goede Reviewed-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20240831142039.28830-7-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/extcon/extcon-intel-cht-wc.c | 15 +++---- drivers/phy/ti/phy-tusb1210.c | 11 ++--- drivers/power/supply/axp20x_usb_power.c | 13 ++---- drivers/power/supply/bq256xx_charger.c | 15 +++---- drivers/power/supply/cros_usbpd-charger.c | 22 ++++------ .../power/supply/lenovo_yoga_c630_battery.c | 7 +--- drivers/power/supply/mp2629_charger.c | 15 +++---- drivers/power/supply/mt6360_charger.c | 13 ++---- drivers/power/supply/mt6370-charger.c | 13 ++---- drivers/power/supply/power_supply_core.c | 4 -- drivers/power/supply/power_supply_sysfs.c | 40 ++----------------- drivers/power/supply/qcom_battmgr.c | 37 +++++++++-------- drivers/power/supply/qcom_pmi8998_charger.c | 13 ++---- drivers/power/supply/rk817_charger.c | 9 +---- drivers/power/supply/rn5t618_power.c | 13 ++---- drivers/power/supply/rt9467-charger.c | 13 ++---- drivers/power/supply/rt9471.c | 15 +++---- drivers/power/supply/ucs1002_power.c | 15 +++---- drivers/usb/typec/anx7411.c | 11 ++--- drivers/usb/typec/rt1719.c | 11 ++--- drivers/usb/typec/tcpm/tcpm.c | 11 ++--- drivers/usb/typec/tipd/core.c | 9 +---- drivers/usb/typec/ucsi/psy.c | 11 ++--- include/linux/power_supply.h | 3 +- 24 files changed, 102 insertions(+), 237 deletions(-) diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c index 733c470c3102..93552dc3c895 100644 --- a/drivers/extcon/extcon-intel-cht-wc.c +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -461,14 +461,6 @@ static int cht_wc_extcon_psy_get_prop(struct power_supply *psy, return 0; } -static const enum power_supply_usb_type cht_wc_extcon_psy_usb_types[] = { - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_ACA, - POWER_SUPPLY_USB_TYPE_UNKNOWN, -}; - static const enum power_supply_property cht_wc_extcon_psy_props[] = { POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_ONLINE, @@ -477,8 +469,11 @@ static const enum power_supply_property cht_wc_extcon_psy_props[] = { static const struct power_supply_desc cht_wc_extcon_psy_desc = { .name = "cht_wcove_pwrsrc", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = cht_wc_extcon_psy_usb_types, - .num_usb_types = ARRAY_SIZE(cht_wc_extcon_psy_usb_types), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_ACA) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .properties = cht_wc_extcon_psy_props, .num_properties = ARRAY_SIZE(cht_wc_extcon_psy_props), .get_property = cht_wc_extcon_psy_get_prop, diff --git a/drivers/phy/ti/phy-tusb1210.c b/drivers/phy/ti/phy-tusb1210.c index 751fecd466e3..c3ae9d7948d7 100644 --- a/drivers/phy/ti/phy-tusb1210.c +++ b/drivers/phy/ti/phy-tusb1210.c @@ -411,12 +411,6 @@ static int tusb1210_psy_get_prop(struct power_supply *psy, return 0; } -static const enum power_supply_usb_type tusb1210_psy_usb_types[] = { - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_UNKNOWN, -}; - static const enum power_supply_property tusb1210_psy_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_USB_TYPE, @@ -426,8 +420,9 @@ static const enum power_supply_property tusb1210_psy_props[] = { static const struct power_supply_desc tusb1210_psy_desc = { .name = "tusb1211-charger-detect", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = tusb1210_psy_usb_types, - .num_usb_types = ARRAY_SIZE(tusb1210_psy_usb_types), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .properties = tusb1210_psy_props, .num_properties = ARRAY_SIZE(tusb1210_psy_props), .get_property = tusb1210_psy_get_prop, diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index dae7e5cfc54e..38ea2388df21 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -412,13 +412,6 @@ static enum power_supply_property axp813_usb_power_properties[] = { POWER_SUPPLY_PROP_USB_TYPE, }; -static enum power_supply_usb_type axp813_usb_types[] = { - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_UNKNOWN, -}; - static const struct power_supply_desc axp20x_usb_power_desc = { .name = "axp20x-usb", .type = POWER_SUPPLY_TYPE_USB, @@ -447,8 +440,10 @@ static const struct power_supply_desc axp813_usb_power_desc = { .property_is_writeable = axp20x_usb_power_prop_writeable, .get_property = axp20x_usb_power_get_property, .set_property = axp20x_usb_power_set_property, - .usb_types = axp813_usb_types, - .num_usb_types = ARRAY_SIZE(axp813_usb_types), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), }; static const char * const axp20x_irq_names[] = { diff --git a/drivers/power/supply/bq256xx_charger.c b/drivers/power/supply/bq256xx_charger.c index 1a935bc88510..5514d1896bb8 100644 --- a/drivers/power/supply/bq256xx_charger.c +++ b/drivers/power/supply/bq256xx_charger.c @@ -334,14 +334,6 @@ static const int bq25618_619_ichg_values[] = { 1290000, 1360000, 1430000, 1500000 }; -static enum power_supply_usb_type bq256xx_usb_type[] = { - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_UNKNOWN, - POWER_SUPPLY_USB_TYPE_ACA, -}; - static int bq256xx_array_parse(int array_size, int val, const int array[]) { int i = 0; @@ -1252,8 +1244,11 @@ static int bq256xx_property_is_writeable(struct power_supply *psy, static const struct power_supply_desc bq256xx_power_supply_desc = { .name = "bq256xx-charger", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = bq256xx_usb_type, - .num_usb_types = ARRAY_SIZE(bq256xx_usb_type), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_ACA) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .properties = bq256xx_power_supply_props, .num_properties = ARRAY_SIZE(bq256xx_power_supply_props), .get_property = bq256xx_get_charger_property, diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c index 8008e31c0c09..bed3e2e9bfea 100644 --- a/drivers/power/supply/cros_usbpd-charger.c +++ b/drivers/power/supply/cros_usbpd-charger.c @@ -73,17 +73,6 @@ static enum power_supply_property cros_usbpd_dedicated_charger_props[] = { POWER_SUPPLY_PROP_VOLTAGE_NOW, }; -static enum power_supply_usb_type cros_usbpd_charger_usb_types[] = { - POWER_SUPPLY_USB_TYPE_UNKNOWN, - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_C, - POWER_SUPPLY_USB_TYPE_PD, - POWER_SUPPLY_USB_TYPE_PD_DRP, - POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID -}; - /* Input voltage/current limit in mV/mA. Default to none. */ static u16 input_voltage_limit = EC_POWER_LIMIT_NONE; static u16 input_current_limit = EC_POWER_LIMIT_NONE; @@ -643,9 +632,14 @@ static int cros_usbpd_charger_probe(struct platform_device *pd) psy_desc->properties = cros_usbpd_charger_props; psy_desc->num_properties = ARRAY_SIZE(cros_usbpd_charger_props); - psy_desc->usb_types = cros_usbpd_charger_usb_types; - psy_desc->num_usb_types = - ARRAY_SIZE(cros_usbpd_charger_usb_types); + psy_desc->usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | + BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_PD_DRP) | + BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID); } psy_desc->name = port->name; diff --git a/drivers/power/supply/lenovo_yoga_c630_battery.c b/drivers/power/supply/lenovo_yoga_c630_battery.c index d4d422cc5353..f98f65e00831 100644 --- a/drivers/power/supply/lenovo_yoga_c630_battery.c +++ b/drivers/power/supply/lenovo_yoga_c630_battery.c @@ -353,15 +353,10 @@ static enum power_supply_property yoga_c630_psy_adpt_properties[] = { POWER_SUPPLY_PROP_USB_TYPE, }; -static const enum power_supply_usb_type yoga_c630_psy_adpt_usb_type[] = { - POWER_SUPPLY_USB_TYPE_C, -}; - static const struct power_supply_desc yoga_c630_psy_adpt_psy_desc = { .name = "yoga-c630-adapter", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = yoga_c630_psy_adpt_usb_type, - .num_usb_types = ARRAY_SIZE(yoga_c630_psy_adpt_usb_type), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_C), .properties = yoga_c630_psy_adpt_properties, .num_properties = ARRAY_SIZE(yoga_c630_psy_adpt_properties), .get_property = yoga_c630_psy_adpt_get_property, diff --git a/drivers/power/supply/mp2629_charger.c b/drivers/power/supply/mp2629_charger.c index 3a2a28fbba73..d281c1059629 100644 --- a/drivers/power/supply/mp2629_charger.c +++ b/drivers/power/supply/mp2629_charger.c @@ -94,14 +94,6 @@ struct mp2629_prop { int shift; }; -static enum power_supply_usb_type mp2629_usb_types[] = { - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_PD_DRP, - POWER_SUPPLY_USB_TYPE_UNKNOWN -}; - static enum power_supply_property mp2629_charger_usb_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_USB_TYPE, @@ -487,8 +479,11 @@ unlock: static const struct power_supply_desc mp2629_usb_desc = { .name = "mp2629_usb", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = mp2629_usb_types, - .num_usb_types = ARRAY_SIZE(mp2629_usb_types), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_PD_DRP) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .properties = mp2629_charger_usb_props, .num_properties = ARRAY_SIZE(mp2629_charger_usb_props), .get_property = mp2629_charger_usb_get_prop, diff --git a/drivers/power/supply/mt6360_charger.c b/drivers/power/supply/mt6360_charger.c index aca123783efc..e99e55148976 100644 --- a/drivers/power/supply/mt6360_charger.c +++ b/drivers/power/supply/mt6360_charger.c @@ -154,13 +154,6 @@ enum mt6360_pmu_chg_type { MT6360_CHG_TYPE_MAX, }; -static enum power_supply_usb_type mt6360_charger_usb_types[] = { - POWER_SUPPLY_USB_TYPE_UNKNOWN, - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, -}; - static int mt6360_get_chrdet_ext_stat(struct mt6360_chg_info *mci, bool *pwr_rdy) { @@ -574,8 +567,10 @@ static const struct power_supply_desc mt6360_charger_desc = { .get_property = mt6360_charger_get_property, .set_property = mt6360_charger_set_property, .property_is_writeable = mt6360_charger_property_is_writeable, - .usb_types = mt6360_charger_usb_types, - .num_usb_types = ARRAY_SIZE(mt6360_charger_usb_types), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), }; static const struct regulator_ops mt6360_chg_otg_ops = { diff --git a/drivers/power/supply/mt6370-charger.c b/drivers/power/supply/mt6370-charger.c index e24fce087d80..ad8793bf997e 100644 --- a/drivers/power/supply/mt6370-charger.c +++ b/drivers/power/supply/mt6370-charger.c @@ -624,13 +624,6 @@ static enum power_supply_property mt6370_chg_properties[] = { POWER_SUPPLY_PROP_USB_TYPE, }; -static enum power_supply_usb_type mt6370_chg_usb_types[] = { - POWER_SUPPLY_USB_TYPE_UNKNOWN, - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_DCP, -}; - static const struct power_supply_desc mt6370_chg_psy_desc = { .name = "mt6370-charger", .type = POWER_SUPPLY_TYPE_USB, @@ -639,8 +632,10 @@ static const struct power_supply_desc mt6370_chg_psy_desc = { .get_property = mt6370_chg_get_property, .set_property = mt6370_chg_set_property, .property_is_writeable = mt6370_chg_property_is_writeable, - .usb_types = mt6370_chg_usb_types, - .num_usb_types = ARRAY_SIZE(mt6370_chg_usb_types), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), }; static const struct regulator_ops mt6370_chg_otg_ops = { diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 8f6025acd10a..4fa3d3414b8c 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1361,10 +1361,6 @@ __power_supply_register(struct device *parent, pr_warn("%s: Expected proper parent device for '%s'\n", __func__, desc->name); - if (psy_has_property(desc, POWER_SUPPLY_PROP_USB_TYPE) && - (!desc->usb_types || !desc->num_usb_types)) - return ERR_PTR(-EINVAL); - psy = kzalloc(sizeof(*psy), GFP_KERNEL); if (!psy) return ERR_PTR(-ENOMEM); diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index c98a6de59d3b..16b3c5880cd8 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -268,40 +268,6 @@ static ssize_t power_supply_show_enum_with_available( return count; } -static ssize_t power_supply_show_usb_type(struct device *dev, - const struct power_supply_desc *desc, - union power_supply_propval *value, - char *buf) -{ - enum power_supply_usb_type usb_type; - ssize_t count = 0; - bool match = false; - int i; - - for (i = 0; i < desc->num_usb_types; ++i) { - usb_type = desc->usb_types[i]; - - if (value->intval == usb_type) { - count += sysfs_emit_at(buf, count, "[%s] ", - POWER_SUPPLY_USB_TYPE_TEXT[usb_type]); - match = true; - } else { - count += sysfs_emit_at(buf, count, "%s ", - POWER_SUPPLY_USB_TYPE_TEXT[usb_type]); - } - } - - if (!match) { - dev_warn(dev, "driver reporting unsupported connected type\n"); - return -EINVAL; - } - - if (count) - buf[count - 1] = '\n'; - - return count; -} - static ssize_t power_supply_show_property(struct device *dev, struct device_attribute *attr, char *buf) { @@ -331,8 +297,10 @@ static ssize_t power_supply_show_property(struct device *dev, switch (psp) { case POWER_SUPPLY_PROP_USB_TYPE: - ret = power_supply_show_usb_type(dev, psy->desc, - &value, buf); + ret = power_supply_show_enum_with_available( + dev, POWER_SUPPLY_USB_TYPE_TEXT, + ARRAY_SIZE(POWER_SUPPLY_USB_TYPE_TEXT), + psy->desc->usb_types, value.intval, buf); break; case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: ret = power_supply_charge_behaviour_show(dev, psy->desc->charge_behaviours, diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index 46f36dcb185c..56c8021383da 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -786,19 +786,6 @@ static int qcom_battmgr_usb_get_property(struct power_supply *psy, return 0; } -static const enum power_supply_usb_type usb_psy_supported_types[] = { - POWER_SUPPLY_USB_TYPE_UNKNOWN, - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_ACA, - POWER_SUPPLY_USB_TYPE_C, - POWER_SUPPLY_USB_TYPE_PD, - POWER_SUPPLY_USB_TYPE_PD_DRP, - POWER_SUPPLY_USB_TYPE_PD_PPS, - POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID, -}; - static const enum power_supply_property sc8280xp_usb_props[] = { POWER_SUPPLY_PROP_ONLINE, }; @@ -809,8 +796,16 @@ static const struct power_supply_desc sc8280xp_usb_psy_desc = { .properties = sc8280xp_usb_props, .num_properties = ARRAY_SIZE(sc8280xp_usb_props), .get_property = qcom_battmgr_usb_get_property, - .usb_types = usb_psy_supported_types, - .num_usb_types = ARRAY_SIZE(usb_psy_supported_types), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | + BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_ACA) | + BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_PD_DRP) | + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS) | + BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID), }; static const enum power_supply_property sm8350_usb_props[] = { @@ -829,8 +824,16 @@ static const struct power_supply_desc sm8350_usb_psy_desc = { .properties = sm8350_usb_props, .num_properties = ARRAY_SIZE(sm8350_usb_props), .get_property = qcom_battmgr_usb_get_property, - .usb_types = usb_psy_supported_types, - .num_usb_types = ARRAY_SIZE(usb_psy_supported_types), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | + BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_ACA) | + BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_PD_DRP) | + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS) | + BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID), }; static const u8 sm8350_wls_prop_map[] = { diff --git a/drivers/power/supply/qcom_pmi8998_charger.c b/drivers/power/supply/qcom_pmi8998_charger.c index 9bb777406013..81acbd8b2169 100644 --- a/drivers/power/supply/qcom_pmi8998_charger.c +++ b/drivers/power/supply/qcom_pmi8998_charger.c @@ -411,13 +411,6 @@ static enum power_supply_property smb2_properties[] = { POWER_SUPPLY_PROP_USB_TYPE, }; -static enum power_supply_usb_type smb2_usb_types[] = { - POWER_SUPPLY_USB_TYPE_UNKNOWN, - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, -}; - static int smb2_get_prop_usb_online(struct smb2_chip *chip, int *val) { unsigned int stat; @@ -775,8 +768,10 @@ static irqreturn_t smb2_handle_wdog_bark(int irq, void *data) static const struct power_supply_desc smb2_psy_desc = { .name = "pmi8998_charger", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = smb2_usb_types, - .num_usb_types = ARRAY_SIZE(smb2_usb_types), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .properties = smb2_properties, .num_properties = ARRAY_SIZE(smb2_properties), .get_property = smb2_get_property, diff --git a/drivers/power/supply/rk817_charger.c b/drivers/power/supply/rk817_charger.c index 7ca91739c6cc..a3d377a32b49 100644 --- a/drivers/power/supply/rk817_charger.c +++ b/drivers/power/supply/rk817_charger.c @@ -673,11 +673,6 @@ static enum power_supply_property rk817_chg_props[] = { POWER_SUPPLY_PROP_VOLTAGE_AVG, }; -static enum power_supply_usb_type rk817_usb_type[] = { - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_UNKNOWN, -}; - static const struct power_supply_desc rk817_bat_desc = { .name = "rk817-battery", .type = POWER_SUPPLY_TYPE_BATTERY, @@ -689,8 +684,8 @@ static const struct power_supply_desc rk817_bat_desc = { static const struct power_supply_desc rk817_chg_desc = { .name = "rk817-charger", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = rk817_usb_type, - .num_usb_types = ARRAY_SIZE(rk817_usb_type), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .properties = rk817_chg_props, .num_properties = ARRAY_SIZE(rk817_chg_props), .get_property = rk817_chg_get_prop, diff --git a/drivers/power/supply/rn5t618_power.c b/drivers/power/supply/rn5t618_power.c index ebea3522a2ac..40dec55a9f73 100644 --- a/drivers/power/supply/rn5t618_power.c +++ b/drivers/power/supply/rn5t618_power.c @@ -70,13 +70,6 @@ struct rn5t618_power_info { int irq; }; -static enum power_supply_usb_type rn5t618_usb_types[] = { - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_UNKNOWN -}; - static enum power_supply_property rn5t618_usb_props[] = { /* input current limit is not very accurate */ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, @@ -681,8 +674,10 @@ static const struct power_supply_desc rn5t618_adp_desc = { static const struct power_supply_desc rn5t618_usb_desc = { .name = "rn5t618-usb", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = rn5t618_usb_types, - .num_usb_types = ARRAY_SIZE(rn5t618_usb_types), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .properties = rn5t618_usb_props, .num_properties = ARRAY_SIZE(rn5t618_usb_props), .get_property = rn5t618_usb_get_property, diff --git a/drivers/power/supply/rt9467-charger.c b/drivers/power/supply/rt9467-charger.c index f935bd761ac1..235169c85c5d 100644 --- a/drivers/power/supply/rt9467-charger.c +++ b/drivers/power/supply/rt9467-charger.c @@ -630,13 +630,6 @@ out: return ret; } -static const enum power_supply_usb_type rt9467_chg_usb_types[] = { - POWER_SUPPLY_USB_TYPE_UNKNOWN, - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, -}; - static const enum power_supply_property rt9467_chg_properties[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_ONLINE, @@ -771,8 +764,10 @@ static int rt9467_chg_prop_is_writeable(struct power_supply *psy, static const struct power_supply_desc rt9467_chg_psy_desc = { .name = "rt9467-charger", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = rt9467_chg_usb_types, - .num_usb_types = ARRAY_SIZE(rt9467_chg_usb_types), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .properties = rt9467_chg_properties, .num_properties = ARRAY_SIZE(rt9467_chg_properties), .property_is_writeable = rt9467_chg_prop_is_writeable, diff --git a/drivers/power/supply/rt9471.c b/drivers/power/supply/rt9471.c index 868b0703d15c..c04af1ee89c6 100644 --- a/drivers/power/supply/rt9471.c +++ b/drivers/power/supply/rt9471.c @@ -333,14 +333,6 @@ static enum power_supply_property rt9471_charger_properties[] = { POWER_SUPPLY_PROP_MANUFACTURER, }; -static enum power_supply_usb_type rt9471_charger_usb_types[] = { - POWER_SUPPLY_USB_TYPE_UNKNOWN, - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID, -}; - static int rt9471_charger_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { @@ -726,8 +718,11 @@ static int rt9471_register_psy(struct rt9471_chip *chip) desc->name = psy_name; desc->type = POWER_SUPPLY_TYPE_USB; - desc->usb_types = rt9471_charger_usb_types; - desc->num_usb_types = ARRAY_SIZE(rt9471_charger_usb_types); + desc->usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN); desc->properties = rt9471_charger_properties; desc->num_properties = ARRAY_SIZE(rt9471_charger_properties); desc->get_property = rt9471_charger_get_property; diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c index b67d5b03d93e..7382bec6a43c 100644 --- a/drivers/power/supply/ucs1002_power.c +++ b/drivers/power/supply/ucs1002_power.c @@ -296,14 +296,6 @@ static int ucs1002_set_max_current(struct ucs1002_info *info, u32 val) return 0; } -static enum power_supply_usb_type ucs1002_usb_types[] = { - POWER_SUPPLY_USB_TYPE_PD, - POWER_SUPPLY_USB_TYPE_SDP, - POWER_SUPPLY_USB_TYPE_DCP, - POWER_SUPPLY_USB_TYPE_CDP, - POWER_SUPPLY_USB_TYPE_UNKNOWN, -}; - static int ucs1002_set_usb_type(struct ucs1002_info *info, int val) { unsigned int mode; @@ -431,8 +423,11 @@ static int ucs1002_property_is_writeable(struct power_supply *psy, static const struct power_supply_desc ucs1002_charger_desc = { .name = "ucs1002", .type = POWER_SUPPLY_TYPE_USB, - .usb_types = ucs1002_usb_types, - .num_usb_types = ARRAY_SIZE(ucs1002_usb_types), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), .get_property = ucs1002_get_property, .set_property = ucs1002_set_property, .property_is_writeable = ucs1002_property_is_writeable, diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index 5a5bf3532ad7..31e3e9544bc0 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -1339,12 +1339,6 @@ static void anx7411_get_gpio_irq(struct anx7411_data *ctx) dev_err(dev, "failed to get GPIO IRQ\n"); } -static enum power_supply_usb_type anx7411_psy_usb_types[] = { - POWER_SUPPLY_USB_TYPE_C, - POWER_SUPPLY_USB_TYPE_PD, - POWER_SUPPLY_USB_TYPE_PD_PPS, -}; - static enum power_supply_property anx7411_psy_props[] = { POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_ONLINE, @@ -1422,8 +1416,9 @@ static int anx7411_psy_register(struct anx7411_data *ctx) psy_desc->name = psy_name; psy_desc->type = POWER_SUPPLY_TYPE_USB; - psy_desc->usb_types = anx7411_psy_usb_types; - psy_desc->num_usb_types = ARRAY_SIZE(anx7411_psy_usb_types); + psy_desc->usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS); psy_desc->properties = anx7411_psy_props; psy_desc->num_properties = ARRAY_SIZE(anx7411_psy_props); diff --git a/drivers/usb/typec/rt1719.c b/drivers/usb/typec/rt1719.c index be02d420920e..0b0c23a0b014 100644 --- a/drivers/usb/typec/rt1719.c +++ b/drivers/usb/typec/rt1719.c @@ -109,12 +109,6 @@ struct rt1719_data { u16 conn_stat; }; -static const enum power_supply_usb_type rt1719_psy_usb_types[] = { - POWER_SUPPLY_USB_TYPE_C, - POWER_SUPPLY_USB_TYPE_PD, - POWER_SUPPLY_USB_TYPE_PD_PPS -}; - static const enum power_supply_property rt1719_psy_properties[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_USB_TYPE, @@ -572,8 +566,9 @@ static int devm_rt1719_psy_register(struct rt1719_data *data) data->psy_desc.name = psy_name; data->psy_desc.type = POWER_SUPPLY_TYPE_USB; - data->psy_desc.usb_types = rt1719_psy_usb_types; - data->psy_desc.num_usb_types = ARRAY_SIZE(rt1719_psy_usb_types); + data->psy_desc.usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS); data->psy_desc.properties = rt1719_psy_properties; data->psy_desc.num_properties = ARRAY_SIZE(rt1719_psy_properties); data->psy_desc.get_property = rt1719_psy_get_property; diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 26f9006e95e1..0bd9c9569fb7 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -7484,12 +7484,6 @@ static int tcpm_psy_prop_writeable(struct power_supply *psy, } } -static enum power_supply_usb_type tcpm_psy_usb_types[] = { - POWER_SUPPLY_USB_TYPE_C, - POWER_SUPPLY_USB_TYPE_PD, - POWER_SUPPLY_USB_TYPE_PD_PPS, -}; - static const char *tcpm_psy_name_prefix = "tcpm-source-psy-"; static int devm_tcpm_psy_register(struct tcpm_port *port) @@ -7510,8 +7504,9 @@ static int devm_tcpm_psy_register(struct tcpm_port *port) port_dev_name); port->psy_desc.name = psy_name; port->psy_desc.type = POWER_SUPPLY_TYPE_USB; - port->psy_desc.usb_types = tcpm_psy_usb_types; - port->psy_desc.num_usb_types = ARRAY_SIZE(tcpm_psy_usb_types); + port->psy_desc.usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS); port->psy_desc.properties = tcpm_psy_props; port->psy_desc.num_properties = ARRAY_SIZE(tcpm_psy_props); port->psy_desc.get_property = tcpm_psy_get_prop; diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index ea768b19a7f1..7512f0c3f6cb 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -150,11 +150,6 @@ static enum power_supply_property tps6598x_psy_props[] = { POWER_SUPPLY_PROP_ONLINE, }; -static enum power_supply_usb_type tps6598x_psy_usb_types[] = { - POWER_SUPPLY_USB_TYPE_C, - POWER_SUPPLY_USB_TYPE_PD, -}; - static const char *tps6598x_psy_name_prefix = "tps6598x-source-psy-"; /* @@ -827,8 +822,8 @@ static int devm_tps6598_psy_register(struct tps6598x *tps) tps->psy_desc.name = psy_name; tps->psy_desc.type = POWER_SUPPLY_TYPE_USB; - tps->psy_desc.usb_types = tps6598x_psy_usb_types; - tps->psy_desc.num_usb_types = ARRAY_SIZE(tps6598x_psy_usb_types); + tps->psy_desc.usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD); tps->psy_desc.properties = tps6598x_psy_props; tps->psy_desc.num_properties = ARRAY_SIZE(tps6598x_psy_props); tps->psy_desc.get_property = tps6598x_psy_get_prop; diff --git a/drivers/usb/typec/ucsi/psy.c b/drivers/usb/typec/ucsi/psy.c index e623d80e177c..1c631c7855a9 100644 --- a/drivers/usb/typec/ucsi/psy.c +++ b/drivers/usb/typec/ucsi/psy.c @@ -254,12 +254,6 @@ static int ucsi_psy_get_prop(struct power_supply *psy, } } -static enum power_supply_usb_type ucsi_psy_usb_types[] = { - POWER_SUPPLY_USB_TYPE_C, - POWER_SUPPLY_USB_TYPE_PD, - POWER_SUPPLY_USB_TYPE_PD_PPS, -}; - int ucsi_register_port_psy(struct ucsi_connector *con) { struct power_supply_config psy_cfg = {}; @@ -276,8 +270,9 @@ int ucsi_register_port_psy(struct ucsi_connector *con) con->psy_desc.name = psy_name; con->psy_desc.type = POWER_SUPPLY_TYPE_USB; - con->psy_desc.usb_types = ucsi_psy_usb_types; - con->psy_desc.num_usb_types = ARRAY_SIZE(ucsi_psy_usb_types); + con->psy_desc.usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | + BIT(POWER_SUPPLY_USB_TYPE_PD) | + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS); con->psy_desc.properties = ucsi_psy_props; con->psy_desc.num_properties = ARRAY_SIZE(ucsi_psy_props); con->psy_desc.get_property = ucsi_psy_get_prop; diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 72dc7e45c90c..910d407ebe63 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -243,8 +243,7 @@ struct power_supply_desc { const char *name; enum power_supply_type type; u8 charge_behaviours; - const enum power_supply_usb_type *usb_types; - size_t num_usb_types; + u32 usb_types; const enum power_supply_property *properties; size_t num_properties; From 57dfd4455bd270d1efebf950c2f722977b09c57a Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 28 Aug 2024 10:34:47 +0100 Subject: [PATCH 352/573] power: supply: axp20x_usb_power: Fix spelling mistake "reqested" -> "requested" There is a spelling mistake in a dev_warn message. Fix it. Signed-off-by: Colin Ian King Acked-by: Chen-Yu Tsai Link: https://lore.kernel.org/r/20240828093447.271503-1-colin.i.king@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_usb_power.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index 7b1bf6766ff7..9f6fb448e353 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -326,7 +326,7 @@ static int axp20x_usb_power_set_input_current_limit(struct axp20x_usb_power *pow if (power->max_input_cur && (intval > power->max_input_cur)) { dev_warn(power->dev, - "reqested current %d clamped to max current %d\n", + "requested current %d clamped to max current %d\n", intval, power->max_input_cur); intval = power->max_input_cur; } From 6f9fec70e730232efa3be7293f220cf317789d17 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 21 Aug 2024 16:54:50 -0500 Subject: [PATCH 353/573] dt-bindings: power: supply: axp20x: Add AXP717 compatible Add support for the AXP717. It has BC 1.2 detection like the AXP813 and uses ADC channels like all other AXP devices, but otherwise is very different requiring new registers for most functions. Acked-by: Krzysztof Kozlowski Acked-by: Chen-Yu Tsai Signed-off-by: Chris Morgan Link: https://lore.kernel.org/r/20240821215456.962564-10-macroalpha82@gmail.com Signed-off-by: Sebastian Reichel --- .../supply/x-powers,axp20x-usb-power-supply.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-usb-power-supply.yaml b/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-usb-power-supply.yaml index ab24ebf2852f..2ec036405ae4 100644 --- a/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-usb-power-supply.yaml +++ b/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-usb-power-supply.yaml @@ -23,6 +23,7 @@ properties: - x-powers,axp202-usb-power-supply - x-powers,axp221-usb-power-supply - x-powers,axp223-usb-power-supply + - x-powers,axp717-usb-power-supply - x-powers,axp813-usb-power-supply - items: - const: x-powers,axp803-usb-power-supply @@ -75,6 +76,19 @@ allOf: input-current-limit-microamp: enum: [500000, 900000] + - if: + properties: + compatible: + contains: + enum: + - x-powers,axp717-usb-power-supply + then: + properties: + input-current-limit-microamp: + description: Maximum input current in increments of 50000 uA. + minimum: 100000 + maximum: 3250000 + - if: properties: compatible: From e44c5691822962dc6f50793029bef5e71f5b0a62 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 21 Aug 2024 16:54:51 -0500 Subject: [PATCH 354/573] dt-bindings: power: supply: axp20x: Add AXP717 compatible Add binding information for AXP717. Acked-by: Krzysztof Kozlowski Signed-off-by: Chris Morgan Acked-by: Jonathan Cameron Link: https://lore.kernel.org/r/20240821215456.962564-11-macroalpha82@gmail.com Signed-off-by: Sebastian Reichel --- .../power/supply/x-powers,axp20x-battery-power-supply.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-battery-power-supply.yaml b/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-battery-power-supply.yaml index f196bf70b248..5ccd375eb294 100644 --- a/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-battery-power-supply.yaml +++ b/Documentation/devicetree/bindings/power/supply/x-powers,axp20x-battery-power-supply.yaml @@ -23,6 +23,7 @@ properties: - const: x-powers,axp202-battery-power-supply - const: x-powers,axp209-battery-power-supply - const: x-powers,axp221-battery-power-supply + - const: x-powers,axp717-battery-power-supply - items: - const: x-powers,axp803-battery-power-supply - const: x-powers,axp813-battery-power-supply From 75098176d17fab88c06120b453b4b0d1641e2a41 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 21 Aug 2024 16:54:54 -0500 Subject: [PATCH 355/573] power: supply: axp20x_usb_power: Add support for AXP717 Add support for the AXP717 PMIC. The AXP717 PMIC allows for detection of USB type like the AXP813, but has little in common otherwise with the other AXP PMICs. The USB charger is able to provide between 100000uA and 3250000uA of power, and can measure the VBUS input in mV with up to 14 bits of precision. Tested-by: Philippe Simons Signed-off-by: Chris Morgan Acked-by: Jonathan Cameron Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240821215456.962564-14-macroalpha82@gmail.com [fix axp717_usb_power_desc.usb_types] Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_usb_power.c | 246 ++++++++++++++++++++++++ 1 file changed, 246 insertions(+) diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index 9f6fb448e353..2766352ab737 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -30,8 +30,13 @@ #define AXP20X_PWR_STATUS_VBUS_PRESENT BIT(5) #define AXP20X_PWR_STATUS_VBUS_USED BIT(4) +#define AXP717_PWR_STATUS_VBUS_GOOD BIT(5) + #define AXP20X_USB_STATUS_VBUS_VALID BIT(2) +#define AXP717_PMU_FAULT_VBUS BIT(5) +#define AXP717_PMU_FAULT_VSYS BIT(3) + #define AXP20X_VBUS_VHOLD_uV(b) (4000000 + (((b) >> 3) & 7) * 100000) #define AXP20X_VBUS_VHOLD_MASK GENMASK(5, 3) #define AXP20X_VBUS_VHOLD_OFFSET 3 @@ -39,6 +44,12 @@ #define AXP20X_ADC_EN1_VBUS_CURR BIT(2) #define AXP20X_ADC_EN1_VBUS_VOLT BIT(3) +#define AXP717_INPUT_VOL_LIMIT_MASK GENMASK(3, 0) +#define AXP717_INPUT_CUR_LIMIT_MASK GENMASK(5, 0) +#define AXP717_ADC_DATA_MASK GENMASK(14, 0) + +#define AXP717_ADC_EN_VBUS_VOLT BIT(2) + /* * Note do not raise the debounce time, we must report Vusb high within * 100ms otherwise we get Vbus errors in musb. @@ -143,6 +154,24 @@ out: mod_delayed_work(system_power_efficient_wq, &power->vbus_detect, DEBOUNCE_TIME); } +static void axp717_usb_power_poll_vbus(struct work_struct *work) +{ + struct axp20x_usb_power *power = + container_of(work, struct axp20x_usb_power, vbus_detect.work); + unsigned int val; + int ret; + + ret = regmap_read(power->regmap, AXP717_ON_INDICATE, &val); + if (ret) + return; + + val &= AXP717_PWR_STATUS_VBUS_GOOD; + if (val != power->old_status) + power_supply_changed(power->supply); + + power->old_status = val; +} + static int axp20x_get_usb_type(struct axp20x_usb_power *power, union power_supply_propval *val) { @@ -288,6 +317,91 @@ static int axp20x_usb_power_get_property(struct power_supply *psy, return 0; } +static int axp717_usb_power_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct axp20x_usb_power *power = power_supply_get_drvdata(psy); + unsigned int v; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + ret = regmap_read(power->regmap, AXP717_ON_INDICATE, &v); + if (ret) + return ret; + + if (!(v & AXP717_PWR_STATUS_VBUS_GOOD)) + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + + ret = regmap_read(power->regmap, AXP717_PMU_FAULT_VBUS, &v); + if (ret) + return ret; + + v &= (AXP717_PMU_FAULT_VBUS | AXP717_PMU_FAULT_VSYS); + if (v) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + regmap_write(power->regmap, AXP717_PMU_FAULT_VBUS, v); + } + + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = regmap_read(power->regmap, AXP717_INPUT_CUR_LIMIT_CTRL, &v); + if (ret) + return ret; + + /* 50ma step size with 100ma offset. */ + v &= AXP717_INPUT_CUR_LIMIT_MASK; + val->intval = (v * 50000) + 100000; + break; + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_PRESENT: + ret = regmap_read(power->regmap, AXP717_ON_INDICATE, &v); + if (ret) + return ret; + val->intval = !!(v & AXP717_PWR_STATUS_VBUS_GOOD); + break; + case POWER_SUPPLY_PROP_USB_TYPE: + return axp20x_get_usb_type(power, val); + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + ret = regmap_read(power->regmap, AXP717_INPUT_VOL_LIMIT_CTRL, &v); + if (ret) + return ret; + + /* 80mv step size with 3.88v offset. */ + v &= AXP717_INPUT_VOL_LIMIT_MASK; + val->intval = (v * 80000) + 3880000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (IS_ENABLED(CONFIG_AXP20X_ADC)) { + ret = iio_read_channel_processed(power->vbus_v, + &val->intval); + if (ret) + return ret; + + /* + * IIO framework gives mV but Power Supply framework + * gives uV. + */ + val->intval *= 1000; + return 0; + } + + ret = axp20x_read_variable_width(power->regmap, + AXP717_VBUS_V_H, 16); + if (ret < 0) + return ret; + + val->intval = (ret % AXP717_ADC_DATA_MASK) * 1000; + break; + default: + return -EINVAL; + } + + return 0; + +} + static int axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power, int intval) { @@ -314,6 +428,22 @@ static int axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power, return -EINVAL; } +static int axp717_usb_power_set_voltage_min(struct axp20x_usb_power *power, + int intval) +{ + int val; + + /* Minimum value of 3.88v and maximum of 5.08v. */ + if (intval < 3880000 || intval > 5080000) + return -EINVAL; + + /* step size of 80ma with 3.88v offset. */ + val = (intval - 3880000) / 80000; + return regmap_update_bits(power->regmap, + AXP717_INPUT_VOL_LIMIT_CTRL, + AXP717_INPUT_VOL_LIMIT_MASK, val); +} + static int axp20x_usb_power_set_input_current_limit(struct axp20x_usb_power *power, int intval) { @@ -354,6 +484,29 @@ static int axp20x_usb_power_set_input_current_limit(struct axp20x_usb_power *pow return regmap_field_write(power->curr_lim_fld, reg); } +static int axp717_usb_power_set_input_current_limit(struct axp20x_usb_power *power, + int intval) +{ + int tmp; + + /* Minimum value of 100mA and maximum value of 3.25A*/ + if (intval < 100000 || intval > 3250000) + return -EINVAL; + + if (power->max_input_cur && (intval > power->max_input_cur)) { + dev_warn(power->dev, + "reqested current %d clamped to max current %d\n", + intval, power->max_input_cur); + intval = power->max_input_cur; + } + + /* Minimum value of 100mA with step size of 50mA. */ + tmp = (intval - 100000) / 50000; + return regmap_update_bits(power->regmap, + AXP717_INPUT_CUR_LIMIT_CTRL, + AXP717_INPUT_CUR_LIMIT_MASK, tmp); +} + static int axp20x_usb_power_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) @@ -376,6 +529,24 @@ static int axp20x_usb_power_set_property(struct power_supply *psy, default: return -EINVAL; } +} + +static int axp717_usb_power_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct axp20x_usb_power *power = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return axp717_usb_power_set_input_current_limit(power, val->intval); + + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + return axp717_usb_power_set_voltage_min(power, val->intval); + + default: + return -EINVAL; + } return -EINVAL; } @@ -399,6 +570,13 @@ static int axp20x_usb_power_prop_writeable(struct power_supply *psy, psp == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT; } +static int axp717_usb_power_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN || + psp == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT; +} + static int axp20x_configure_iio_channels(struct platform_device *pdev, struct axp20x_usb_power *power) { @@ -419,6 +597,19 @@ static int axp20x_configure_iio_channels(struct platform_device *pdev, return 0; } +static int axp717_configure_iio_channels(struct platform_device *pdev, + struct axp20x_usb_power *power) +{ + power->vbus_v = devm_iio_channel_get(&pdev->dev, "vbus_v"); + if (IS_ERR(power->vbus_v)) { + if (PTR_ERR(power->vbus_v) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(power->vbus_v); + } + + return 0; +} + static int axp20x_configure_adc_registers(struct axp20x_usb_power *power) { /* Enable vbus voltage and current measurement */ @@ -429,6 +620,14 @@ static int axp20x_configure_adc_registers(struct axp20x_usb_power *power) AXP20X_ADC_EN1_VBUS_VOLT); } +static int axp717_configure_adc_registers(struct axp20x_usb_power *power) +{ + /* Enable vbus voltage measurement */ + return regmap_update_bits(power->regmap, AXP717_ADC_CH_EN_CONTROL, + AXP717_ADC_EN_VBUS_VOLT, + AXP717_ADC_EN_VBUS_VOLT); +} + static enum power_supply_property axp20x_usb_power_properties[] = { POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, @@ -447,6 +646,16 @@ static enum power_supply_property axp22x_usb_power_properties[] = { POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, }; +static enum power_supply_property axp717_usb_power_properties[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + static enum power_supply_property axp813_usb_power_properties[] = { POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, @@ -476,6 +685,20 @@ static const struct power_supply_desc axp22x_usb_power_desc = { .set_property = axp20x_usb_power_set_property, }; +static const struct power_supply_desc axp717_usb_power_desc = { + .name = "axp20x-usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = axp717_usb_power_properties, + .num_properties = ARRAY_SIZE(axp717_usb_power_properties), + .property_is_writeable = axp717_usb_power_prop_writeable, + .get_property = axp717_usb_power_get_property, + .set_property = axp717_usb_power_set_property, + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN), +}; + static const struct power_supply_desc axp813_usb_power_desc = { .name = "axp20x-usb", .type = POWER_SUPPLY_TYPE_USB, @@ -502,6 +725,12 @@ static const char * const axp22x_irq_names[] = { "VBUS_REMOVAL", }; +static const char * const axp717_irq_names[] = { + "VBUS_PLUGIN", + "VBUS_REMOVAL", + "VBUS_OVER_V", +}; + static int axp192_usb_curr_lim_table[] = { -1, -1, @@ -589,6 +818,20 @@ static const struct axp_data axp223_data = { .axp20x_cfg_adc_reg = axp20x_configure_adc_registers, }; +static const struct axp_data axp717_data = { + .power_desc = &axp717_usb_power_desc, + .irq_names = axp717_irq_names, + .num_irq_names = ARRAY_SIZE(axp717_irq_names), + .curr_lim_fld = REG_FIELD(AXP717_INPUT_CUR_LIMIT_CTRL, 0, 5), + .usb_bc_en_bit = REG_FIELD(AXP717_MODULE_EN_CONTROL_1, 4, 4), + .usb_bc_det_fld = REG_FIELD(AXP717_BC_DETECT, 5, 7), + .vbus_mon_bit = REG_FIELD(AXP717_ADC_CH_EN_CONTROL, 2, 2), + .vbus_needs_polling = false, + .axp20x_read_vbus = &axp717_usb_power_poll_vbus, + .axp20x_cfg_iio_chan = axp717_configure_iio_channels, + .axp20x_cfg_adc_reg = axp717_configure_adc_registers, +}; + static const struct axp_data axp813_data = { .power_desc = &axp813_usb_power_desc, .irq_names = axp22x_irq_names, @@ -816,6 +1059,9 @@ static const struct of_device_id axp20x_usb_power_match[] = { }, { .compatible = "x-powers,axp223-usb-power-supply", .data = &axp223_data, + }, { + .compatible = "x-powers,axp717-usb-power-supply", + .data = &axp717_data, }, { .compatible = "x-powers,axp813-usb-power-supply", .data = &axp813_data, From 6625767049c2e0960ba9835392a6ef9143170be6 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Wed, 21 Aug 2024 16:54:55 -0500 Subject: [PATCH 356/573] power: supply: axp20x_battery: add support for AXP717 Add support for the AXP717 PMIC battery charger. The AXP717 differs greatly from existing AXP battery chargers in that it cannot measure the discharge current. The datasheet does not document the current value's offset or scale, so the POWER_SUPPLY_PROP_CURRENT_NOW is left unscaled. Tested-by: Philippe Simons Signed-off-by: Chris Morgan Link: https://lore.kernel.org/r/20240821215456.962564-15-macroalpha82@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_battery.c | 438 ++++++++++++++++++++++++++ 1 file changed, 438 insertions(+) diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c index c903c588b361..f71cc90fea12 100644 --- a/drivers/power/supply/axp20x_battery.c +++ b/drivers/power/supply/axp20x_battery.c @@ -17,6 +17,7 @@ * GNU General Public License for more details. */ +#include #include #include #include @@ -32,9 +33,19 @@ #include #define AXP20X_PWR_STATUS_BAT_CHARGING BIT(2) +#define AXP717_PWR_STATUS_MASK GENMASK(6, 5) +#define AXP717_PWR_STATUS_BAT_STANDBY 0 +#define AXP717_PWR_STATUS_BAT_CHRG 1 +#define AXP717_PWR_STATUS_BAT_DISCHRG 2 #define AXP20X_PWR_OP_BATT_PRESENT BIT(5) #define AXP20X_PWR_OP_BATT_ACTIVATED BIT(3) +#define AXP717_PWR_OP_BATT_PRESENT BIT(3) + +#define AXP717_BATT_PMU_FAULT_MASK GENMASK(2, 0) +#define AXP717_BATT_UVLO_2_5V BIT(2) +#define AXP717_BATT_OVER_TEMP BIT(1) +#define AXP717_BATT_UNDER_TEMP BIT(0) #define AXP209_FG_PERCENT GENMASK(6, 0) #define AXP22X_FG_VALID BIT(7) @@ -49,11 +60,34 @@ #define AXP22X_CHRG_CTRL1_TGT_4_22V (1 << 5) #define AXP22X_CHRG_CTRL1_TGT_4_24V (3 << 5) +#define AXP717_CHRG_ENABLE BIT(1) +#define AXP717_CHRG_CV_VOLT_MASK GENMASK(2, 0) +#define AXP717_CHRG_CV_4_0V 0 +#define AXP717_CHRG_CV_4_1V 1 +#define AXP717_CHRG_CV_4_2V 2 +#define AXP717_CHRG_CV_4_35V 3 +#define AXP717_CHRG_CV_4_4V 4 +/* Values 5 and 6 reserved. */ +#define AXP717_CHRG_CV_5_0V 7 + #define AXP813_CHRG_CTRL1_TGT_4_35V (3 << 5) #define AXP20X_CHRG_CTRL1_TGT_CURR GENMASK(3, 0) +#define AXP717_ICC_CHARGER_LIM_MASK GENMASK(5, 0) + +#define AXP717_ITERM_CHG_LIM_MASK GENMASK(3, 0) +#define AXP717_ITERM_CC_STEP 64000 #define AXP20X_V_OFF_MASK GENMASK(2, 0) +#define AXP717_V_OFF_MASK GENMASK(6, 4) + +#define AXP717_BAT_VMIN_MIN_UV 2600000 +#define AXP717_BAT_VMIN_MAX_UV 3300000 +#define AXP717_BAT_VMIN_STEP 100000 +#define AXP717_BAT_CV_MIN_UV 4000000 +#define AXP717_BAT_CV_MAX_UV 5000000 +#define AXP717_BAT_CC_MIN_UA 0 +#define AXP717_BAT_CC_MAX_UA 3008000 struct axp20x_batt_ps; @@ -143,6 +177,39 @@ static int axp22x_battery_get_max_voltage(struct axp20x_batt_ps *axp20x_batt, return 0; } +static int axp717_battery_get_max_voltage(struct axp20x_batt_ps *axp20x_batt, + int *val) +{ + int ret, reg; + + ret = regmap_read(axp20x_batt->regmap, AXP717_CV_CHG_SET, ®); + if (ret) + return ret; + + switch (reg & AXP717_CHRG_CV_VOLT_MASK) { + case AXP717_CHRG_CV_4_0V: + *val = 4000000; + return 0; + case AXP717_CHRG_CV_4_1V: + *val = 4100000; + return 0; + case AXP717_CHRG_CV_4_2V: + *val = 4200000; + return 0; + case AXP717_CHRG_CV_4_35V: + *val = 4350000; + return 0; + case AXP717_CHRG_CV_4_4V: + *val = 4400000; + return 0; + case AXP717_CHRG_CV_5_0V: + *val = 5000000; + return 0; + default: + return -EINVAL; + } +} + static int axp813_battery_get_max_voltage(struct axp20x_batt_ps *axp20x_batt, int *val) { @@ -188,6 +255,21 @@ static int axp20x_get_constant_charge_current(struct axp20x_batt_ps *axp, return 0; } +static int axp717_get_constant_charge_current(struct axp20x_batt_ps *axp, + int *val) +{ + int ret; + + ret = regmap_read(axp->regmap, AXP717_ICC_CHG_SET, val); + if (ret) + return ret; + + *val = FIELD_GET(AXP717_ICC_CHARGER_LIM_MASK, *val) * + axp->data->ccc_scale; + + return 0; +} + static int axp20x_battery_get_prop(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -340,6 +422,171 @@ static int axp20x_battery_get_prop(struct power_supply *psy, return 0; } +static int axp717_battery_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); + int ret = 0, reg; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + ret = regmap_read(axp20x_batt->regmap, AXP717_ON_INDICATE, + ®); + if (ret) + return ret; + + val->intval = FIELD_GET(AXP717_PWR_OP_BATT_PRESENT, reg); + return 0; + + case POWER_SUPPLY_PROP_STATUS: + ret = regmap_read(axp20x_batt->regmap, AXP717_PMU_STATUS_2, + ®); + if (ret) + return ret; + + switch (FIELD_GET(AXP717_PWR_STATUS_MASK, reg)) { + case AXP717_PWR_STATUS_BAT_STANDBY: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + return 0; + + case AXP717_PWR_STATUS_BAT_CHRG: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + return 0; + + case AXP717_PWR_STATUS_BAT_DISCHRG: + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + + default: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + return 0; + } + + /* + * If a fault is detected it must also be cleared; if the + * condition persists it should reappear (This is an + * assumption, it's actually not documented). A restart was + * not sufficient to clear the bit in testing despite the + * register listed as POR. + */ + case POWER_SUPPLY_PROP_HEALTH: + ret = regmap_read(axp20x_batt->regmap, AXP717_PMU_FAULT, + ®); + if (ret) + return ret; + + switch (reg & AXP717_BATT_PMU_FAULT_MASK) { + case AXP717_BATT_UVLO_2_5V: + val->intval = POWER_SUPPLY_HEALTH_DEAD; + regmap_update_bits(axp20x_batt->regmap, + AXP717_PMU_FAULT, + AXP717_BATT_UVLO_2_5V, + AXP717_BATT_UVLO_2_5V); + return 0; + + case AXP717_BATT_OVER_TEMP: + val->intval = POWER_SUPPLY_HEALTH_HOT; + regmap_update_bits(axp20x_batt->regmap, + AXP717_PMU_FAULT, + AXP717_BATT_OVER_TEMP, + AXP717_BATT_OVER_TEMP); + return 0; + + case AXP717_BATT_UNDER_TEMP: + val->intval = POWER_SUPPLY_HEALTH_COLD; + regmap_update_bits(axp20x_batt->regmap, + AXP717_PMU_FAULT, + AXP717_BATT_UNDER_TEMP, + AXP717_BATT_UNDER_TEMP); + return 0; + + default: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + return 0; + } + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + ret = axp717_get_constant_charge_current(axp20x_batt, + &val->intval); + if (ret) + return ret; + return 0; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + /* + * The offset of this value is currently unknown and is + * not documented in the datasheet. Based on + * observation it's assumed to be somewhere around + * 450ma. I will leave the value raw for now. + */ + ret = iio_read_channel_processed(axp20x_batt->batt_chrg_i, &val->intval); + if (ret) + return ret; + /* IIO framework gives mA but Power Supply framework gives uA */ + val->intval *= 1000; + return 0; + + case POWER_SUPPLY_PROP_CAPACITY: + ret = regmap_read(axp20x_batt->regmap, AXP717_ON_INDICATE, + ®); + if (ret) + return ret; + + if (!FIELD_GET(AXP717_PWR_OP_BATT_PRESENT, reg)) + return -ENODEV; + + ret = regmap_read(axp20x_batt->regmap, + AXP717_BATT_PERCENT_DATA, ®); + if (ret) + return ret; + + /* + * Fuel Gauge data takes 7 bits but the stored value seems to be + * directly the raw percentage without any scaling to 7 bits. + */ + val->intval = reg & AXP209_FG_PERCENT; + return 0; + + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + return axp20x_batt->data->get_max_voltage(axp20x_batt, + &val->intval); + + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + ret = regmap_read(axp20x_batt->regmap, + AXP717_VSYS_V_POWEROFF, ®); + if (ret) + return ret; + + val->intval = AXP717_BAT_VMIN_MIN_UV + AXP717_BAT_VMIN_STEP * + (reg & AXP717_V_OFF_MASK); + return 0; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = iio_read_channel_processed(axp20x_batt->batt_v, + &val->intval); + if (ret) + return ret; + + /* IIO framework gives mV but Power Supply framework gives uV */ + val->intval *= 1000; + return 0; + + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + ret = regmap_read(axp20x_batt->regmap, + AXP717_ITERM_CHG_SET, ®); + if (ret) + return ret; + + val->intval = (reg & AXP717_ITERM_CHG_LIM_MASK) * AXP717_ITERM_CC_STEP; + return 0; + + default: + return -EINVAL; + } +} + static int axp22x_battery_set_max_voltage(struct axp20x_batt_ps *axp20x_batt, int val) { @@ -396,6 +643,35 @@ static int axp20x_battery_set_max_voltage(struct axp20x_batt_ps *axp20x_batt, AXP20X_CHRG_CTRL1_TGT_VOLT, val); } +static int axp717_battery_set_max_voltage(struct axp20x_batt_ps *axp20x_batt, + int val) +{ + switch (val) { + case 4000000: + val = AXP717_CHRG_CV_4_0V; + break; + + case 4100000: + val = AXP717_CHRG_CV_4_1V; + break; + + case 4200000: + val = AXP717_CHRG_CV_4_2V; + break; + + default: + /* + * AXP717 can go up to 4.35, 4.4, and 5.0 volts which + * seem too high for lithium batteries, so do not allow. + */ + return -EINVAL; + } + + return regmap_update_bits(axp20x_batt->regmap, + AXP717_CV_CHG_SET, + AXP717_CHRG_CV_VOLT_MASK, val); +} + static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt, int charge_current) { @@ -412,6 +688,24 @@ static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt, AXP20X_CHRG_CTRL1_TGT_CURR, charge_current); } +static int axp717_set_constant_charge_current(struct axp20x_batt_ps *axp, + int charge_current) +{ + int val; + + if (charge_current > axp->max_ccc) + return -EINVAL; + + if (charge_current > AXP717_BAT_CC_MAX_UA || charge_current < 0) + return -EINVAL; + + val = (charge_current - axp->data->ccc_offset) / + axp->data->ccc_scale; + + return regmap_update_bits(axp->regmap, AXP717_ICC_CHG_SET, + AXP717_ICC_CHARGER_LIM_MASK, val); +} + static int axp20x_set_max_constant_charge_current(struct axp20x_batt_ps *axp, int charge_current) { @@ -456,6 +750,19 @@ static int axp20x_set_voltage_min_design(struct axp20x_batt_ps *axp_batt, AXP20X_V_OFF_MASK, val1); } +static int axp717_set_voltage_min_design(struct axp20x_batt_ps *axp_batt, + int min_voltage) +{ + int val1 = (min_voltage - AXP717_BAT_VMIN_MIN_UV) / AXP717_BAT_VMIN_STEP; + + if (val1 < 0 || val1 > AXP717_V_OFF_MASK) + return -EINVAL; + + return regmap_update_bits(axp_batt->regmap, + AXP717_VSYS_V_POWEROFF, + AXP717_V_OFF_MASK, val1); +} + static int axp20x_battery_set_prop(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) @@ -492,6 +799,42 @@ static int axp20x_battery_set_prop(struct power_supply *psy, } } +static int axp717_battery_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct axp20x_batt_ps *axp20x_batt = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + return axp717_set_voltage_min_design(axp20x_batt, val->intval); + + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + return axp20x_batt->data->set_max_voltage(axp20x_batt, val->intval); + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return axp717_set_constant_charge_current(axp20x_batt, + val->intval); + case POWER_SUPPLY_PROP_STATUS: + switch (val->intval) { + case POWER_SUPPLY_STATUS_CHARGING: + return regmap_update_bits(axp20x_batt->regmap, + AXP717_MODULE_EN_CONTROL_2, + AXP717_CHRG_ENABLE, + AXP717_CHRG_ENABLE); + + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + return regmap_update_bits(axp20x_batt->regmap, + AXP717_MODULE_EN_CONTROL_2, + AXP717_CHRG_ENABLE, 0); + } + return -EINVAL; + default: + return -EINVAL; + } +} + static enum power_supply_property axp20x_battery_props[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, @@ -506,6 +849,20 @@ static enum power_supply_property axp20x_battery_props[] = { POWER_SUPPLY_PROP_CAPACITY, }; +static enum power_supply_property axp717_battery_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, +}; + static int axp20x_battery_prop_writeable(struct power_supply *psy, enum power_supply_property psp) { @@ -516,6 +873,15 @@ static int axp20x_battery_prop_writeable(struct power_supply *psy, psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX; } +static int axp717_battery_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + return psp == POWER_SUPPLY_PROP_STATUS || + psp == POWER_SUPPLY_PROP_VOLTAGE_MIN || + psp == POWER_SUPPLY_PROP_VOLTAGE_MAX || + psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX; +} + static const struct power_supply_desc axp209_batt_ps_desc = { .name = "axp20x-battery", .type = POWER_SUPPLY_TYPE_BATTERY, @@ -526,6 +892,16 @@ static const struct power_supply_desc axp209_batt_ps_desc = { .set_property = axp20x_battery_set_prop, }; +static const struct power_supply_desc axp717_batt_ps_desc = { + .name = "axp20x-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = axp717_battery_props, + .num_properties = ARRAY_SIZE(axp717_battery_props), + .property_is_writeable = axp717_battery_prop_writeable, + .get_property = axp717_battery_get_prop, + .set_property = axp717_battery_set_prop, +}; + static int axp209_bat_cfg_iio_channels(struct platform_device *pdev, struct axp20x_batt_ps *axp_batt) { @@ -555,6 +931,27 @@ static int axp209_bat_cfg_iio_channels(struct platform_device *pdev, return 0; } +static int axp717_bat_cfg_iio_channels(struct platform_device *pdev, + struct axp20x_batt_ps *axp_batt) +{ + axp_batt->batt_v = devm_iio_channel_get(&pdev->dev, "batt_v"); + if (IS_ERR(axp_batt->batt_v)) { + if (PTR_ERR(axp_batt->batt_v) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(axp_batt->batt_v); + } + + axp_batt->batt_chrg_i = devm_iio_channel_get(&pdev->dev, + "batt_chrg_i"); + if (IS_ERR(axp_batt->batt_chrg_i)) { + if (PTR_ERR(axp_batt->batt_chrg_i) == -ENODEV) + return -EPROBE_DEFER; + return PTR_ERR(axp_batt->batt_chrg_i); + } + + return 0; +} + static void axp209_set_battery_info(struct platform_device *pdev, struct axp20x_batt_ps *axp_batt, struct power_supply_battery_info *info) @@ -578,6 +975,32 @@ static void axp209_set_battery_info(struct platform_device *pdev, } } +static void axp717_set_battery_info(struct platform_device *pdev, + struct axp20x_batt_ps *axp_batt, + struct power_supply_battery_info *info) +{ + int vmin = info->voltage_min_design_uv; + int vmax = info->voltage_max_design_uv; + int ccc = info->constant_charge_current_max_ua; + int val; + + if (vmin > 0 && axp717_set_voltage_min_design(axp_batt, vmin)) + dev_err(&pdev->dev, + "couldn't set voltage_min_design\n"); + + if (vmax > 0 && axp717_battery_set_max_voltage(axp_batt, vmax)) + dev_err(&pdev->dev, + "couldn't set voltage_max_design\n"); + + axp717_get_constant_charge_current(axp_batt, &val); + axp_batt->max_ccc = ccc; + if (ccc <= 0 || axp717_set_constant_charge_current(axp_batt, ccc)) { + dev_err(&pdev->dev, + "couldn't set ccc from DT: current ccc is %d\n", + val); + } +} + static const struct axp_data axp209_data = { .ccc_scale = 100000, .ccc_offset = 300000, @@ -603,6 +1026,18 @@ static const struct axp_data axp221_data = { .set_bat_info = axp209_set_battery_info, }; +static const struct axp_data axp717_data = { + .ccc_scale = 64000, + .ccc_offset = 0, + .ccc_reg = AXP717_ICC_CHG_SET, + .ccc_mask = AXP717_ICC_CHARGER_LIM_MASK, + .bat_ps_desc = &axp717_batt_ps_desc, + .get_max_voltage = axp717_battery_get_max_voltage, + .set_max_voltage = axp717_battery_set_max_voltage, + .cfg_iio_chan = axp717_bat_cfg_iio_channels, + .set_bat_info = axp717_set_battery_info, +}; + static const struct axp_data axp813_data = { .ccc_scale = 200000, .ccc_offset = 200000, @@ -623,6 +1058,9 @@ static const struct of_device_id axp20x_battery_ps_id[] = { }, { .compatible = "x-powers,axp221-battery-power-supply", .data = (void *)&axp221_data, + }, { + .compatible = "x-powers,axp717-battery-power-supply", + .data = (void *)&axp717_data, }, { .compatible = "x-powers,axp813-battery-power-supply", .data = (void *)&axp813_data, From a794331325f143bd010a91aa078547fee7fe907e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 2 Sep 2024 16:30:40 +0300 Subject: [PATCH 357/573] gpio: stmpe: Fix IRQ related error messages First of all, remove duplicate message that platform_get_irq() does already print. Second, correct the error message when unable to register a handler, which is broken in two ways: 1) the misleading 'get' vs. 'register'; 2) missing '\n' at the end. (Yes, for the curious ones, the dev_*() cases do not require '\n' and issue it automatically, but it's better to have them explicit) Fix all this here. Fixes: 1882e769362b ("gpio: stmpe: Simplify with dev_err_probe()") Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240902133148.2569486-2-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-stmpe.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-stmpe.c b/drivers/gpio/gpio-stmpe.c index abd42a975b09..4e171f9075bf 100644 --- a/drivers/gpio/gpio-stmpe.c +++ b/drivers/gpio/gpio-stmpe.c @@ -464,6 +464,7 @@ static void stmpe_gpio_disable(void *stmpe) static int stmpe_gpio_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent); struct device_node *np = pdev->dev.of_node; struct stmpe_gpio *stmpe_gpio; @@ -493,12 +494,6 @@ static int stmpe_gpio_probe(struct platform_device *pdev) of_property_read_u32(np, "st,norequest-mask", &stmpe_gpio->norequest_mask); - irq = platform_get_irq(pdev, 0); - if (irq < 0) - dev_info(&pdev->dev, - "device configured in no-irq mode: " - "irqs are not available\n"); - ret = stmpe_enable(stmpe, STMPE_BLOCK_GPIO); if (ret) return ret; @@ -507,6 +502,7 @@ static int stmpe_gpio_probe(struct platform_device *pdev) if (ret) return ret; + irq = platform_get_irq(pdev, 0); if (irq > 0) { struct gpio_irq_chip *girq; @@ -514,8 +510,7 @@ static int stmpe_gpio_probe(struct platform_device *pdev) stmpe_gpio_irq, IRQF_ONESHOT, "stmpe-gpio", stmpe_gpio); if (ret) - return dev_err_probe(&pdev->dev, ret, - "unable to get irq"); + return dev_err_probe(dev, ret, "unable to register IRQ handler\n"); girq = &stmpe_gpio->chip.irq; gpio_irq_chip_set_chip(girq, &stmpe_gpio_irq_chip); From c028e1c5a414f03cd849912073db7c1927ec8d89 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 2 Sep 2024 16:30:41 +0300 Subject: [PATCH 358/573] gpio: stmpe: Remove unused 'dev' member of struct stmpe_gpio There is no evidence that the 'dev' member of struct stmpe_gpio is used, drop it. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240902133148.2569486-3-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-stmpe.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpio/gpio-stmpe.c b/drivers/gpio/gpio-stmpe.c index 4e171f9075bf..99f1482b2ab3 100644 --- a/drivers/gpio/gpio-stmpe.c +++ b/drivers/gpio/gpio-stmpe.c @@ -31,7 +31,6 @@ enum { LSB, CSB, MSB }; struct stmpe_gpio { struct gpio_chip chip; struct stmpe *stmpe; - struct device *dev; struct mutex irq_lock; u32 norequest_mask; /* Caches of interrupt control registers for bus_lock */ @@ -481,7 +480,6 @@ static int stmpe_gpio_probe(struct platform_device *pdev) mutex_init(&stmpe_gpio->irq_lock); - stmpe_gpio->dev = &pdev->dev; stmpe_gpio->stmpe = stmpe; stmpe_gpio->chip = template_chip; stmpe_gpio->chip.ngpio = stmpe->num_gpios; From 56f534dde6ff41eaf71f4e368953cb8da54cecc3 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 2 Sep 2024 16:30:42 +0300 Subject: [PATCH 359/573] gpio: stmpe: Utilise temporary variable for struct device We have a temporary variable to keep a pointer to struct device. Utilise it where it makes sense. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240902133148.2569486-4-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-stmpe.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/gpio/gpio-stmpe.c b/drivers/gpio/gpio-stmpe.c index 99f1482b2ab3..7f2911c478ea 100644 --- a/drivers/gpio/gpio-stmpe.c +++ b/drivers/gpio/gpio-stmpe.c @@ -464,17 +464,17 @@ static void stmpe_gpio_disable(void *stmpe) static int stmpe_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent); - struct device_node *np = pdev->dev.of_node; + struct stmpe *stmpe = dev_get_drvdata(dev->parent); + struct device_node *np = dev->of_node; struct stmpe_gpio *stmpe_gpio; int ret, irq; if (stmpe->num_gpios > MAX_GPIOS) { - dev_err(&pdev->dev, "Need to increase maximum GPIO number\n"); + dev_err(dev, "Need to increase maximum GPIO number\n"); return -EINVAL; } - stmpe_gpio = devm_kzalloc(&pdev->dev, sizeof(*stmpe_gpio), GFP_KERNEL); + stmpe_gpio = devm_kzalloc(dev, sizeof(*stmpe_gpio), GFP_KERNEL); if (!stmpe_gpio) return -ENOMEM; @@ -483,7 +483,7 @@ static int stmpe_gpio_probe(struct platform_device *pdev) stmpe_gpio->stmpe = stmpe; stmpe_gpio->chip = template_chip; stmpe_gpio->chip.ngpio = stmpe->num_gpios; - stmpe_gpio->chip.parent = &pdev->dev; + stmpe_gpio->chip.parent = dev; stmpe_gpio->chip.base = -1; if (IS_ENABLED(CONFIG_DEBUG_FS)) @@ -496,7 +496,7 @@ static int stmpe_gpio_probe(struct platform_device *pdev) if (ret) return ret; - ret = devm_add_action_or_reset(&pdev->dev, stmpe_gpio_disable, stmpe); + ret = devm_add_action_or_reset(dev, stmpe_gpio_disable, stmpe); if (ret) return ret; @@ -504,9 +504,8 @@ static int stmpe_gpio_probe(struct platform_device *pdev) if (irq > 0) { struct gpio_irq_chip *girq; - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - stmpe_gpio_irq, IRQF_ONESHOT, - "stmpe-gpio", stmpe_gpio); + ret = devm_request_threaded_irq(dev, irq, NULL, stmpe_gpio_irq, + IRQF_ONESHOT, "stmpe-gpio", stmpe_gpio); if (ret) return dev_err_probe(dev, ret, "unable to register IRQ handler\n"); @@ -522,7 +521,7 @@ static int stmpe_gpio_probe(struct platform_device *pdev) girq->init_valid_mask = stmpe_init_irq_valid_mask; } - return devm_gpiochip_add_data(&pdev->dev, &stmpe_gpio->chip, stmpe_gpio); + return devm_gpiochip_add_data(dev, &stmpe_gpio->chip, stmpe_gpio); } static struct platform_driver stmpe_gpio_driver = { From e6815a05c0c909c8d6396bf41d0c06bc967f37bc Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 2 Sep 2024 16:30:43 +0300 Subject: [PATCH 360/573] gpio: stmpe: Make use of device properties Convert the module to be property provider agnostic and allow it to be used on non-OF platforms. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240902133148.2569486-5-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-stmpe.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-stmpe.c b/drivers/gpio/gpio-stmpe.c index 7f2911c478ea..c1fb06925e09 100644 --- a/drivers/gpio/gpio-stmpe.c +++ b/drivers/gpio/gpio-stmpe.c @@ -11,8 +11,8 @@ #include #include #include -#include #include +#include #include #include @@ -465,7 +465,6 @@ static int stmpe_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct stmpe *stmpe = dev_get_drvdata(dev->parent); - struct device_node *np = dev->of_node; struct stmpe_gpio *stmpe_gpio; int ret, irq; @@ -489,8 +488,7 @@ static int stmpe_gpio_probe(struct platform_device *pdev) if (IS_ENABLED(CONFIG_DEBUG_FS)) stmpe_gpio->chip.dbg_show = stmpe_dbg_show; - of_property_read_u32(np, "st,norequest-mask", - &stmpe_gpio->norequest_mask); + device_property_read_u32(dev, "st,norequest-mask", &stmpe_gpio->norequest_mask); ret = stmpe_enable(stmpe, STMPE_BLOCK_GPIO); if (ret) From 9f0127b9cea593a661004df948dc0b4479081c2e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 2 Sep 2024 16:30:44 +0300 Subject: [PATCH 361/573] gpio: stmpe: Sort headers Sort the headers in alphabetic order in order to ease the maintenance for this part. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240902133148.2569486-6-andriy.shevchenko@linux.intel.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-stmpe.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpio-stmpe.c b/drivers/gpio/gpio-stmpe.c index c1fb06925e09..75a3633ceddb 100644 --- a/drivers/gpio/gpio-stmpe.c +++ b/drivers/gpio/gpio-stmpe.c @@ -5,16 +5,16 @@ * Author: Rabin Vincent for ST-Ericsson */ +#include #include -#include -#include -#include #include +#include #include #include #include +#include #include -#include +#include /* * These registers are modified under the irq bus lock and cached to avoid From d29e741cad3f8f41df1834bf74df79380c1c6c6d Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 19 Aug 2024 17:17:04 +0200 Subject: [PATCH 362/573] gpio: davinci: drop platform data support There are no more any board files that use the platform data for gpio-davinci. We can remove the header defining it and port the code to no longer store any context in pdata. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20240819151705.37258-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-davinci.c | 89 ++++++---------------- include/linux/platform_data/gpio-davinci.h | 21 ----- 2 files changed, 25 insertions(+), 85 deletions(-) delete mode 100644 include/linux/platform_data/gpio-davinci.h diff --git a/drivers/gpio/gpio-davinci.c b/drivers/gpio/gpio-davinci.c index 1d0175d6350b..7763b99f814a 100644 --- a/drivers/gpio/gpio-davinci.c +++ b/drivers/gpio/gpio-davinci.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -154,74 +153,37 @@ davinci_gpio_set(struct gpio_chip *chip, unsigned offset, int value) value ? &g->set_data : &g->clr_data); } -static struct davinci_gpio_platform_data * -davinci_gpio_get_pdata(struct platform_device *pdev) -{ - struct device_node *dn = pdev->dev.of_node; - struct davinci_gpio_platform_data *pdata; - int ret; - u32 val; - - if (!IS_ENABLED(CONFIG_OF) || !pdev->dev.of_node) - return dev_get_platdata(&pdev->dev); - - pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - return NULL; - - ret = of_property_read_u32(dn, "ti,ngpio", &val); - if (ret) - goto of_err; - - pdata->ngpio = val; - - ret = of_property_read_u32(dn, "ti,davinci-gpio-unbanked", &val); - if (ret) - goto of_err; - - pdata->gpio_unbanked = val; - - return pdata; - -of_err: - dev_err(&pdev->dev, "Populating pdata from DT failed: err %d\n", ret); - return NULL; -} - static int davinci_gpio_probe(struct platform_device *pdev) { int bank, i, ret = 0; - unsigned int ngpio, nbank, nirq; + unsigned int ngpio, nbank, nirq, gpio_unbanked; struct davinci_gpio_controller *chips; - struct davinci_gpio_platform_data *pdata; struct device *dev = &pdev->dev; - - pdata = davinci_gpio_get_pdata(pdev); - if (!pdata) { - dev_err(dev, "No platform data found\n"); - return -EINVAL; - } - - dev->platform_data = pdata; + struct device_node *dn = dev_of_node(dev); /* * The gpio banks conceptually expose a segmented bitmap, * and "ngpio" is one more than the largest zero-based * bit index that's valid. */ - ngpio = pdata->ngpio; - if (ngpio == 0) { - dev_err(dev, "How many GPIOs?\n"); - return -EINVAL; - } + ret = of_property_read_u32(dn, "ti,ngpio", &ngpio); + if (ret) + return dev_err_probe(dev, ret, "Failed to get the number of GPIOs\n"); + if (ngpio == 0) + return dev_err_probe(dev, -EINVAL, "How many GPIOs?\n"); /* * If there are unbanked interrupts then the number of * interrupts is equal to number of gpios else all are banked so * number of interrupts is equal to number of banks(each with 16 gpios) */ - if (pdata->gpio_unbanked) - nirq = pdata->gpio_unbanked; + ret = of_property_read_u32(dn, "ti,davinci-gpio-unbanked", + &gpio_unbanked); + if (ret) + return dev_err_probe(dev, ret, "Failed to get the unbanked GPIOs property\n"); + + if (gpio_unbanked) + nirq = gpio_unbanked; else nirq = DIV_ROUND_UP(ngpio, 16); @@ -252,7 +214,7 @@ static int davinci_gpio_probe(struct platform_device *pdev) chips->chip.set = davinci_gpio_set; chips->chip.ngpio = ngpio; - chips->chip.base = pdata->no_auto_base ? pdata->base : -1; + chips->chip.base = -1; #ifdef CONFIG_OF_GPIO chips->chip.parent = dev; @@ -261,6 +223,8 @@ static int davinci_gpio_probe(struct platform_device *pdev) #endif spin_lock_init(&chips->lock); + chips->gpio_unbanked = gpio_unbanked; + nbank = DIV_ROUND_UP(ngpio, 32); for (bank = 0; bank < nbank; bank++) chips->regs[bank] = gpio_base + offset_array[bank]; @@ -488,7 +452,6 @@ static int davinci_gpio_irq_setup(struct platform_device *pdev) unsigned ngpio; struct device *dev = &pdev->dev; struct davinci_gpio_controller *chips = platform_get_drvdata(pdev); - struct davinci_gpio_platform_data *pdata = dev->platform_data; struct davinci_gpio_regs __iomem *g; struct irq_domain *irq_domain = NULL; struct irq_chip *irq_chip; @@ -502,7 +465,7 @@ static int davinci_gpio_irq_setup(struct platform_device *pdev) if (dev->of_node) gpio_get_irq_chip = (gpio_get_irq_chip_cb_t)device_get_match_data(dev); - ngpio = pdata->ngpio; + ngpio = chips->chip.ngpio; clk = devm_clk_get(dev, "gpio"); if (IS_ERR(clk)) { @@ -514,7 +477,7 @@ static int davinci_gpio_irq_setup(struct platform_device *pdev) if (ret) return ret; - if (!pdata->gpio_unbanked) { + if (chips->gpio_unbanked) { irq = devm_irq_alloc_descs(dev, -1, 0, ngpio, 0); if (irq < 0) { dev_err(dev, "Couldn't allocate IRQ numbers\n"); @@ -546,11 +509,11 @@ static int davinci_gpio_irq_setup(struct platform_device *pdev) * controller only handling trigger modes. We currently assume no * IRQ mux conflicts; gpio_irq_type_unbanked() is only for GPIOs. */ - if (pdata->gpio_unbanked) { + if (chips->gpio_unbanked) { /* pass "bank 0" GPIO IRQs to AINTC */ chips->chip.to_irq = gpio_to_irq_unbanked; - chips->gpio_unbanked = pdata->gpio_unbanked; - binten = GENMASK(pdata->gpio_unbanked / 16, 0); + + binten = GENMASK(chips->gpio_unbanked / 16, 0); /* AINTC handles mask/unmask; GPIO handles triggering */ irq = chips->irqs[0]; @@ -564,7 +527,7 @@ static int davinci_gpio_irq_setup(struct platform_device *pdev) writel_relaxed(~0, &g->set_rising); /* set the direct IRQs up to use that irqchip */ - for (gpio = 0; gpio < pdata->gpio_unbanked; gpio++) { + for (gpio = 0; gpio < chips->gpio_unbanked; gpio++) { irq_set_chip(chips->irqs[gpio], irq_chip); irq_set_handler_data(chips->irqs[gpio], chips); irq_set_status_flags(chips->irqs[gpio], @@ -675,8 +638,7 @@ static void davinci_gpio_restore_context(struct davinci_gpio_controller *chips, static int davinci_gpio_suspend(struct device *dev) { struct davinci_gpio_controller *chips = dev_get_drvdata(dev); - struct davinci_gpio_platform_data *pdata = dev_get_platdata(dev); - u32 nbank = DIV_ROUND_UP(pdata->ngpio, 32); + u32 nbank = DIV_ROUND_UP(chips->chip.ngpio, 32); davinci_gpio_save_context(chips, nbank); @@ -686,8 +648,7 @@ static int davinci_gpio_suspend(struct device *dev) static int davinci_gpio_resume(struct device *dev) { struct davinci_gpio_controller *chips = dev_get_drvdata(dev); - struct davinci_gpio_platform_data *pdata = dev_get_platdata(dev); - u32 nbank = DIV_ROUND_UP(pdata->ngpio, 32); + u32 nbank = DIV_ROUND_UP(chips->chip.ngpio, 32); davinci_gpio_restore_context(chips, nbank); diff --git a/include/linux/platform_data/gpio-davinci.h b/include/linux/platform_data/gpio-davinci.h deleted file mode 100644 index b82e44662efe..000000000000 --- a/include/linux/platform_data/gpio-davinci.h +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * DaVinci GPIO Platform Related Defines - * - * Copyright (C) 2013 Texas Instruments Incorporated - https://www.ti.com/ - */ - -#ifndef __DAVINCI_GPIO_PLATFORM_H -#define __DAVINCI_GPIO_PLATFORM_H - -struct davinci_gpio_platform_data { - bool no_auto_base; - u32 base; - u32 ngpio; - u32 gpio_unbanked; -}; - -/* Convert GPIO signal to GPIO pin number */ -#define GPIO_TO_PIN(bank, gpio) (16 * (bank) + (gpio)) - -#endif From d14f6f405fc7b66b0a18967378a4114054b2690c Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 19 Aug 2024 17:17:05 +0200 Subject: [PATCH 363/573] gpio: davinci: use devm_clk_get_enabled() Simplify the code in error paths by using the managed variant of the clock getter that controls the clock state as well. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20240819151705.37258-2-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-davinci.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/drivers/gpio/gpio-davinci.c b/drivers/gpio/gpio-davinci.c index 7763b99f814a..b54fef6b1e12 100644 --- a/drivers/gpio/gpio-davinci.c +++ b/drivers/gpio/gpio-davinci.c @@ -446,7 +446,6 @@ static int davinci_gpio_irq_setup(struct platform_device *pdev) { unsigned gpio, bank; int irq; - int ret; struct clk *clk; u32 binten = 0; unsigned ngpio; @@ -467,21 +466,16 @@ static int davinci_gpio_irq_setup(struct platform_device *pdev) ngpio = chips->chip.ngpio; - clk = devm_clk_get(dev, "gpio"); + clk = devm_clk_get_enabled(dev, "gpio"); if (IS_ERR(clk)) { dev_err(dev, "Error %ld getting gpio clock\n", PTR_ERR(clk)); return PTR_ERR(clk); } - ret = clk_prepare_enable(clk); - if (ret) - return ret; - if (chips->gpio_unbanked) { irq = devm_irq_alloc_descs(dev, -1, 0, ngpio, 0); if (irq < 0) { dev_err(dev, "Couldn't allocate IRQ numbers\n"); - clk_disable_unprepare(clk); return irq; } @@ -490,7 +484,6 @@ static int davinci_gpio_irq_setup(struct platform_device *pdev) chips); if (!irq_domain) { dev_err(dev, "Couldn't register an IRQ domain\n"); - clk_disable_unprepare(clk); return -ENODEV; } } @@ -559,10 +552,8 @@ static int davinci_gpio_irq_setup(struct platform_device *pdev) sizeof(struct davinci_gpio_irq_data), GFP_KERNEL); - if (!irqdata) { - clk_disable_unprepare(clk); + if (!irqdata) return -ENOMEM; - } irqdata->regs = g; irqdata->bank_num = bank; From ccaf84694ce7e7438706185c726310be51954fd3 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 3 Sep 2024 17:45:32 +0200 Subject: [PATCH 364/573] gpio: mpc8xxx: order headers alphabetically Cleanup the includes by putting them in alphabetical order. Reviewed-by: Andy Shevchenko Reviewed-by: Martyn Welch Link: https://lore.kernel.org/r/20240903154533.101258-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mpc8xxx.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/gpio/gpio-mpc8xxx.c b/drivers/gpio/gpio-mpc8xxx.c index ab30c911c9d5..e084e08f5438 100644 --- a/drivers/gpio/gpio-mpc8xxx.c +++ b/drivers/gpio/gpio-mpc8xxx.c @@ -7,19 +7,19 @@ */ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #define MPC8XXX_GPIO_PINS 32 From 04f4f33c941c221645d2a58b46f4d698b0f5aa39 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Wed, 31 Jul 2024 13:12:44 -0600 Subject: [PATCH 365/573] iommu: Use of_property_present() Use of_property_present() to test for property presence rather than of_(find|get)_property(). This is part of a larger effort to remove callers of of_find_property() and similar functions. of_find_property() leaks the DT struct property and data pointers which is a problem for dynamically allocated nodes which may be freed. Signed-off-by: Rob Herring (Arm) Reviewed-by: Robin Murphy Link: https://lore.kernel.org/r/20240731191312.1710417-6-robh@kernel.org Signed-off-by: Joerg Roedel --- drivers/iommu/fsl_pamu_domain.c | 4 +--- drivers/iommu/of_iommu.c | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/iommu/fsl_pamu_domain.c b/drivers/iommu/fsl_pamu_domain.c index e9d2bff4659b..30be786bff11 100644 --- a/drivers/iommu/fsl_pamu_domain.c +++ b/drivers/iommu/fsl_pamu_domain.c @@ -416,14 +416,12 @@ static struct iommu_group *fsl_pamu_device_group(struct device *dev) static struct iommu_device *fsl_pamu_probe_device(struct device *dev) { - int len; - /* * uboot must fill the fsl,liodn for platform devices to be supported by * the iommu. */ if (!dev_is_pci(dev) && - !of_get_property(dev->of_node, "fsl,liodn", &len)) + !of_property_present(dev->of_node, "fsl,liodn")) return ERR_PTR(-ENODEV); return &pamu_iommu; diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c index 78d61da75257..e7a6a1611d19 100644 --- a/drivers/iommu/of_iommu.c +++ b/drivers/iommu/of_iommu.c @@ -214,7 +214,7 @@ void of_iommu_get_resv_regions(struct device *dev, struct list_head *list) * that represent reservations in the IOVA space, which are regions that should * not be mapped. */ - if (of_find_property(it.node, "reg", NULL)) { + if (of_property_present(it.node, "reg")) { err = of_address_to_resource(it.node, 0, &phys); if (err < 0) { dev_err(dev, "failed to parse memory region %pOF: %d\n", From fdc39b77db95e36f6b4d3c006a2642b9f47510c5 Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Wed, 28 Aug 2024 11:10:22 +0000 Subject: [PATCH 366/573] iommu/amd: Update event log pointer as soon as processing is complete Update event buffer head pointer once driver completes processing. So that IOMMU can write new log without waiting for driver to complete processing all event logs. Signed-off-by: Vasant Hegde Reviewed-by: Suravee Suthikulpanit Link: https://lore.kernel.org/r/20240828111029.5429-2-vasant.hegde@amd.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/iommu.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 87c5385ce3f2..8ada0b227ca1 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -825,10 +825,12 @@ static void iommu_poll_events(struct amd_iommu *iommu) while (head != tail) { iommu_print_event(iommu, iommu->evt_buf + head); + + /* Update head pointer of hardware ring-buffer */ head = (head + EVENT_ENTRY_SIZE) % EVT_BUFFER_SIZE; + writel(head, iommu->mmio_base + MMIO_EVT_HEAD_OFFSET); } - writel(head, iommu->mmio_base + MMIO_EVT_HEAD_OFFSET); } #ifdef CONFIG_IRQ_REMAP From 53f1fb0c46f0e3ebf13b76697b67782f65fc3f69 Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Wed, 28 Aug 2024 11:10:23 +0000 Subject: [PATCH 367/573] iommu/amd: Make amd_iommu_is_attach_deferred() static amd_iommu_is_attach_deferred() is a callback function called by iommu_ops. Make it as static. No functional changes intended. Signed-off-by: Vasant Hegde Reviewed-by: Suravee Suthikulpanit Link: https://lore.kernel.org/r/20240828111029.5429-3-vasant.hegde@amd.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu.h | 1 - drivers/iommu/amd/iommu.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h index 52e18b5f99fd..a9e207ec2bbb 100644 --- a/drivers/iommu/amd/amd_iommu.h +++ b/drivers/iommu/amd/amd_iommu.h @@ -180,7 +180,6 @@ static inline struct protection_domain *to_pdomain(struct iommu_domain *dom) } bool translation_pre_enabled(struct amd_iommu *iommu); -bool amd_iommu_is_attach_deferred(struct device *dev); int __init add_special_device(u8 type, u8 id, u32 *devid, bool cmd_line); #ifdef CONFIG_DMI diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 8ada0b227ca1..f90fd106906c 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -2782,7 +2782,7 @@ static void amd_iommu_get_resv_regions(struct device *dev, list_add_tail(®ion->list, head); } -bool amd_iommu_is_attach_deferred(struct device *dev) +static bool amd_iommu_is_attach_deferred(struct device *dev) { struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev); From 95eb6a05124f9a206f009a21c9a0a9f320859b6c Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Wed, 28 Aug 2024 11:10:24 +0000 Subject: [PATCH 368/573] iommu/amd: Remove unused DTE_GCR3_INDEX_* macros It was added in commit 52815b75682e ("iommu/amd: Add support for IOMMUv2 domain mode"), but never used it. Hence remove these unused macros. Signed-off-by: Vasant Hegde Reviewed-by: Suravee Suthikulpanit Link: https://lore.kernel.org/r/20240828111029.5429-4-vasant.hegde@amd.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu_types.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h index c9f9a598eb82..c7432296bb90 100644 --- a/drivers/iommu/amd/amd_iommu_types.h +++ b/drivers/iommu/amd/amd_iommu_types.h @@ -415,10 +415,6 @@ #define DTE_GCR3_VAL_B(x) (((x) >> 15) & 0x0ffffULL) #define DTE_GCR3_VAL_C(x) (((x) >> 31) & 0x1fffffULL) -#define DTE_GCR3_INDEX_A 0 -#define DTE_GCR3_INDEX_B 1 -#define DTE_GCR3_INDEX_C 1 - #define DTE_GCR3_SHIFT_A 58 #define DTE_GCR3_SHIFT_B 16 #define DTE_GCR3_SHIFT_C 43 From 293aa9ec694e633bff83ab93715a2684e15fe214 Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Wed, 28 Aug 2024 11:10:25 +0000 Subject: [PATCH 369/573] iommu/amd: Handle error path in amd_iommu_probe_device() Do not try to set max_pasids in error path as dev_data is not allocated. Fixes: a0c47f233e68 ("iommu/amd: Introduce iommu_dev_data.max_pasids") Signed-off-by: Vasant Hegde Reviewed-by: Suravee Suthikulpanit Link: https://lore.kernel.org/r/20240828111029.5429-5-vasant.hegde@amd.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/iommu.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index f90fd106906c..51b226b9e727 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -2187,11 +2187,12 @@ static struct iommu_device *amd_iommu_probe_device(struct device *dev) dev_err(dev, "Failed to initialize - trying to proceed anyway\n"); iommu_dev = ERR_PTR(ret); iommu_ignore_device(iommu, dev); - } else { - amd_iommu_set_pci_msi_domain(dev, iommu); - iommu_dev = &iommu->iommu; + goto out_err; } + amd_iommu_set_pci_msi_domain(dev, iommu); + iommu_dev = &iommu->iommu; + /* * If IOMMU and device supports PASID then it will contain max * supported PASIDs, else it will be zero. @@ -2203,6 +2204,7 @@ static struct iommu_device *amd_iommu_probe_device(struct device *dev) pci_max_pasids(to_pci_dev(dev))); } +out_err: iommu_completion_wait(iommu); return iommu_dev; From 845bd6ac43422671778120ec082d7ddcab035a1f Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Wed, 28 Aug 2024 11:10:26 +0000 Subject: [PATCH 370/573] iommu/amd: Make amd_iommu_dev_flush_pasid_all() static As its not used outside iommu.c. Also rename it as dev_flush_pasid_all(). No functional change intended. Signed-off-by: Vasant Hegde Reviewed-by: Suravee Suthikulpanit Link: https://lore.kernel.org/r/20240828111029.5429-6-vasant.hegde@amd.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu.h | 2 -- drivers/iommu/amd/iommu.c | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h index a9e207ec2bbb..d0a24ec3ada2 100644 --- a/drivers/iommu/amd/amd_iommu.h +++ b/drivers/iommu/amd/amd_iommu.h @@ -93,8 +93,6 @@ void amd_iommu_domain_flush_pages(struct protection_domain *domain, u64 address, size_t size); void amd_iommu_dev_flush_pasid_pages(struct iommu_dev_data *dev_data, ioasid_t pasid, u64 address, size_t size); -void amd_iommu_dev_flush_pasid_all(struct iommu_dev_data *dev_data, - ioasid_t pasid); #ifdef CONFIG_IRQ_REMAP int amd_iommu_create_irq_domain(struct amd_iommu *iommu); diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 51b226b9e727..ddd63c2b6594 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -1551,8 +1551,8 @@ void amd_iommu_dev_flush_pasid_pages(struct iommu_dev_data *dev_data, iommu_completion_wait(iommu); } -void amd_iommu_dev_flush_pasid_all(struct iommu_dev_data *dev_data, - ioasid_t pasid) +static void dev_flush_pasid_all(struct iommu_dev_data *dev_data, + ioasid_t pasid) { amd_iommu_dev_flush_pasid_pages(dev_data, 0, CMD_INV_IOMMU_ALL_PAGES_ADDRESS, pasid); @@ -1818,7 +1818,7 @@ static int update_gcr3(struct iommu_dev_data *dev_data, else *pte = 0; - amd_iommu_dev_flush_pasid_all(dev_data, pasid); + dev_flush_pasid_all(dev_data, pasid); return 0; } From 964877dc26232835d4465d9565399fe8ca4525e8 Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Wed, 28 Aug 2024 11:10:27 +0000 Subject: [PATCH 371/573] iommu/amd: Make amd_iommu_domain_flush_complete() static AMD driver uses amd_iommu_domain_flush_complete() function to make sure IOMMU processed invalidation commands before proceeding. Ideally this should be called from functions which updates DTE/invalidates caches. There is no need to call this function explicitly. This patches makes below changes : - Rename amd_iommu_domain_flush_complete() -> domain_flush_complete() and make it as static function. - Rearrage domain_flush_complete() to avoid forward declaration. - Update amd_iommu_update_and_flush_device_table() to call domain_flush_complete(). Signed-off-by: Vasant Hegde Reviewed-by: Suravee Suthikulpanit Link: https://lore.kernel.org/r/20240828111029.5429-7-vasant.hegde@amd.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu.h | 1 - drivers/iommu/amd/io_pgtable.c | 1 - drivers/iommu/amd/iommu.c | 37 +++++++++++++++++----------------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h index d0a24ec3ada2..94402b88789d 100644 --- a/drivers/iommu/amd/amd_iommu.h +++ b/drivers/iommu/amd/amd_iommu.h @@ -88,7 +88,6 @@ void amd_iommu_flush_all_caches(struct amd_iommu *iommu); void amd_iommu_update_and_flush_device_table(struct protection_domain *domain); void amd_iommu_domain_update(struct protection_domain *domain); void amd_iommu_dev_update_dte(struct iommu_dev_data *dev_data, bool set); -void amd_iommu_domain_flush_complete(struct protection_domain *domain); void amd_iommu_domain_flush_pages(struct protection_domain *domain, u64 address, size_t size); void amd_iommu_dev_flush_pasid_pages(struct iommu_dev_data *dev_data, diff --git a/drivers/iommu/amd/io_pgtable.c b/drivers/iommu/amd/io_pgtable.c index 1074ee25064d..bfbcec68efb9 100644 --- a/drivers/iommu/amd/io_pgtable.c +++ b/drivers/iommu/amd/io_pgtable.c @@ -175,7 +175,6 @@ static bool increase_address_space(struct protection_domain *domain, domain->iop.root = pte; domain->iop.mode += 1; amd_iommu_update_and_flush_device_table(domain); - amd_iommu_domain_flush_complete(domain); /* * Device Table needs to be updated and flushed before the new root can diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index ddd63c2b6594..9af084fa6dd4 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -1249,6 +1249,22 @@ out_unlock: return ret; } +static void domain_flush_complete(struct protection_domain *domain) +{ + int i; + + for (i = 0; i < amd_iommu_get_num_iommus(); ++i) { + if (domain && !domain->dev_iommu[i]) + continue; + + /* + * Devices of this domain are behind this IOMMU + * We need to wait for completion of all commands. + */ + iommu_completion_wait(amd_iommus[i]); + } +} + static int iommu_flush_dte(struct amd_iommu *iommu, u16 devid) { struct iommu_cmd cmd; @@ -1485,7 +1501,7 @@ void amd_iommu_domain_flush_pages(struct protection_domain *domain, __domain_flush_pages(domain, address, size); /* Wait until IOMMU TLB and all device IOTLB flushes are complete */ - amd_iommu_domain_flush_complete(domain); + domain_flush_complete(domain); return; } @@ -1525,7 +1541,7 @@ void amd_iommu_domain_flush_pages(struct protection_domain *domain, } /* Wait until IOMMU TLB and all device IOTLB flushes are complete */ - amd_iommu_domain_flush_complete(domain); + domain_flush_complete(domain); } /* Flush the whole IO/TLB for a given protection domain - including PDE */ @@ -1558,22 +1574,6 @@ static void dev_flush_pasid_all(struct iommu_dev_data *dev_data, CMD_INV_IOMMU_ALL_PAGES_ADDRESS, pasid); } -void amd_iommu_domain_flush_complete(struct protection_domain *domain) -{ - int i; - - for (i = 0; i < amd_iommu_get_num_iommus(); ++i) { - if (domain && !domain->dev_iommu[i]) - continue; - - /* - * Devices of this domain are behind this IOMMU - * We need to wait for completion of all commands. - */ - iommu_completion_wait(amd_iommus[i]); - } -} - /* Flush the not present cache if it exists */ static void domain_flush_np_cache(struct protection_domain *domain, dma_addr_t iova, size_t size) @@ -1615,6 +1615,7 @@ void amd_iommu_update_and_flush_device_table(struct protection_domain *domain) { update_device_table(domain); domain_flush_devices(domain); + domain_flush_complete(domain); } void amd_iommu_domain_update(struct protection_domain *domain) From a3303762eb80fe03479962470a81b9176fc24f8e Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Wed, 28 Aug 2024 11:10:28 +0000 Subject: [PATCH 372/573] iommu/amd: Rework amd_iommu_update_and_flush_device_table() Remove separate function to update and flush the device table as only amd_iommu_update_and_flush_device_table() calls these functions. No functional changes intended. Signed-off-by: Vasant Hegde Reviewed-by: Suravee Suthikulpanit Link: https://lore.kernel.org/r/20240828111029.5429-8-vasant.hegde@amd.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/iommu.c | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 9af084fa6dd4..5289be98377a 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -1591,15 +1591,7 @@ static void domain_flush_np_cache(struct protection_domain *domain, /* * This function flushes the DTEs for all devices in domain */ -static void domain_flush_devices(struct protection_domain *domain) -{ - struct iommu_dev_data *dev_data; - - list_for_each_entry(dev_data, &domain->dev_list, list) - device_flush_dte(dev_data); -} - -static void update_device_table(struct protection_domain *domain) +void amd_iommu_update_and_flush_device_table(struct protection_domain *domain) { struct iommu_dev_data *dev_data; @@ -1609,12 +1601,10 @@ static void update_device_table(struct protection_domain *domain) set_dte_entry(iommu, dev_data); clone_aliases(iommu, dev_data->dev); } -} -void amd_iommu_update_and_flush_device_table(struct protection_domain *domain) -{ - update_device_table(domain); - domain_flush_devices(domain); + list_for_each_entry(dev_data, &domain->dev_list, list) + device_flush_dte(dev_data); + domain_flush_complete(domain); } From 89ffb2c3c2a1d0bff5515fc53f93de86fb6753c0 Mon Sep 17 00:00:00 2001 From: Vasant Hegde Date: Wed, 28 Aug 2024 11:10:29 +0000 Subject: [PATCH 373/573] iommu/amd: Make amd_iommu_dev_update_dte() static As its used inside iommu.c only. Also rename function to dev_update_dte() as its static function. No functional changes intended. Signed-off-by: Vasant Hegde Reviewed-by: Suravee Suthikulpanit Link: https://lore.kernel.org/r/20240828111029.5429-9-vasant.hegde@amd.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu.h | 1 - drivers/iommu/amd/iommu.c | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h index 94402b88789d..705dd364afe3 100644 --- a/drivers/iommu/amd/amd_iommu.h +++ b/drivers/iommu/amd/amd_iommu.h @@ -87,7 +87,6 @@ int amd_iommu_complete_ppr(struct device *dev, u32 pasid, int status, int tag); void amd_iommu_flush_all_caches(struct amd_iommu *iommu); void amd_iommu_update_and_flush_device_table(struct protection_domain *domain); void amd_iommu_domain_update(struct protection_domain *domain); -void amd_iommu_dev_update_dte(struct iommu_dev_data *dev_data, bool set); void amd_iommu_domain_flush_pages(struct protection_domain *domain, u64 address, size_t size); void amd_iommu_dev_flush_pasid_pages(struct iommu_dev_data *dev_data, diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 5289be98377a..adb579030e68 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -1955,7 +1955,7 @@ static void clear_dte_entry(struct amd_iommu *iommu, u16 devid) } /* Update and flush DTE for the given device */ -void amd_iommu_dev_update_dte(struct iommu_dev_data *dev_data, bool set) +static void dev_update_dte(struct iommu_dev_data *dev_data, bool set) { struct amd_iommu *iommu = get_amd_iommu_from_dev(dev_data->dev); @@ -2055,7 +2055,7 @@ static void do_detach(struct iommu_dev_data *dev_data) struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data); /* Clear DTE and flush the entry */ - amd_iommu_dev_update_dte(dev_data, false); + dev_update_dte(dev_data, false); /* Flush IOTLB and wait for the flushes to finish */ amd_iommu_domain_flush_all(domain); @@ -2467,7 +2467,7 @@ static int blocked_domain_attach_device(struct iommu_domain *domain, /* Clear DTE and flush the entry */ spin_lock(&dev_data->lock); - amd_iommu_dev_update_dte(dev_data, false); + dev_update_dte(dev_data, false); spin_unlock(&dev_data->lock); return 0; @@ -2535,7 +2535,7 @@ static int amd_iommu_attach_device(struct iommu_domain *dom, } /* Update device table */ - amd_iommu_dev_update_dte(dev_data, true); + dev_update_dte(dev_data, true); return ret; } From 8d00b77a52ef4b2091696ca25753d0ab95e4d839 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:10 -0300 Subject: [PATCH 374/573] iommu/amd: Move allocation of the top table into v1_alloc_pgtable All the page table memory should be allocated/free within the io_pgtable struct. The v2 path is already doing this, make it consistent. It is hard to see but the free of the root in protection_domain_free() is a NOP on the success path because v1_free_pgtable() does amd_iommu_domain_clr_pt_root(). The root memory is already freed because free_sub_pt() put it on the freelist. The free path in protection_domain_free() is only used during error unwind of protection_domain_alloc(). Reviewed-by: Vasant Hegde Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/1-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/io_pgtable.c | 8 ++++++-- drivers/iommu/amd/iommu.c | 21 ++------------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/drivers/iommu/amd/io_pgtable.c b/drivers/iommu/amd/io_pgtable.c index bfbcec68efb9..03a3b09f0512 100644 --- a/drivers/iommu/amd/io_pgtable.c +++ b/drivers/iommu/amd/io_pgtable.c @@ -573,20 +573,24 @@ static void v1_free_pgtable(struct io_pgtable *iop) pgtable->mode > PAGE_MODE_6_LEVEL); free_sub_pt(pgtable->root, pgtable->mode, &freelist); + iommu_put_pages_list(&freelist); /* Update data structure */ amd_iommu_domain_clr_pt_root(dom); /* Make changes visible to IOMMUs */ amd_iommu_domain_update(dom); - - iommu_put_pages_list(&freelist); } static struct io_pgtable *v1_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie) { struct amd_io_pgtable *pgtable = io_pgtable_cfg_to_data(cfg); + pgtable->root = iommu_alloc_page(GFP_KERNEL); + if (!pgtable->root) + return NULL; + pgtable->mode = PAGE_MODE_3_LEVEL; + cfg->pgsize_bitmap = AMD_IOMMU_PGSIZES; cfg->ias = IOMMU_IN_ADDR_BIT_SIZE; cfg->oas = IOMMU_OUT_ADDR_BIT_SIZE; diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index adb579030e68..70213f94e24c 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -52,8 +52,6 @@ #define HT_RANGE_START (0xfd00000000ULL) #define HT_RANGE_END (0xffffffffffULL) -#define DEFAULT_PGTABLE_LEVEL PAGE_MODE_3_LEVEL - static DEFINE_SPINLOCK(pd_bitmap_lock); LIST_HEAD(ioapic_map); @@ -2260,30 +2258,15 @@ void protection_domain_free(struct protection_domain *domain) if (domain->iop.pgtbl_cfg.tlb) free_io_pgtable_ops(&domain->iop.iop.ops); - if (domain->iop.root) - iommu_free_page(domain->iop.root); - if (domain->id) domain_id_free(domain->id); kfree(domain); } -static int protection_domain_init_v1(struct protection_domain *domain, int mode) +static int protection_domain_init_v1(struct protection_domain *domain) { - u64 *pt_root = NULL; - - BUG_ON(mode < PAGE_MODE_NONE || mode > PAGE_MODE_6_LEVEL); - - if (mode != PAGE_MODE_NONE) { - pt_root = iommu_alloc_page(GFP_KERNEL); - if (!pt_root) - return -ENOMEM; - } - domain->pd_mode = PD_MODE_V1; - amd_iommu_domain_set_pgtable(domain, pt_root, mode); - return 0; } @@ -2336,7 +2319,7 @@ struct protection_domain *protection_domain_alloc(unsigned int type) switch (pgtable) { case AMD_IOMMU_V1: - ret = protection_domain_init_v1(domain, DEFAULT_PGTABLE_LEVEL); + ret = protection_domain_init_v1(domain); break; case AMD_IOMMU_V2: ret = protection_domain_init_v2(domain); From b0a6c883bcd42eeb0850135e529b34b64d57673c Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:11 -0300 Subject: [PATCH 375/573] iommu/amd: Allocate the page table root using GFP_KERNEL Domain allocation is always done under a sleepable context, the v1 path and other drivers use GFP_KERNEL already. Fix the v2 path to also use GFP_KERNEL. Fixes: 0d571dcbe7c6 ("iommu/amd: Allocate page table using numa locality info") Reviewed-by: Vasant Hegde Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/2-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/io_pgtable_v2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/amd/io_pgtable_v2.c b/drivers/iommu/amd/io_pgtable_v2.c index 664e91c88748..6088822180e1 100644 --- a/drivers/iommu/amd/io_pgtable_v2.c +++ b/drivers/iommu/amd/io_pgtable_v2.c @@ -362,7 +362,7 @@ static struct io_pgtable *v2_alloc_pgtable(struct io_pgtable_cfg *cfg, void *coo struct protection_domain *pdom = (struct protection_domain *)cookie; int ias = IOMMU_IN_ADDR_BIT_SIZE; - pgtable->pgd = iommu_alloc_page_node(pdom->nid, GFP_ATOMIC); + pgtable->pgd = iommu_alloc_page_node(pdom->nid, GFP_KERNEL); if (!pgtable->pgd) return NULL; From 7a41dcb52f9de6079621fc31c3b84c7fc290934b Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:12 -0300 Subject: [PATCH 376/573] iommu/amd: Set the pgsize_bitmap correctly When using io_pgtable the correct pgsize_bitmap is stored in the cfg, both v1_alloc_pgtable() and v2_alloc_pgtable() set it correctly. This fixes a bug where the v2 pgtable had the wrong pgsize as protection_domain_init_v2() would set it and then do_iommu_domain_alloc() immediately resets it. Remove the confusing ops.pgsize_bitmap since that is not used if the driver sets domain.pgsize_bitmap. Fixes: 134288158a41 ("iommu/amd: Add domain_alloc_user based domain allocation") Reviewed-by: Vasant Hegde Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/3-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/iommu.c | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 70213f94e24c..c90a53a0385d 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -2264,26 +2264,11 @@ void protection_domain_free(struct protection_domain *domain) kfree(domain); } -static int protection_domain_init_v1(struct protection_domain *domain) -{ - domain->pd_mode = PD_MODE_V1; - return 0; -} - -static int protection_domain_init_v2(struct protection_domain *pdom) -{ - pdom->pd_mode = PD_MODE_V2; - pdom->domain.pgsize_bitmap = AMD_IOMMU_PGSIZES_V2; - - return 0; -} - struct protection_domain *protection_domain_alloc(unsigned int type) { struct io_pgtable_ops *pgtbl_ops; struct protection_domain *domain; int pgtable; - int ret; domain = kzalloc(sizeof(*domain), GFP_KERNEL); if (!domain) @@ -2319,18 +2304,14 @@ struct protection_domain *protection_domain_alloc(unsigned int type) switch (pgtable) { case AMD_IOMMU_V1: - ret = protection_domain_init_v1(domain); + domain->pd_mode = PD_MODE_V1; break; case AMD_IOMMU_V2: - ret = protection_domain_init_v2(domain); + domain->pd_mode = PD_MODE_V2; break; default: - ret = -EINVAL; - break; - } - - if (ret) goto out_err; + } pgtbl_ops = alloc_io_pgtable_ops(pgtable, &domain->iop.pgtbl_cfg, domain); if (!pgtbl_ops) @@ -2383,10 +2364,10 @@ static struct iommu_domain *do_iommu_domain_alloc(unsigned int type, domain->domain.geometry.aperture_start = 0; domain->domain.geometry.aperture_end = dma_max_address(); domain->domain.geometry.force_aperture = true; + domain->domain.pgsize_bitmap = domain->iop.iop.cfg.pgsize_bitmap; if (iommu) { domain->domain.type = type; - domain->domain.pgsize_bitmap = iommu->iommu.ops->pgsize_bitmap; domain->domain.ops = iommu->iommu.ops->default_domain_ops; if (dirty_tracking) @@ -2869,7 +2850,6 @@ const struct iommu_ops amd_iommu_ops = { .device_group = amd_iommu_device_group, .get_resv_regions = amd_iommu_get_resv_regions, .is_attach_deferred = amd_iommu_is_attach_deferred, - .pgsize_bitmap = AMD_IOMMU_PGSIZES, .def_domain_type = amd_iommu_def_domain_type, .dev_enable_feat = amd_iommu_dev_enable_feature, .dev_disable_feat = amd_iommu_dev_disable_feature, From 322d889ae7d39f8538a6deac35869aa3be1855bd Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:13 -0300 Subject: [PATCH 377/573] iommu/amd: Remove amd_iommu_domain_update() from page table freeing It is a serious bug if the domain is still mapped to any DTEs when it is freed as we immediately start freeing page table memory, so any remaining HW touch will UAF. If it is not mapped then dev_list is empty and amd_iommu_domain_update() does nothing. Remove it and add a WARN_ON() to catch this class of bug. Reviewed-by: Vasant Hegde Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/4-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/io_pgtable.c | 3 --- drivers/iommu/amd/iommu.c | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/amd/io_pgtable.c b/drivers/iommu/amd/io_pgtable.c index 03a3b09f0512..0743b698128d 100644 --- a/drivers/iommu/amd/io_pgtable.c +++ b/drivers/iommu/amd/io_pgtable.c @@ -577,9 +577,6 @@ static void v1_free_pgtable(struct io_pgtable *iop) /* Update data structure */ amd_iommu_domain_clr_pt_root(dom); - - /* Make changes visible to IOMMUs */ - amd_iommu_domain_update(dom); } static struct io_pgtable *v1_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index c90a53a0385d..05ae44f13ec8 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -2255,6 +2255,8 @@ void protection_domain_free(struct protection_domain *domain) if (!domain) return; + WARN_ON(!list_empty(&domain->dev_list)); + if (domain->iop.pgtbl_cfg.tlb) free_io_pgtable_ops(&domain->iop.iop.ops); From 1ed2d21d471caf2e4351c2e8bb14143bc8062092 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:14 -0300 Subject: [PATCH 378/573] iommu/amd: Remove the amd_iommu_domain_set_pt_root() and related Looks like many refactorings here have left this confused. There is only one storage of the root/mode, it is in the iop struct. increase_address_space() calls amd_iommu_domain_set_pgtable() with values that it already stored in iop a few lines above. amd_iommu_domain_clr_pt_root() is zero'ing memory we are about to free. It used to protect against a double free of root, but that is gone now. Remove amd_iommu_domain_set_pgtable(), amd_iommu_domain_set_pt_root(), amd_iommu_domain_clr_pt_root() as they are all pointless. Reviewed-by: Vasant Hegde Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/5-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu.h | 13 ------------- drivers/iommu/amd/io_pgtable.c | 24 ------------------------ 2 files changed, 37 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h index 705dd364afe3..8de6d6091454 100644 --- a/drivers/iommu/amd/amd_iommu.h +++ b/drivers/iommu/amd/amd_iommu.h @@ -134,19 +134,6 @@ static inline void *iommu_phys_to_virt(unsigned long paddr) return phys_to_virt(__sme_clr(paddr)); } -static inline -void amd_iommu_domain_set_pt_root(struct protection_domain *domain, u64 root) -{ - domain->iop.root = (u64 *)(root & PAGE_MASK); - domain->iop.mode = root & 7; /* lowest 3 bits encode pgtable mode */ -} - -static inline -void amd_iommu_domain_clr_pt_root(struct protection_domain *domain) -{ - amd_iommu_domain_set_pt_root(domain, 0); -} - static inline int get_pci_sbdf_id(struct pci_dev *pdev) { int seg = pci_domain_nr(pdev->bus); diff --git a/drivers/iommu/amd/io_pgtable.c b/drivers/iommu/amd/io_pgtable.c index 0743b698128d..e0abcf38c314 100644 --- a/drivers/iommu/amd/io_pgtable.c +++ b/drivers/iommu/amd/io_pgtable.c @@ -132,18 +132,6 @@ static void free_sub_pt(u64 *root, int mode, struct list_head *freelist) } } -void amd_iommu_domain_set_pgtable(struct protection_domain *domain, - u64 *root, int mode) -{ - u64 pt_root; - - /* lowest 3 bits encode pgtable mode */ - pt_root = mode & 7; - pt_root |= (u64)root; - - amd_iommu_domain_set_pt_root(domain, pt_root); -} - /* * This function is used to add another level to an IO page table. Adding * another level increases the size of the address space by 9 bits to a size up @@ -176,12 +164,6 @@ static bool increase_address_space(struct protection_domain *domain, domain->iop.mode += 1; amd_iommu_update_and_flush_device_table(domain); - /* - * Device Table needs to be updated and flushed before the new root can - * be published. - */ - amd_iommu_domain_set_pgtable(domain, pte, domain->iop.mode); - pte = NULL; ret = true; @@ -560,23 +542,17 @@ static int iommu_v1_read_and_clear_dirty(struct io_pgtable_ops *ops, static void v1_free_pgtable(struct io_pgtable *iop) { struct amd_io_pgtable *pgtable = container_of(iop, struct amd_io_pgtable, iop); - struct protection_domain *dom; LIST_HEAD(freelist); if (pgtable->mode == PAGE_MODE_NONE) return; - dom = container_of(pgtable, struct protection_domain, iop); - /* Page-table is not visible to IOMMU anymore, so free it */ BUG_ON(pgtable->mode < PAGE_MODE_NONE || pgtable->mode > PAGE_MODE_6_LEVEL); free_sub_pt(pgtable->root, pgtable->mode, &freelist); iommu_put_pages_list(&freelist); - - /* Update data structure */ - amd_iommu_domain_clr_pt_root(dom); } static struct io_pgtable *v1_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie) From 670b57796c5dc1ca58912132cad914cf4b3c0cdd Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:15 -0300 Subject: [PATCH 379/573] iommu/amd: Rename struct amd_io_pgtable iopt to pgtbl There is struct protection_domain iopt and struct amd_io_pgtable iopt. Next patches are going to want to write domain.iopt.iopt.xx which is quite unnatural to read. Give one of them a different name, amd_io_pgtable has fewer references so call it pgtbl, to match pgtbl_cfg, instead. Suggested-by: Alejandro Jimenez Reviewed-by: Vasant Hegde Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/6-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu_types.h | 4 ++-- drivers/iommu/amd/io_pgtable.c | 12 ++++++------ drivers/iommu/amd/io_pgtable_v2.c | 14 +++++++------- drivers/iommu/amd/iommu.c | 14 +++++++------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h index c7432296bb90..ce7825b4d631 100644 --- a/drivers/iommu/amd/amd_iommu_types.h +++ b/drivers/iommu/amd/amd_iommu_types.h @@ -519,7 +519,7 @@ struct amd_irte_ops; #define AMD_IOMMU_FLAG_TRANS_PRE_ENABLED (1 << 0) #define io_pgtable_to_data(x) \ - container_of((x), struct amd_io_pgtable, iop) + container_of((x), struct amd_io_pgtable, pgtbl) #define io_pgtable_ops_to_data(x) \ io_pgtable_to_data(io_pgtable_ops_to_pgtable(x)) @@ -540,7 +540,7 @@ struct gcr3_tbl_info { struct amd_io_pgtable { struct io_pgtable_cfg pgtbl_cfg; - struct io_pgtable iop; + struct io_pgtable pgtbl; int mode; u64 *root; u64 *pgd; /* v2 pgtable pgd pointer */ diff --git a/drivers/iommu/amd/io_pgtable.c b/drivers/iommu/amd/io_pgtable.c index e0abcf38c314..53de1146928e 100644 --- a/drivers/iommu/amd/io_pgtable.c +++ b/drivers/iommu/amd/io_pgtable.c @@ -541,7 +541,7 @@ static int iommu_v1_read_and_clear_dirty(struct io_pgtable_ops *ops, */ static void v1_free_pgtable(struct io_pgtable *iop) { - struct amd_io_pgtable *pgtable = container_of(iop, struct amd_io_pgtable, iop); + struct amd_io_pgtable *pgtable = container_of(iop, struct amd_io_pgtable, pgtbl); LIST_HEAD(freelist); if (pgtable->mode == PAGE_MODE_NONE) @@ -569,12 +569,12 @@ static struct io_pgtable *v1_alloc_pgtable(struct io_pgtable_cfg *cfg, void *coo cfg->oas = IOMMU_OUT_ADDR_BIT_SIZE; cfg->tlb = &v1_flush_ops; - pgtable->iop.ops.map_pages = iommu_v1_map_pages; - pgtable->iop.ops.unmap_pages = iommu_v1_unmap_pages; - pgtable->iop.ops.iova_to_phys = iommu_v1_iova_to_phys; - pgtable->iop.ops.read_and_clear_dirty = iommu_v1_read_and_clear_dirty; + pgtable->pgtbl.ops.map_pages = iommu_v1_map_pages; + pgtable->pgtbl.ops.unmap_pages = iommu_v1_unmap_pages; + pgtable->pgtbl.ops.iova_to_phys = iommu_v1_iova_to_phys; + pgtable->pgtbl.ops.read_and_clear_dirty = iommu_v1_read_and_clear_dirty; - return &pgtable->iop; + return &pgtable->pgtbl; } struct io_pgtable_init_fns io_pgtable_amd_iommu_v1_init_fns = { diff --git a/drivers/iommu/amd/io_pgtable_v2.c b/drivers/iommu/amd/io_pgtable_v2.c index 6088822180e1..45a6bc332639 100644 --- a/drivers/iommu/amd/io_pgtable_v2.c +++ b/drivers/iommu/amd/io_pgtable_v2.c @@ -234,7 +234,7 @@ static int iommu_v2_map_pages(struct io_pgtable_ops *ops, unsigned long iova, int prot, gfp_t gfp, size_t *mapped) { struct protection_domain *pdom = io_pgtable_ops_to_domain(ops); - struct io_pgtable_cfg *cfg = &pdom->iop.iop.cfg; + struct io_pgtable_cfg *cfg = &pdom->iop.pgtbl.cfg; u64 *pte; unsigned long map_size; unsigned long mapped_size = 0; @@ -281,7 +281,7 @@ static unsigned long iommu_v2_unmap_pages(struct io_pgtable_ops *ops, struct iommu_iotlb_gather *gather) { struct amd_io_pgtable *pgtable = io_pgtable_ops_to_data(ops); - struct io_pgtable_cfg *cfg = &pgtable->iop.cfg; + struct io_pgtable_cfg *cfg = &pgtable->pgtbl.cfg; unsigned long unmap_size; unsigned long unmapped = 0; size_t size = pgcount << __ffs(pgsize); @@ -346,7 +346,7 @@ static const struct iommu_flush_ops v2_flush_ops = { static void v2_free_pgtable(struct io_pgtable *iop) { - struct amd_io_pgtable *pgtable = container_of(iop, struct amd_io_pgtable, iop); + struct amd_io_pgtable *pgtable = container_of(iop, struct amd_io_pgtable, pgtbl); if (!pgtable || !pgtable->pgd) return; @@ -369,16 +369,16 @@ static struct io_pgtable *v2_alloc_pgtable(struct io_pgtable_cfg *cfg, void *coo if (get_pgtable_level() == PAGE_MODE_5_LEVEL) ias = 57; - pgtable->iop.ops.map_pages = iommu_v2_map_pages; - pgtable->iop.ops.unmap_pages = iommu_v2_unmap_pages; - pgtable->iop.ops.iova_to_phys = iommu_v2_iova_to_phys; + pgtable->pgtbl.ops.map_pages = iommu_v2_map_pages; + pgtable->pgtbl.ops.unmap_pages = iommu_v2_unmap_pages; + pgtable->pgtbl.ops.iova_to_phys = iommu_v2_iova_to_phys; cfg->pgsize_bitmap = AMD_IOMMU_PGSIZES_V2, cfg->ias = ias, cfg->oas = IOMMU_OUT_ADDR_BIT_SIZE, cfg->tlb = &v2_flush_ops; - return &pgtable->iop; + return &pgtable->pgtbl; } struct io_pgtable_init_fns io_pgtable_amd_iommu_v2_init_fns = { diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 05ae44f13ec8..14030adba3d0 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -2258,7 +2258,7 @@ void protection_domain_free(struct protection_domain *domain) WARN_ON(!list_empty(&domain->dev_list)); if (domain->iop.pgtbl_cfg.tlb) - free_io_pgtable_ops(&domain->iop.iop.ops); + free_io_pgtable_ops(&domain->iop.pgtbl.ops); if (domain->id) domain_id_free(domain->id); @@ -2366,7 +2366,7 @@ static struct iommu_domain *do_iommu_domain_alloc(unsigned int type, domain->domain.geometry.aperture_start = 0; domain->domain.geometry.aperture_end = dma_max_address(); domain->domain.geometry.force_aperture = true; - domain->domain.pgsize_bitmap = domain->iop.iop.cfg.pgsize_bitmap; + domain->domain.pgsize_bitmap = domain->iop.pgtbl.cfg.pgsize_bitmap; if (iommu) { domain->domain.type = type; @@ -2510,7 +2510,7 @@ static int amd_iommu_iotlb_sync_map(struct iommu_domain *dom, unsigned long iova, size_t size) { struct protection_domain *domain = to_pdomain(dom); - struct io_pgtable_ops *ops = &domain->iop.iop.ops; + struct io_pgtable_ops *ops = &domain->iop.pgtbl.ops; if (ops->map_pages) domain_flush_np_cache(domain, iova, size); @@ -2522,7 +2522,7 @@ static int amd_iommu_map_pages(struct iommu_domain *dom, unsigned long iova, int iommu_prot, gfp_t gfp, size_t *mapped) { struct protection_domain *domain = to_pdomain(dom); - struct io_pgtable_ops *ops = &domain->iop.iop.ops; + struct io_pgtable_ops *ops = &domain->iop.pgtbl.ops; int prot = 0; int ret = -EINVAL; @@ -2569,7 +2569,7 @@ static size_t amd_iommu_unmap_pages(struct iommu_domain *dom, unsigned long iova struct iommu_iotlb_gather *gather) { struct protection_domain *domain = to_pdomain(dom); - struct io_pgtable_ops *ops = &domain->iop.iop.ops; + struct io_pgtable_ops *ops = &domain->iop.pgtbl.ops; size_t r; if ((domain->pd_mode == PD_MODE_V1) && @@ -2588,7 +2588,7 @@ static phys_addr_t amd_iommu_iova_to_phys(struct iommu_domain *dom, dma_addr_t iova) { struct protection_domain *domain = to_pdomain(dom); - struct io_pgtable_ops *ops = &domain->iop.iop.ops; + struct io_pgtable_ops *ops = &domain->iop.pgtbl.ops; return ops->iova_to_phys(ops, iova); } @@ -2666,7 +2666,7 @@ static int amd_iommu_read_and_clear_dirty(struct iommu_domain *domain, struct iommu_dirty_bitmap *dirty) { struct protection_domain *pdomain = to_pdomain(domain); - struct io_pgtable_ops *ops = &pdomain->iop.iop.ops; + struct io_pgtable_ops *ops = &pdomain->iop.pgtbl.ops; unsigned long lflags; if (!ops || !ops->read_and_clear_dirty) From 977fc27ca7f8a83b67ccd91264ac56b0fb996f51 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:16 -0300 Subject: [PATCH 380/573] iommu/amd: Remove amd_io_pgtable::pgtbl_cfg This struct is already in iop.cfg, we don't need two. AMD is using this API sort of wrong, the cfg is supposed to be passed in and then the allocation function will allocate ops memory and copy the passed config into the new memory. Keep it kind of wrong and pass in the cfg memory that is already part of the pagetable struct. Reviewed-by: Vasant Hegde Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/7-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu_types.h | 3 +-- drivers/iommu/amd/iommu.c | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h index ce7825b4d631..7aa4f1983e40 100644 --- a/drivers/iommu/amd/amd_iommu_types.h +++ b/drivers/iommu/amd/amd_iommu_types.h @@ -529,7 +529,7 @@ struct amd_irte_ops; struct protection_domain, iop) #define io_pgtable_cfg_to_data(x) \ - container_of((x), struct amd_io_pgtable, pgtbl_cfg) + container_of((x), struct amd_io_pgtable, pgtbl.cfg) struct gcr3_tbl_info { u64 *gcr3_tbl; /* Guest CR3 table */ @@ -539,7 +539,6 @@ struct gcr3_tbl_info { }; struct amd_io_pgtable { - struct io_pgtable_cfg pgtbl_cfg; struct io_pgtable pgtbl; int mode; u64 *root; diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 14030adba3d0..212cc2801274 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -2257,7 +2257,7 @@ void protection_domain_free(struct protection_domain *domain) WARN_ON(!list_empty(&domain->dev_list)); - if (domain->iop.pgtbl_cfg.tlb) + if (domain->iop.pgtbl.cfg.tlb) free_io_pgtable_ops(&domain->iop.pgtbl.ops); if (domain->id) @@ -2315,7 +2315,8 @@ struct protection_domain *protection_domain_alloc(unsigned int type) goto out_err; } - pgtbl_ops = alloc_io_pgtable_ops(pgtable, &domain->iop.pgtbl_cfg, domain); + pgtbl_ops = + alloc_io_pgtable_ops(pgtable, &domain->iop.pgtbl.cfg, domain); if (!pgtbl_ops) goto out_err; From 47f218d108950984b24af81f66356ceda380eb74 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:17 -0300 Subject: [PATCH 381/573] iommu/amd: Store the nid in io_pgtable_cfg instead of the domain We already have memory in the union here that is being wasted in AMD's case, use it to store the nid. Putting the nid here further isolates the io_pgtable code from the struct protection_domain. Fixup protection_domain_alloc so that the NID from the device is provided, at this point dev is never NULL for AMD so this will now allocate the first table pointer on the correct NUMA node. Signed-off-by: Jason Gunthorpe Reviewed-by: Vasant Hegde Link: https://lore.kernel.org/r/8-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu.h | 2 +- drivers/iommu/amd/amd_iommu_types.h | 1 - drivers/iommu/amd/io_pgtable.c | 8 +++++--- drivers/iommu/amd/io_pgtable_v2.c | 5 ++--- drivers/iommu/amd/iommu.c | 12 +++++++----- drivers/iommu/amd/pasid.c | 2 +- include/linux/io-pgtable.h | 4 ++++ 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h index 8de6d6091454..29e6e71f7f9a 100644 --- a/drivers/iommu/amd/amd_iommu.h +++ b/drivers/iommu/amd/amd_iommu.h @@ -45,7 +45,7 @@ extern enum io_pgtable_fmt amd_iommu_pgtable; extern int amd_iommu_gpt_level; /* Protection domain ops */ -struct protection_domain *protection_domain_alloc(unsigned int type); +struct protection_domain *protection_domain_alloc(unsigned int type, int nid); void protection_domain_free(struct protection_domain *domain); struct iommu_domain *amd_iommu_domain_alloc_sva(struct device *dev, struct mm_struct *mm); diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h index 7aa4f1983e40..30eb07acb8b1 100644 --- a/drivers/iommu/amd/amd_iommu_types.h +++ b/drivers/iommu/amd/amd_iommu_types.h @@ -571,7 +571,6 @@ struct protection_domain { struct amd_io_pgtable iop; spinlock_t lock; /* mostly used to lock the page table*/ u16 id; /* the domain id written to the device table */ - int nid; /* Node ID */ enum protection_domain_mode pd_mode; /* Track page table type */ bool dirty_tracking; /* dirty tracking is enabled in the domain */ unsigned dev_cnt; /* devices assigned to this domain */ diff --git a/drivers/iommu/amd/io_pgtable.c b/drivers/iommu/amd/io_pgtable.c index 53de1146928e..4492a5800b35 100644 --- a/drivers/iommu/amd/io_pgtable.c +++ b/drivers/iommu/amd/io_pgtable.c @@ -141,11 +141,12 @@ static bool increase_address_space(struct protection_domain *domain, unsigned long address, gfp_t gfp) { + struct io_pgtable_cfg *cfg = &domain->iop.pgtbl.cfg; unsigned long flags; bool ret = true; u64 *pte; - pte = iommu_alloc_page_node(domain->nid, gfp); + pte = iommu_alloc_page_node(cfg->amd.nid, gfp); if (!pte) return false; @@ -181,6 +182,7 @@ static u64 *alloc_pte(struct protection_domain *domain, gfp_t gfp, bool *updated) { + struct io_pgtable_cfg *cfg = &domain->iop.pgtbl.cfg; int level, end_lvl; u64 *pte, *page; @@ -232,7 +234,7 @@ static u64 *alloc_pte(struct protection_domain *domain, if (!IOMMU_PTE_PRESENT(__pte) || pte_level == PAGE_MODE_NONE) { - page = iommu_alloc_page_node(domain->nid, gfp); + page = iommu_alloc_page_node(cfg->amd.nid, gfp); if (!page) return NULL; @@ -559,7 +561,7 @@ static struct io_pgtable *v1_alloc_pgtable(struct io_pgtable_cfg *cfg, void *coo { struct amd_io_pgtable *pgtable = io_pgtable_cfg_to_data(cfg); - pgtable->root = iommu_alloc_page(GFP_KERNEL); + pgtable->root = iommu_alloc_page_node(cfg->amd.nid, GFP_KERNEL); if (!pgtable->root) return NULL; pgtable->mode = PAGE_MODE_3_LEVEL; diff --git a/drivers/iommu/amd/io_pgtable_v2.c b/drivers/iommu/amd/io_pgtable_v2.c index 45a6bc332639..1e3be8c5312b 100644 --- a/drivers/iommu/amd/io_pgtable_v2.c +++ b/drivers/iommu/amd/io_pgtable_v2.c @@ -251,7 +251,7 @@ static int iommu_v2_map_pages(struct io_pgtable_ops *ops, unsigned long iova, while (mapped_size < size) { map_size = get_alloc_page_size(pgsize); - pte = v2_alloc_pte(pdom->nid, pdom->iop.pgd, + pte = v2_alloc_pte(cfg->amd.nid, pdom->iop.pgd, iova, map_size, gfp, &updated); if (!pte) { ret = -EINVAL; @@ -359,10 +359,9 @@ static void v2_free_pgtable(struct io_pgtable *iop) static struct io_pgtable *v2_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie) { struct amd_io_pgtable *pgtable = io_pgtable_cfg_to_data(cfg); - struct protection_domain *pdom = (struct protection_domain *)cookie; int ias = IOMMU_IN_ADDR_BIT_SIZE; - pgtable->pgd = iommu_alloc_page_node(pdom->nid, GFP_KERNEL); + pgtable->pgd = iommu_alloc_page_node(cfg->amd.nid, GFP_KERNEL); if (!pgtable->pgd) return NULL; diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 212cc2801274..44231c3b6b71 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -2023,6 +2023,7 @@ static int do_attach(struct iommu_dev_data *dev_data, struct protection_domain *domain) { struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data); + struct io_pgtable_cfg *cfg = &domain->iop.pgtbl.cfg; int ret = 0; /* Update data structures */ @@ -2030,8 +2031,8 @@ static int do_attach(struct iommu_dev_data *dev_data, list_add(&dev_data->list, &domain->dev_list); /* Update NUMA Node ID */ - if (domain->nid == NUMA_NO_NODE) - domain->nid = dev_to_node(dev_data->dev); + if (cfg->amd.nid == NUMA_NO_NODE) + cfg->amd.nid = dev_to_node(dev_data->dev); /* Do reference counting */ domain->dev_iommu[iommu->index] += 1; @@ -2266,7 +2267,7 @@ void protection_domain_free(struct protection_domain *domain) kfree(domain); } -struct protection_domain *protection_domain_alloc(unsigned int type) +struct protection_domain *protection_domain_alloc(unsigned int type, int nid) { struct io_pgtable_ops *pgtbl_ops; struct protection_domain *domain; @@ -2283,7 +2284,7 @@ struct protection_domain *protection_domain_alloc(unsigned int type) spin_lock_init(&domain->lock); INIT_LIST_HEAD(&domain->dev_list); INIT_LIST_HEAD(&domain->dev_data_list); - domain->nid = NUMA_NO_NODE; + domain->iop.pgtbl.cfg.amd.nid = nid; switch (type) { /* No need to allocate io pgtable ops in passthrough mode */ @@ -2360,7 +2361,8 @@ static struct iommu_domain *do_iommu_domain_alloc(unsigned int type, if (dirty_tracking && !amd_iommu_hd_support(iommu)) return ERR_PTR(-EOPNOTSUPP); - domain = protection_domain_alloc(type); + domain = protection_domain_alloc(type, + dev ? dev_to_node(dev) : NUMA_NO_NODE); if (!domain) return ERR_PTR(-ENOMEM); diff --git a/drivers/iommu/amd/pasid.c b/drivers/iommu/amd/pasid.c index a68215f2b3e1..0657b9373be5 100644 --- a/drivers/iommu/amd/pasid.c +++ b/drivers/iommu/amd/pasid.c @@ -181,7 +181,7 @@ struct iommu_domain *amd_iommu_domain_alloc_sva(struct device *dev, struct protection_domain *pdom; int ret; - pdom = protection_domain_alloc(IOMMU_DOMAIN_SVA); + pdom = protection_domain_alloc(IOMMU_DOMAIN_SVA, dev_to_node(dev)); if (!pdom) return ERR_PTR(-ENOMEM); diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h index f9a81761bfce..b1ecfc3cd5bc 100644 --- a/include/linux/io-pgtable.h +++ b/include/linux/io-pgtable.h @@ -171,6 +171,10 @@ struct io_pgtable_cfg { u64 ttbr[4]; u32 n_ttbrs; } apple_dart_cfg; + + struct { + int nid; + } amd; }; }; From 9ac0b3380acdece01fa1b361687e3cd988831c55 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:18 -0300 Subject: [PATCH 382/573] iommu/amd: Narrow the use of struct protection_domain to invalidation The AMD io_pgtable stuff doesn't implement the tlb ops callbacks, instead it invokes the invalidation ops directly on the struct protection_domain. Narrow the use of struct protection_domain to only those few code paths. Make everything else properly use struct amd_io_pgtable through the call chains, which is the correct modular type for an io-pgtable module. Signed-off-by: Jason Gunthorpe Reviewed-by: Vasant Hegde Link: https://lore.kernel.org/r/9-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/io_pgtable.c | 33 +++++++++++++++++-------------- drivers/iommu/amd/io_pgtable_v2.c | 11 +++++++---- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/drivers/iommu/amd/io_pgtable.c b/drivers/iommu/amd/io_pgtable.c index 4492a5800b35..1cf3d580a551 100644 --- a/drivers/iommu/amd/io_pgtable.c +++ b/drivers/iommu/amd/io_pgtable.c @@ -137,11 +137,13 @@ static void free_sub_pt(u64 *root, int mode, struct list_head *freelist) * another level increases the size of the address space by 9 bits to a size up * to 64 bits. */ -static bool increase_address_space(struct protection_domain *domain, +static bool increase_address_space(struct amd_io_pgtable *pgtable, unsigned long address, gfp_t gfp) { - struct io_pgtable_cfg *cfg = &domain->iop.pgtbl.cfg; + struct io_pgtable_cfg *cfg = &pgtable->pgtbl.cfg; + struct protection_domain *domain = + container_of(pgtable, struct protection_domain, iop); unsigned long flags; bool ret = true; u64 *pte; @@ -152,17 +154,17 @@ static bool increase_address_space(struct protection_domain *domain, spin_lock_irqsave(&domain->lock, flags); - if (address <= PM_LEVEL_SIZE(domain->iop.mode)) + if (address <= PM_LEVEL_SIZE(pgtable->mode)) goto out; ret = false; - if (WARN_ON_ONCE(domain->iop.mode == PAGE_MODE_6_LEVEL)) + if (WARN_ON_ONCE(pgtable->mode == PAGE_MODE_6_LEVEL)) goto out; - *pte = PM_LEVEL_PDE(domain->iop.mode, iommu_virt_to_phys(domain->iop.root)); + *pte = PM_LEVEL_PDE(pgtable->mode, iommu_virt_to_phys(pgtable->root)); - domain->iop.root = pte; - domain->iop.mode += 1; + pgtable->root = pte; + pgtable->mode += 1; amd_iommu_update_and_flush_device_table(domain); pte = NULL; @@ -175,31 +177,31 @@ out: return ret; } -static u64 *alloc_pte(struct protection_domain *domain, +static u64 *alloc_pte(struct amd_io_pgtable *pgtable, unsigned long address, unsigned long page_size, u64 **pte_page, gfp_t gfp, bool *updated) { - struct io_pgtable_cfg *cfg = &domain->iop.pgtbl.cfg; + struct io_pgtable_cfg *cfg = &pgtable->pgtbl.cfg; int level, end_lvl; u64 *pte, *page; BUG_ON(!is_power_of_2(page_size)); - while (address > PM_LEVEL_SIZE(domain->iop.mode)) { + while (address > PM_LEVEL_SIZE(pgtable->mode)) { /* * Return an error if there is no memory to update the * page-table. */ - if (!increase_address_space(domain, address, gfp)) + if (!increase_address_space(pgtable, address, gfp)) return NULL; } - level = domain->iop.mode - 1; - pte = &domain->iop.root[PM_LEVEL_INDEX(level, address)]; + level = pgtable->mode - 1; + pte = &pgtable->root[PM_LEVEL_INDEX(level, address)]; address = PAGE_SIZE_ALIGN(address, page_size); end_lvl = PAGE_SIZE_LEVEL(page_size); @@ -348,7 +350,7 @@ static int iommu_v1_map_pages(struct io_pgtable_ops *ops, unsigned long iova, phys_addr_t paddr, size_t pgsize, size_t pgcount, int prot, gfp_t gfp, size_t *mapped) { - struct protection_domain *dom = io_pgtable_ops_to_domain(ops); + struct amd_io_pgtable *pgtable = io_pgtable_ops_to_data(ops); LIST_HEAD(freelist); bool updated = false; u64 __pte, *pte; @@ -365,7 +367,7 @@ static int iommu_v1_map_pages(struct io_pgtable_ops *ops, unsigned long iova, while (pgcount > 0) { count = PAGE_SIZE_PTE_COUNT(pgsize); - pte = alloc_pte(dom, iova, pgsize, NULL, gfp, &updated); + pte = alloc_pte(pgtable, iova, pgsize, NULL, gfp, &updated); ret = -ENOMEM; if (!pte) @@ -402,6 +404,7 @@ static int iommu_v1_map_pages(struct io_pgtable_ops *ops, unsigned long iova, out: if (updated) { + struct protection_domain *dom = io_pgtable_ops_to_domain(ops); unsigned long flags; spin_lock_irqsave(&dom->lock, flags); diff --git a/drivers/iommu/amd/io_pgtable_v2.c b/drivers/iommu/amd/io_pgtable_v2.c index 1e3be8c5312b..ed2c1faae6d5 100644 --- a/drivers/iommu/amd/io_pgtable_v2.c +++ b/drivers/iommu/amd/io_pgtable_v2.c @@ -233,8 +233,8 @@ static int iommu_v2_map_pages(struct io_pgtable_ops *ops, unsigned long iova, phys_addr_t paddr, size_t pgsize, size_t pgcount, int prot, gfp_t gfp, size_t *mapped) { - struct protection_domain *pdom = io_pgtable_ops_to_domain(ops); - struct io_pgtable_cfg *cfg = &pdom->iop.pgtbl.cfg; + struct amd_io_pgtable *pgtable = io_pgtable_ops_to_data(ops); + struct io_pgtable_cfg *cfg = &pgtable->pgtbl.cfg; u64 *pte; unsigned long map_size; unsigned long mapped_size = 0; @@ -251,7 +251,7 @@ static int iommu_v2_map_pages(struct io_pgtable_ops *ops, unsigned long iova, while (mapped_size < size) { map_size = get_alloc_page_size(pgsize); - pte = v2_alloc_pte(cfg->amd.nid, pdom->iop.pgd, + pte = v2_alloc_pte(cfg->amd.nid, pgtable->pgd, iova, map_size, gfp, &updated); if (!pte) { ret = -EINVAL; @@ -266,8 +266,11 @@ static int iommu_v2_map_pages(struct io_pgtable_ops *ops, unsigned long iova, } out: - if (updated) + if (updated) { + struct protection_domain *pdom = io_pgtable_ops_to_domain(ops); + amd_iommu_domain_flush_pages(pdom, o_iova, size); + } if (mapped) *mapped += mapped_size; From 485534bfccb23e1c3e5915aca9acce1ecdc07a3f Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:19 -0300 Subject: [PATCH 383/573] iommu/amd: Remove conditions from domain free paths Don't use tlb as some flag to indicate if protection_domain_alloc() completed. Have protection_domain_alloc() unwind itself in the normal kernel style and require protection_domain_free() only be called on successful results of protection_domain_alloc(). Also, the amd_iommu_domain_free() op is never called by the core code with a NULL argument, so remove all the NULL tests as well. Reviewed-by: Vasant Hegde Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/10-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/iommu.c | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 44231c3b6b71..2876ac42ae2d 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -2253,17 +2253,9 @@ static void cleanup_domain(struct protection_domain *domain) void protection_domain_free(struct protection_domain *domain) { - if (!domain) - return; - WARN_ON(!list_empty(&domain->dev_list)); - - if (domain->iop.pgtbl.cfg.tlb) - free_io_pgtable_ops(&domain->iop.pgtbl.ops); - - if (domain->id) - domain_id_free(domain->id); - + free_io_pgtable_ops(&domain->iop.pgtbl.ops); + domain_id_free(domain->id); kfree(domain); } @@ -2279,7 +2271,7 @@ struct protection_domain *protection_domain_alloc(unsigned int type, int nid) domain->id = domain_id_alloc(); if (!domain->id) - goto out_err; + goto err_free; spin_lock_init(&domain->lock); INIT_LIST_HEAD(&domain->dev_list); @@ -2302,7 +2294,7 @@ struct protection_domain *protection_domain_alloc(unsigned int type, int nid) pgtable = AMD_IOMMU_V1; break; default: - goto out_err; + goto err_id; } switch (pgtable) { @@ -2313,17 +2305,19 @@ struct protection_domain *protection_domain_alloc(unsigned int type, int nid) domain->pd_mode = PD_MODE_V2; break; default: - goto out_err; + goto err_id; } pgtbl_ops = alloc_io_pgtable_ops(pgtable, &domain->iop.pgtbl.cfg, domain); if (!pgtbl_ops) - goto out_err; + goto err_id; return domain; -out_err: - protection_domain_free(domain); +err_id: + domain_id_free(domain->id); +err_free: + kfree(domain); return NULL; } @@ -2412,9 +2406,6 @@ void amd_iommu_domain_free(struct iommu_domain *dom) struct protection_domain *domain; unsigned long flags; - if (!dom) - return; - domain = to_pdomain(dom); spin_lock_irqsave(&domain->lock, flags); From a06dcb6b7897152e4dafdc9d4d84e9a35d0ae94a Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:20 -0300 Subject: [PATCH 384/573] iommu/amd: Fix typo of , instead of ; Generates the same code, but is not the expected C style. Fixes: aaac38f61487 ("iommu/amd: Initial support for AMD IOMMU v2 page table") Reviewed-by: Vasant Hegde Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/11-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/io_pgtable_v2.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/amd/io_pgtable_v2.c b/drivers/iommu/amd/io_pgtable_v2.c index ed2c1faae6d5..910fe1879f3e 100644 --- a/drivers/iommu/amd/io_pgtable_v2.c +++ b/drivers/iommu/amd/io_pgtable_v2.c @@ -375,9 +375,9 @@ static struct io_pgtable *v2_alloc_pgtable(struct io_pgtable_cfg *cfg, void *coo pgtable->pgtbl.ops.unmap_pages = iommu_v2_unmap_pages; pgtable->pgtbl.ops.iova_to_phys = iommu_v2_iova_to_phys; - cfg->pgsize_bitmap = AMD_IOMMU_PGSIZES_V2, - cfg->ias = ias, - cfg->oas = IOMMU_OUT_ADDR_BIT_SIZE, + cfg->pgsize_bitmap = AMD_IOMMU_PGSIZES_V2; + cfg->ias = ias; + cfg->oas = IOMMU_OUT_ADDR_BIT_SIZE; cfg->tlb = &v2_flush_ops; return &pgtable->pgtbl; From c435209f7203d90676e9eeae6c1b2d375fbf0304 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:21 -0300 Subject: [PATCH 385/573] iommu/amd: Remove the confusing dummy iommu_flush_ops tlb ops The iommu driver is supposed to provide these ops to its io_pgtable implementation so that it can hook the invalidations and do the right thing. They are called by wrapper functions like io_pgtable_tlb_add_page() etc, which the AMD code never calls. Instead it directly calls the AMD IOMMU invalidation functions by casting to the struct protection_domain. Remove it all. Reviewed-by: Vasant Hegde Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/12-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/io_pgtable.c | 22 ---------------------- drivers/iommu/amd/io_pgtable_v2.c | 22 ---------------------- 2 files changed, 44 deletions(-) diff --git a/drivers/iommu/amd/io_pgtable.c b/drivers/iommu/amd/io_pgtable.c index 1cf3d580a551..14f62c420e4a 100644 --- a/drivers/iommu/amd/io_pgtable.c +++ b/drivers/iommu/amd/io_pgtable.c @@ -24,27 +24,6 @@ #include "amd_iommu.h" #include "../iommu-pages.h" -static void v1_tlb_flush_all(void *cookie) -{ -} - -static void v1_tlb_flush_walk(unsigned long iova, size_t size, - size_t granule, void *cookie) -{ -} - -static void v1_tlb_add_page(struct iommu_iotlb_gather *gather, - unsigned long iova, size_t granule, - void *cookie) -{ -} - -static const struct iommu_flush_ops v1_flush_ops = { - .tlb_flush_all = v1_tlb_flush_all, - .tlb_flush_walk = v1_tlb_flush_walk, - .tlb_add_page = v1_tlb_add_page, -}; - /* * Helper function to get the first pte of a large mapping */ @@ -572,7 +551,6 @@ static struct io_pgtable *v1_alloc_pgtable(struct io_pgtable_cfg *cfg, void *coo cfg->pgsize_bitmap = AMD_IOMMU_PGSIZES; cfg->ias = IOMMU_IN_ADDR_BIT_SIZE; cfg->oas = IOMMU_OUT_ADDR_BIT_SIZE; - cfg->tlb = &v1_flush_ops; pgtable->pgtbl.ops.map_pages = iommu_v1_map_pages; pgtable->pgtbl.ops.unmap_pages = iommu_v1_unmap_pages; diff --git a/drivers/iommu/amd/io_pgtable_v2.c b/drivers/iommu/amd/io_pgtable_v2.c index 910fe1879f3e..77cc1b4a3f02 100644 --- a/drivers/iommu/amd/io_pgtable_v2.c +++ b/drivers/iommu/amd/io_pgtable_v2.c @@ -326,27 +326,6 @@ static phys_addr_t iommu_v2_iova_to_phys(struct io_pgtable_ops *ops, unsigned lo /* * ---------------------------------------------------- */ -static void v2_tlb_flush_all(void *cookie) -{ -} - -static void v2_tlb_flush_walk(unsigned long iova, size_t size, - size_t granule, void *cookie) -{ -} - -static void v2_tlb_add_page(struct iommu_iotlb_gather *gather, - unsigned long iova, size_t granule, - void *cookie) -{ -} - -static const struct iommu_flush_ops v2_flush_ops = { - .tlb_flush_all = v2_tlb_flush_all, - .tlb_flush_walk = v2_tlb_flush_walk, - .tlb_add_page = v2_tlb_add_page, -}; - static void v2_free_pgtable(struct io_pgtable *iop) { struct amd_io_pgtable *pgtable = container_of(iop, struct amd_io_pgtable, pgtbl); @@ -378,7 +357,6 @@ static struct io_pgtable *v2_alloc_pgtable(struct io_pgtable_cfg *cfg, void *coo cfg->pgsize_bitmap = AMD_IOMMU_PGSIZES_V2; cfg->ias = ias; cfg->oas = IOMMU_OUT_ADDR_BIT_SIZE; - cfg->tlb = &v2_flush_ops; return &pgtable->pgtbl; } From 7e515866299d1d01db6c2bbbc8045218c099ba1f Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:22 -0300 Subject: [PATCH 386/573] iommu/amd: Correct the reported page sizes from the V1 table The HW only has 52 bits of physical address support, the supported page sizes should not have bits set beyond this. Further the spec says that the 6th level does not support any "default page size for translation entries" meaning leafs in the 6th level are not allowed too. Rework the definition to use GENMASK to build the range of supported pages from the top of physical to 4k. Nothing ever uses such large pages, so this is a cosmetic/documentation improvement only. Reported-by: Joao Martins Reviewed-by: Vasant Hegde Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/13-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/amd_iommu_types.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h index 30eb07acb8b1..35aa4ff020f5 100644 --- a/drivers/iommu/amd/amd_iommu_types.h +++ b/drivers/iommu/amd/amd_iommu_types.h @@ -290,8 +290,9 @@ * that we support. * * 512GB Pages are not supported due to a hardware bug + * Page sizes >= the 52 bit max physical address of the CPU are not supported. */ -#define AMD_IOMMU_PGSIZES ((~0xFFFUL) & ~(2ULL << 38)) +#define AMD_IOMMU_PGSIZES (GENMASK_ULL(51, 12) ^ SZ_512G) /* 4K, 2MB, 1G page sizes are supported */ #define AMD_IOMMU_PGSIZES_V2 (PAGE_SIZE | (1ULL << 21) | (1ULL << 30)) From 2910a7fa1be090fc7637cef0b2e70bcd15bf5469 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 29 Aug 2024 21:06:23 -0300 Subject: [PATCH 387/573] iommu/amd: Do not set the D bit on AMD v2 table entries The manual says that bit 6 is IGN for all Page-Table Base Address pointers, don't set it. Fixes: aaac38f61487 ("iommu/amd: Initial support for AMD IOMMU v2 page table") Reviewed-by: Vasant Hegde Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/14-v2-831cdc4d00f3+1a315-amd_iopgtbl_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/io_pgtable_v2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/amd/io_pgtable_v2.c b/drivers/iommu/amd/io_pgtable_v2.c index 77cc1b4a3f02..25b9042fa453 100644 --- a/drivers/iommu/amd/io_pgtable_v2.c +++ b/drivers/iommu/amd/io_pgtable_v2.c @@ -51,7 +51,7 @@ static inline u64 set_pgtable_attr(u64 *page) u64 prot; prot = IOMMU_PAGE_PRESENT | IOMMU_PAGE_RW | IOMMU_PAGE_USER; - prot |= IOMMU_PAGE_ACCESS | IOMMU_PAGE_DIRTY; + prot |= IOMMU_PAGE_ACCESS; return (iommu_virt_to_phys(page) | prot); } From 3360211b2a955a30458de1f2657f0c9f75ef839c Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Tue, 3 Sep 2024 09:47:53 +0700 Subject: [PATCH 388/573] Documentation: livepatch: Correct release locks antonym "get" doesn't properly fit as an antonym for "release" in the context of locking. Correct it with "acquire". Signed-off-by: Bagas Sanjaya Reviewed-by: Petr Mladek Link: https://lore.kernel.org/r/20240903024753.104609-1-bagasdotme@gmail.com Signed-off-by: Petr Mladek --- Documentation/livepatch/livepatch.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/livepatch/livepatch.rst b/Documentation/livepatch/livepatch.rst index 68e3651e8af9..acb90164929e 100644 --- a/Documentation/livepatch/livepatch.rst +++ b/Documentation/livepatch/livepatch.rst @@ -50,7 +50,7 @@ some limitations, see below. 3. Consistency model ==================== -Functions are there for a reason. They take some input parameters, get or +Functions are there for a reason. They take some input parameters, acquire or release locks, read, process, and even write some data in a defined way, have return values. In other words, each function has a defined semantic. From 5854809b51f4e3868202d9b45e628133d76b48cd Mon Sep 17 00:00:00 2001 From: Jens Wiklander Date: Mon, 2 Sep 2024 12:58:03 +0200 Subject: [PATCH 389/573] rpmb: fix error path in rpmb_dev_register() Until this patch was rpmb_dev_register() always freeing rdev in the error path. However, past device_register() it must not do that since the memory is now managed by the device even if it failed to register properly. So fix this by doing a put_device() before returning the error code. Fixes the smatch warning: drivers/misc/rpmb-core.c:204 rpmb_dev_register() warn: freeing device managed memory (leak): 'rdev' Fixes: 1e9046e3a154 ("rpmb: add Replay Protected Memory Block (RPMB) subsystem") Reported-by: Dan Carpenter Signed-off-by: Jens Wiklander Link: https://lore.kernel.org/r/20240902105803.2885544-1-jens.wiklander@linaro.org Signed-off-by: Ulf Hansson --- drivers/misc/rpmb-core.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/misc/rpmb-core.c b/drivers/misc/rpmb-core.c index c8888267c222..bc68cde1a8bf 100644 --- a/drivers/misc/rpmb-core.c +++ b/drivers/misc/rpmb-core.c @@ -187,17 +187,15 @@ struct rpmb_dev *rpmb_dev_register(struct device *dev, rdev->dev.parent = dev; ret = device_register(&rdev->dev); - if (ret) - goto err_id_remove; + if (ret) { + put_device(&rdev->dev); + return ERR_PTR(ret); + } dev_dbg(&rdev->dev, "registered device\n"); return rdev; -err_id_remove: - mutex_lock(&rpmb_mutex); - ida_simple_remove(&rpmb_ida, rdev->id); - mutex_unlock(&rpmb_mutex); err_free_dev_id: kfree(rdev->descr.dev_id); err_free_rdev: From 086a3c40ebd02a4ac38121cf909326407b2883bc Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 4 Sep 2024 11:02:43 +0300 Subject: [PATCH 390/573] iommu/tegra241-cmdqv: Fix ioremap() error handling in probe() The ioremap() function doesn't return error pointers, it returns NULL on error so update the error handling. Also just return directly instead of calling iounmap() on the NULL pointer. Calling iounmap(NULL) doesn't cause a problem on ARM but on other architectures it can trigger a warning so it'a bad habbit. Fixes: 918eb5c856f6 ("iommu/arm-smmu-v3: Add in-kernel support for NVIDIA Tegra241 (Grace) CMDQV") Signed-off-by: Dan Carpenter Reviewed-by: Nicolin Chen Link: https://lore.kernel.org/r/5a6c1e9a-0724-41b1-86d4-36335d3768ea@stanley.mountain Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c index 03fd13c21dcc..240b54192177 100644 --- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c +++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c @@ -772,9 +772,9 @@ __tegra241_cmdqv_probe(struct arm_smmu_device *smmu, struct resource *res, static_assert(offsetof(struct tegra241_cmdqv, smmu) == 0); base = ioremap(res->start, resource_size(res)); - if (IS_ERR(base)) { - dev_err(smmu->dev, "failed to ioremap: %ld\n", PTR_ERR(base)); - goto iounmap; + if (!base) { + dev_err(smmu->dev, "failed to ioremap\n"); + return NULL; } regval = readl(base + TEGRA241_CMDQV_CONFIG); From f4d08a8fed0542effc545990b583b5322e5bd003 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Thu, 5 Sep 2024 10:42:45 +0800 Subject: [PATCH 391/573] gpio: sama5d2-piobu: convert comma to semicolon Replace comma between expressions with semicolons. Using a ',' in place of a ';' can have unintended side effects. Although that is not the case here, it is seems best to use ';' unless ',' is intended. Found by inspection. No functional change intended. Compile tested only. Signed-off-by: Chen Ni Link: https://lore.kernel.org/r/20240905024245.1642989-1-nichen@iscas.ac.cn Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-sama5d2-piobu.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/gpio/gpio-sama5d2-piobu.c b/drivers/gpio/gpio-sama5d2-piobu.c index d89da7300ddd..d770a6f3d846 100644 --- a/drivers/gpio/gpio-sama5d2-piobu.c +++ b/drivers/gpio/gpio-sama5d2-piobu.c @@ -191,15 +191,15 @@ static int sama5d2_piobu_probe(struct platform_device *pdev) piobu->chip.label = pdev->name; piobu->chip.parent = &pdev->dev; - piobu->chip.owner = THIS_MODULE, - piobu->chip.get_direction = sama5d2_piobu_get_direction, - piobu->chip.direction_input = sama5d2_piobu_direction_input, - piobu->chip.direction_output = sama5d2_piobu_direction_output, - piobu->chip.get = sama5d2_piobu_get, - piobu->chip.set = sama5d2_piobu_set, - piobu->chip.base = -1, - piobu->chip.ngpio = PIOBU_NUM, - piobu->chip.can_sleep = 0, + piobu->chip.owner = THIS_MODULE; + piobu->chip.get_direction = sama5d2_piobu_get_direction; + piobu->chip.direction_input = sama5d2_piobu_direction_input; + piobu->chip.direction_output = sama5d2_piobu_direction_output; + piobu->chip.get = sama5d2_piobu_get; + piobu->chip.set = sama5d2_piobu_set; + piobu->chip.base = -1; + piobu->chip.ngpio = PIOBU_NUM; + piobu->chip.can_sleep = 0; piobu->regmap = syscon_node_to_regmap(pdev->dev.of_node); if (IS_ERR(piobu->regmap)) { From 24e529c5fbcad9a480607bb05128c61ba908a926 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 25 Jul 2024 21:38:04 +0200 Subject: [PATCH 392/573] dt-bindings: pwm: renesas,pwm-rcar: Add r8a779h0 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document support for the PWM timers in the Renesas R-Car V4M (R8A779H0) SoC. Signed-off-by: Wolfram Sang Acked-by: Krzysztof Kozlowski Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20240725193803.14130-5-wsa+renesas@sang-engineering.com Signed-off-by: Uwe Kleine-König --- Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.yaml b/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.yaml index 6b6a302a175c..2fe1992e2908 100644 --- a/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.yaml +++ b/Documentation/devicetree/bindings/pwm/renesas,pwm-rcar.yaml @@ -37,6 +37,7 @@ properties: - renesas,pwm-r8a77995 # R-Car D3 - renesas,pwm-r8a779a0 # R-Car V3U - renesas,pwm-r8a779g0 # R-Car V4H + - renesas,pwm-r8a779h0 # R-Car V4M - const: renesas,pwm-rcar reg: From 9407f23d14edd42d372cefee023302bd95c9bca6 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 25 Jul 2024 21:38:05 +0200 Subject: [PATCH 393/573] dt-bindings: pwm: renesas,tpu: Add r8a779h0 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document support for the 16-Bit Timer Pulse Unit (TPU) in the Renesas R-Car V4M (R8A779H0) SoC. Signed-off-by: Wolfram Sang Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20240725193803.14130-6-wsa+renesas@sang-engineering.com Signed-off-by: Uwe Kleine-König --- Documentation/devicetree/bindings/pwm/renesas,tpu-pwm.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/pwm/renesas,tpu-pwm.yaml b/Documentation/devicetree/bindings/pwm/renesas,tpu-pwm.yaml index a3e52b22dd18..a4dfa09344dd 100644 --- a/Documentation/devicetree/bindings/pwm/renesas,tpu-pwm.yaml +++ b/Documentation/devicetree/bindings/pwm/renesas,tpu-pwm.yaml @@ -41,6 +41,7 @@ properties: - renesas,tpu-r8a77980 # R-Car V3H - renesas,tpu-r8a779a0 # R-Car V3U - renesas,tpu-r8a779g0 # R-Car V4H + - renesas,tpu-r8a779h0 # R-Car V4M - const: renesas,tpu reg: From 1c3e34bf8802b8b17d0c2067c43bb49b7e83885c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Fri, 5 Jul 2024 23:14:51 +0200 Subject: [PATCH 394/573] pwm: Make info in traces about affected pwm more useful MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hashed pointer isn't useful to identify the pwm device. Instead store and emit chipid and hwpwm. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20240705211452.1157967-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- include/trace/events/pwm.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/trace/events/pwm.h b/include/trace/events/pwm.h index 12b35e4ff917..8022701c446d 100644 --- a/include/trace/events/pwm.h +++ b/include/trace/events/pwm.h @@ -15,7 +15,8 @@ DECLARE_EVENT_CLASS(pwm, TP_ARGS(pwm, state, err), TP_STRUCT__entry( - __field(struct pwm_device *, pwm) + __field(unsigned int, chipid) + __field(unsigned int, hwpwm) __field(u64, period) __field(u64, duty_cycle) __field(enum pwm_polarity, polarity) @@ -24,7 +25,8 @@ DECLARE_EVENT_CLASS(pwm, ), TP_fast_assign( - __entry->pwm = pwm; + __entry->chipid = pwm->chip->id; + __entry->hwpwm = pwm->hwpwm; __entry->period = state->period; __entry->duty_cycle = state->duty_cycle; __entry->polarity = state->polarity; @@ -32,8 +34,8 @@ DECLARE_EVENT_CLASS(pwm, __entry->err = err; ), - TP_printk("%p: period=%llu duty_cycle=%llu polarity=%d enabled=%d err=%d", - __entry->pwm, __entry->period, __entry->duty_cycle, + TP_printk("pwmchip%u.%u: period=%llu duty_cycle=%llu polarity=%d enabled=%d err=%d", + __entry->chipid, __entry->hwpwm, __entry->period, __entry->duty_cycle, __entry->polarity, __entry->enabled, __entry->err) ); From f9ecc2febf6fd6ad53208a1c0e1b5066ee65dd8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Fri, 12 Jul 2024 19:18:20 +0200 Subject: [PATCH 395/573] pwm: Don't export pwm_capture() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is only a single caller of this function, and that's in drivers/pwm/core.c itself. So don't export the function. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20240712171821.1470833-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/core.c | 5 ++--- include/linux/pwm.h | 10 ---------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 8acbcf5b6673..56d91c11f0d4 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -325,8 +325,8 @@ EXPORT_SYMBOL_GPL(pwm_adjust_config); * * Returns: 0 on success or a negative error code on failure. */ -int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result, - unsigned long timeout) +static int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result, + unsigned long timeout) { if (!pwm || !pwm->chip->ops) return -EINVAL; @@ -338,7 +338,6 @@ int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result, return pwm->chip->ops->capture(pwm->chip, pwm, result, timeout); } -EXPORT_SYMBOL_GPL(pwm_capture); static struct pwm_chip *pwmchip_find_by_name(const char *name) { diff --git a/include/linux/pwm.h b/include/linux/pwm.h index f8c2dc12dbd3..8acd60b53f58 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -394,9 +394,6 @@ static inline bool pwm_might_sleep(struct pwm_device *pwm) } /* PWM provider APIs */ -int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result, - unsigned long timeout); - void pwmchip_put(struct pwm_chip *chip); struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv); struct pwm_chip *devm_pwmchip_alloc(struct device *parent, unsigned int npwm, size_t sizeof_priv); @@ -462,13 +459,6 @@ static inline void pwm_disable(struct pwm_device *pwm) might_sleep(); } -static inline int pwm_capture(struct pwm_device *pwm, - struct pwm_capture *result, - unsigned long timeout) -{ - return -EINVAL; -} - static inline void pwmchip_put(struct pwm_chip *chip) { } From 75f0cb339b7814dc759c58a0d42fc5c1b93c7836 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Wed, 31 Jul 2024 14:14:03 -0600 Subject: [PATCH 396/573] pwm: lp3943: Use of_property_count_u32_elems() to get property length MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace of_get_property() with the type specific of_property_count_u32_elems() to get the property length. This is part of a larger effort to remove callers of of_get_property() and similar functions. of_get_property() leaks the DT property data pointer which is a problem for dynamically allocated nodes which may be freed. Signed-off-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20240731201407.1838385-8-robh@kernel.org Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-lp3943.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/pwm/pwm-lp3943.c b/drivers/pwm/pwm-lp3943.c index 61189cea1046..f0e94c9e5956 100644 --- a/drivers/pwm/pwm-lp3943.c +++ b/drivers/pwm/pwm-lp3943.c @@ -218,7 +218,7 @@ static int lp3943_pwm_parse_dt(struct device *dev, struct lp3943_platform_data *pdata; struct lp3943_pwm_map *pwm_map; enum lp3943_pwm_output *output; - int i, err, proplen, count = 0; + int i, err, count = 0; u32 num_outputs; if (!node) @@ -234,11 +234,8 @@ static int lp3943_pwm_parse_dt(struct device *dev, */ for (i = 0; i < LP3943_NUM_PWMS; i++) { - if (!of_get_property(node, name[i], &proplen)) - continue; - - num_outputs = proplen / sizeof(u32); - if (num_outputs == 0) + num_outputs = of_property_count_u32_elems(node, name[i]); + if (num_outputs <= 0) continue; output = devm_kcalloc(dev, num_outputs, sizeof(*output), From d6a800796e9806f62b52f2aa80e52a2553e11e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 29 Jul 2024 16:34:17 +0200 Subject: [PATCH 397/573] pwm: Simplify pwm_capture() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When pwm_capture() is called, pwm is valid, so the checks for pwm and pwm->chip->ops being NULL can be dropped. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/ee7b3322c7b3e28defdfb886a70b8ba40d298416.1722261050.git.u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 56d91c11f0d4..6e752e148b98 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -328,15 +328,15 @@ EXPORT_SYMBOL_GPL(pwm_adjust_config); static int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result, unsigned long timeout) { - if (!pwm || !pwm->chip->ops) - return -EINVAL; + struct pwm_chip *chip = pwm->chip; + const struct pwm_ops *ops = chip->ops; - if (!pwm->chip->ops->capture) + if (!ops->capture) return -ENOSYS; guard(mutex)(&pwm_lock); - return pwm->chip->ops->capture(pwm->chip, pwm, result, timeout); + return ops->capture(chip, pwm, result, timeout); } static struct pwm_chip *pwmchip_find_by_name(const char *name) From 8d72eba1cf8cecd76a2b4c1dd7673c2dc775f514 Mon Sep 17 00:00:00 2001 From: Dhananjay Ugwekar Date: Tue, 30 Jul 2024 04:49:18 +0000 Subject: [PATCH 398/573] perf/x86/rapl: Fix the energy-pkg event for AMD CPUs After commit: 63edbaa48a57 ("x86/cpu/topology: Add support for the AMD 0x80000026 leaf") ... on AMD processors that support extended CPUID leaf 0x80000026, the topology_die_cpumask() and topology_logical_die_id() macros no longer return the package cpumask and package ID, instead they return the CCD (Core Complex Die) mask and ID respectively. This leads to the energy-pkg event scope to be modified to CCD instead of package. So, change the PMU scope for AMD and Hygon back to package. On a 12 CCD 1 Package AMD Zen4 Genoa machine: Before: $ cat /sys/devices/power/cpumask 0,8,16,24,32,40,48,56,64,72,80,88. The expected cpumask here is supposed to be just "0", as it is a package scope event, only one CPU will be collecting the event for all the CPUs in the package. After: $ cat /sys/devices/power/cpumask 0 [ mingo: Cleaned up the changelog ] Signed-off-by: Dhananjay Ugwekar Signed-off-by: Ingo Molnar Reviewed-by: Kan Liang Link: https://lore.kernel.org/r/20240904100934.3260-1-Dhananjay.Ugwekar@amd.com --- arch/x86/events/rapl.c | 47 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/arch/x86/events/rapl.c b/arch/x86/events/rapl.c index b985ca79cf97..a481a939862e 100644 --- a/arch/x86/events/rapl.c +++ b/arch/x86/events/rapl.c @@ -103,6 +103,19 @@ static struct perf_pmu_events_attr event_attr_##v = { \ .event_str = str, \ }; +/* + * RAPL Package energy counter scope: + * 1. AMD/HYGON platforms have a per-PKG package energy counter + * 2. For Intel platforms + * 2.1. CLX-AP is multi-die and its RAPL MSRs are die-scope + * 2.2. Other Intel platforms are single die systems so the scope can be + * considered as either pkg-scope or die-scope, and we are considering + * them as die-scope. + */ +#define rapl_pmu_is_pkg_scope() \ + (boot_cpu_data.x86_vendor == X86_VENDOR_AMD || \ + boot_cpu_data.x86_vendor == X86_VENDOR_HYGON) + struct rapl_pmu { raw_spinlock_t lock; int n_active; @@ -140,9 +153,25 @@ static unsigned int rapl_cntr_mask; static u64 rapl_timer_ms; static struct perf_msr *rapl_msrs; +/* + * Helper functions to get the correct topology macros according to the + * RAPL PMU scope. + */ +static inline unsigned int get_rapl_pmu_idx(int cpu) +{ + return rapl_pmu_is_pkg_scope() ? topology_logical_package_id(cpu) : + topology_logical_die_id(cpu); +} + +static inline const struct cpumask *get_rapl_pmu_cpumask(int cpu) +{ + return rapl_pmu_is_pkg_scope() ? topology_core_cpumask(cpu) : + topology_die_cpumask(cpu); +} + static inline struct rapl_pmu *cpu_to_rapl_pmu(unsigned int cpu) { - unsigned int rapl_pmu_idx = topology_logical_die_id(cpu); + unsigned int rapl_pmu_idx = get_rapl_pmu_idx(cpu); /* * The unsigned check also catches the '-1' return value for non @@ -552,7 +581,7 @@ static int rapl_cpu_offline(unsigned int cpu) pmu->cpu = -1; /* Find a new cpu to collect rapl events */ - target = cpumask_any_but(topology_die_cpumask(cpu), cpu); + target = cpumask_any_but(get_rapl_pmu_cpumask(cpu), cpu); /* Migrate rapl events to the new target */ if (target < nr_cpu_ids) { @@ -565,6 +594,11 @@ static int rapl_cpu_offline(unsigned int cpu) static int rapl_cpu_online(unsigned int cpu) { + s32 rapl_pmu_idx = get_rapl_pmu_idx(cpu); + if (rapl_pmu_idx < 0) { + pr_err("topology_logical_(package/die)_id() returned a negative value"); + return -EINVAL; + } struct rapl_pmu *pmu = cpu_to_rapl_pmu(cpu); int target; @@ -579,14 +613,14 @@ static int rapl_cpu_online(unsigned int cpu) pmu->timer_interval = ms_to_ktime(rapl_timer_ms); rapl_hrtimer_init(pmu); - rapl_pmus->pmus[topology_logical_die_id(cpu)] = pmu; + rapl_pmus->pmus[rapl_pmu_idx] = pmu; } /* * Check if there is an online cpu in the package which collects rapl * events already. */ - target = cpumask_any_and(&rapl_cpu_mask, topology_die_cpumask(cpu)); + target = cpumask_any_and(&rapl_cpu_mask, get_rapl_pmu_cpumask(cpu)); if (target < nr_cpu_ids) return 0; @@ -675,7 +709,10 @@ static const struct attribute_group *rapl_attr_update[] = { static int __init init_rapl_pmus(void) { - int nr_rapl_pmu = topology_max_packages() * topology_max_dies_per_package(); + int nr_rapl_pmu = topology_max_packages(); + + if (!rapl_pmu_is_pkg_scope()) + nr_rapl_pmu *= topology_max_dies_per_package(); rapl_pmus = kzalloc(struct_size(rapl_pmus, pmus, nr_rapl_pmu), GFP_KERNEL); if (!rapl_pmus) From 819e4b3723bf191e0887207f7d456f4b928a87f0 Mon Sep 17 00:00:00 2001 From: Avri Altman Date: Tue, 3 Sep 2024 16:38:55 +0300 Subject: [PATCH 399/573] Documentation: mmc: Add mmc-test doc Add missing documentation for mmc_test. Reviewed-by: Christian Loehle Signed-off-by: Avri Altman Link: https://lore.kernel.org/r/20240903133855.3589845-1-avri.altman@wdc.com Signed-off-by: Ulf Hansson --- Documentation/driver-api/mmc/index.rst | 1 + Documentation/driver-api/mmc/mmc-test.rst | 299 ++++++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 Documentation/driver-api/mmc/mmc-test.rst diff --git a/Documentation/driver-api/mmc/index.rst b/Documentation/driver-api/mmc/index.rst index 7339736ac774..8188863e5959 100644 --- a/Documentation/driver-api/mmc/index.rst +++ b/Documentation/driver-api/mmc/index.rst @@ -10,4 +10,5 @@ MMC/SD/SDIO card support mmc-dev-attrs mmc-dev-parts mmc-async-req + mmc-test mmc-tools diff --git a/Documentation/driver-api/mmc/mmc-test.rst b/Documentation/driver-api/mmc/mmc-test.rst new file mode 100644 index 000000000000..1fe33eb43742 --- /dev/null +++ b/Documentation/driver-api/mmc/mmc-test.rst @@ -0,0 +1,299 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================== +MMC Test Framework +======================== + +Overview +======== + +The `mmc_test` framework is designed to test the performance and reliability of host controller drivers and all devices handled by the MMC subsystem. This includes not only MMC devices but also SD cards and other devices supported by the subsystem. + +The framework provides a variety of tests to evaluate different aspects of the host controller and device interactions, such as read and write performance, data integrity, and error handling. These tests help ensure that the host controller drivers and devices operate correctly under various conditions. + +The `mmc_test` framework is particularly useful for: + +- Verifying the functionality and performance of MMC and SD host controller drivers. +- Ensuring compatibility and reliability of MMC and SD devices. +- Identifying and diagnosing issues in the MMC subsystem. + +The results of the tests are logged in the kernel log, providing detailed information about the test outcomes and any encountered issues. + +Note: whatever is on your card will be overwritten by these tests. + +Initialization +============== + +To use the ``mmc_test`` framework, follow these steps: + +1. **Enable the MMC Test Framework**: + + Ensure that the ``CONFIG_MMC_TEST`` kernel configuration option is enabled. This can be done by configuring the kernel: + + .. code-block:: none + + make menuconfig + + Navigate to: + + Device Drivers ---> + <*> MMC/SD/SDIO card support ---> + [*] MMC host test driver + + Alternatively, you can enable it directly in the kernel configuration file: + + .. code-block:: none + + echo "CONFIG_MMC_TEST=y" >> .config + + Rebuild and install the kernel if necessary. + +2. **Load the MMC Test Module**: + + If the ``mmc_test`` framework is built as a module, you need to load it using ``modprobe``: + + .. code-block:: none + + modprobe mmc_test + +Binding the MMC Card for Testing +================================ + +To enable MMC testing, you need to unbind the MMC card from the ``mmcblk`` driver and bind it to the ``mmc_test`` driver. This allows the ``mmc_test`` framework to take control of the MMC card for testing purposes. + +1. Identify the MMC card: + + .. code-block:: sh + + ls /sys/bus/mmc/devices/ + + This will list the MMC devices, such as ``mmc0:0001``. + +2. Unbind the MMC card from the ``mmcblk`` driver: + + .. code-block:: sh + + echo 'mmc0:0001' > /sys/bus/mmc/drivers/mmcblk/unbind + +3. Bind the MMC card to the ``mmc_test`` driver: + + .. code-block:: sh + + echo 'mmc0:0001' > /sys/bus/mmc/drivers/mmc_test/bind + +After binding, you should see a line in the kernel log indicating that the card has been claimed for testing: + +.. code-block:: none + + mmc_test mmc0:0001: Card claimed for testing. + + +Usage - Debugfs Entries +======================= + +Once the ``mmc_test`` framework is enabled, you can interact with the following debugfs entries located in ``/sys/kernel/debug/mmc0/mmc0:0001``: + +1. **test**: + + This file is used to run specific tests. Write the test number to this file to execute a test. + + .. code-block:: sh + + echo > /sys/kernel/debug/mmc0/mmc0:0001/test + + The test result is indicated in the kernel log info. You can view the kernel log using the `dmesg` command or by checking the log file in `/var/log/`. + + .. code-block:: sh + + dmesg | grep mmc0 + + Example: + + To run test number 4 (Basic read with data verification): + + .. code-block:: sh + + echo 4 > /sys/kernel/debug/mmc0/mmc0:0001/test + + Check the kernel log for the result: + + .. code-block:: sh + + dmesg | grep mmc0 + +2. **testlist**: + + This file lists all available tests. You can read this file to see the list of tests and their corresponding numbers. + + .. code-block:: sh + + cat /sys/kernel/debug/mmc0/mmc0:0001/testlist + + The available tests are listed in the table below: + ++------+--------------------------------+---------------------------------------------+ +| Test | Test Name | Test Description | ++======+================================+=============================================+ +| 0 | Run all tests | Runs all available tests | ++------+--------------------------------+---------------------------------------------+ +| 1 | Basic write | Performs a basic write operation of a | +| | | single 512-Byte block to the MMC card | +| | | without data verification. | ++------+--------------------------------+---------------------------------------------+ +| 2 | Basic read | Same for read | ++------+--------------------------------+---------------------------------------------+ +| 3 | Basic write | Performs a basic write operation of a | +| | (with data verification) | single 512-Byte block to the MMC card | +| | | with data verification by reading back | +| | | the written data and comparing it. | ++------+--------------------------------+---------------------------------------------+ +| 4 | Basic read | Same for read | +| | (with data verification) | | ++------+--------------------------------+---------------------------------------------+ +| 5 | Multi-block write | Performs a multi-block write operation of | +| | | 8 blocks (each 512 bytes) to the MMC card. | ++------+--------------------------------+---------------------------------------------+ +| 6 | Multi-block read | Same for read | ++------+--------------------------------+---------------------------------------------+ +| 7 | Power of two block writes | Performs write operations with block sizes | +| | | that are powers of two, starting from 1 | +| | | byte up to 256 bytes, to the MMC card. | ++------+--------------------------------+---------------------------------------------+ +| 8 | Power of two block reads | Same for read | ++------+--------------------------------+---------------------------------------------+ +| 9 | Weird sized block writes | Performs write operations with varying | +| | | block sizes starting from 3 bytes and | +| | | increasing by 7 bytes each iteration, up | +| | | to 511 bytes, to the MMC card. | ++------+--------------------------------+---------------------------------------------+ +| 10 | Weird sized block reads | same for read | ++------+--------------------------------+---------------------------------------------+ +| 11 | Badly aligned write | Performs write operations with buffers | +| | | starting at different alignments (0 to 7 | +| | | bytes offset) to test how the MMC card | +| | | handles unaligned data transfers. | ++------+--------------------------------+---------------------------------------------+ +| 12 | Badly aligned read | same for read | ++------+--------------------------------+---------------------------------------------+ +| 13 | Badly aligned multi-block write| same for multi-write | ++------+--------------------------------+---------------------------------------------+ +| 14 | Badly aligned multi-block read | same for multi-read | ++------+--------------------------------+---------------------------------------------+ +| 15 | Proper xfer_size at write | intentionally create a broken transfer by | +| | (Start failure) | modifying the MMC request in a way that it | +| | | will not perform as expected, e.g. use | +| | | MMC_WRITE_BLOCK for a multi-block transfer | ++------+--------------------------------+---------------------------------------------+ +| 16 | Proper xfer_size at read | same for read | +| | (Start failure) | | ++------+--------------------------------+---------------------------------------------+ +| 17 | Proper xfer_size at write | same for 2 blocks | +| | (Midway failure) | | ++------+--------------------------------+---------------------------------------------+ +| 18 | Proper xfer_size at read | same for read | +| | (Midway failure) | | ++------+--------------------------------+---------------------------------------------+ +| 19 | Highmem write | use a high memory page | ++------+--------------------------------+---------------------------------------------+ +| 20 | Highmem read | same for read | ++------+--------------------------------+---------------------------------------------+ +| 21 | Multi-block highmem write | same for multi-write | ++------+--------------------------------+---------------------------------------------+ +| 22 | Multi-block highmem read | same for mult-read | ++------+--------------------------------+---------------------------------------------+ +| 23 | Best-case read performance | Performs 512K sequential read (non sg) | ++------+--------------------------------+---------------------------------------------+ +| 24 | Best-case write performance | same for write | ++------+--------------------------------+---------------------------------------------+ +| 25 | Best-case read performance | Same using sg | +| | (Into scattered pages) | | ++------+--------------------------------+---------------------------------------------+ +| 26 | Best-case write performance | same for write | +| | (From scattered pages) | | ++------+--------------------------------+---------------------------------------------+ +| 27 | Single read performance | By transfer size | ++------+--------------------------------+---------------------------------------------+ +| 28 | Single write performance | By transfer size | ++------+--------------------------------+---------------------------------------------+ +| 29 | Single trim performance | By transfer size | ++------+--------------------------------+---------------------------------------------+ +| 30 | Consecutive read performance | By transfer size | ++------+--------------------------------+---------------------------------------------+ +| 31 | Consecutive write performance | By transfer size | ++------+--------------------------------+---------------------------------------------+ +| 32 | Consecutive trim performance | By transfer size | ++------+--------------------------------+---------------------------------------------+ +| 33 | Random read performance | By transfer size | ++------+--------------------------------+---------------------------------------------+ +| 34 | Random write performance | By transfer size | ++------+--------------------------------+---------------------------------------------+ +| 35 | Large sequential read | Into scattered pages | ++------+--------------------------------+---------------------------------------------+ +| 36 | Large sequential write | From scattered pages | ++------+--------------------------------+---------------------------------------------+ +| 37 | Write performance | With blocking req 4k to 4MB | ++------+--------------------------------+---------------------------------------------+ +| 38 | Write performance | With non-blocking req 4k to 4MB | ++------+--------------------------------+---------------------------------------------+ +| 39 | Read performance | With blocking req 4k to 4MB | ++------+--------------------------------+---------------------------------------------+ +| 40 | Read performance | With non-blocking req 4k to 4MB | ++------+--------------------------------+---------------------------------------------+ +| 41 | Write performance | Blocking req 1 to 512 sg elems | ++------+--------------------------------+---------------------------------------------+ +| 42 | Write performance | Non-blocking req 1 to 512 sg elems | ++------+--------------------------------+---------------------------------------------+ +| 43 | Read performance | Blocking req 1 to 512 sg elems | ++------+--------------------------------+---------------------------------------------+ +| 44 | Read performance | Non-blocking req 1 to 512 sg elems | ++------+--------------------------------+---------------------------------------------+ +| 45 | Reset test | | ++------+--------------------------------+---------------------------------------------+ +| 46 | Commands during read | No Set Block Count (CMD23) | ++------+--------------------------------+---------------------------------------------+ +| 47 | Commands during write | No Set Block Count (CMD23) | ++------+--------------------------------+---------------------------------------------+ +| 48 | Commands during read | Use Set Block Count (CMD23) | ++------+--------------------------------+---------------------------------------------+ +| 49 | Commands during write | Use Set Block Count (CMD23) | ++------+--------------------------------+---------------------------------------------+ +| 50 | Commands during non-blocking | Read - use Set Block Count (CMD23) | ++------+--------------------------------+---------------------------------------------+ +| 51 | Commands during non-blocking | Write - use Set Block Count (CMD23) | ++------+--------------------------------+---------------------------------------------+ + +Test Results +============ + +The results of the tests are logged in the kernel log. Each test logs the start, end, and result of the test. The possible results are: + +- **OK**: The test completed successfully. +- **FAILED**: The test failed. +- **UNSUPPORTED (by host)**: The test is unsupported by the host. +- **UNSUPPORTED (by card)**: The test is unsupported by the card. +- **ERROR**: An error occurred during the test. + +Example Kernel Log Output +========================= + +When running a test, you will see log entries similar to the following in the kernel log: + +.. code-block:: none + + [ 1234.567890] mmc0: Starting tests of card mmc0:0001... + [ 1234.567891] mmc0: Test case 4. Basic read (with data verification)... + [ 1234.567892] mmc0: Result: OK + [ 1234.567893] mmc0: Tests completed. + +In this example, test case 4 (Basic read with data verification) was executed, and the result was OK. + + +Contributing +============ + +Contributions to the `mmc_test` framework are welcome. Please follow the standard Linux kernel contribution guidelines and submit patches to the appropriate maintainers. + +Contact +======= + +For more information or to report issues, please contact the MMC subsystem maintainers. From 7e856617a1f34fc9714eaf562b78fcffacda4e3e Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Wed, 4 Sep 2024 16:30:58 -0400 Subject: [PATCH 400/573] dt-bindings: mmc: Add support for rk3576 eMMC The device is compatible with rk3588, so add an entry for the 2 compatibles together. The rk3576 device has a power-domain that needs to be on for the eMMC to be used. Add it as a requirement. Signed-off-by: Detlev Casanova Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240904203154.253655-2-detlev.casanova@collabora.com Signed-off-by: Ulf Hansson --- .../bindings/mmc/snps,dwcmshc-sdhci.yaml | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml index 80d50178d2e3..c3d5e0230af1 100644 --- a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml +++ b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml @@ -12,14 +12,18 @@ maintainers: properties: compatible: - enum: - - rockchip,rk3568-dwcmshc - - rockchip,rk3588-dwcmshc - - snps,dwcmshc-sdhci - - sophgo,cv1800b-dwcmshc - - sophgo,sg2002-dwcmshc - - sophgo,sg2042-dwcmshc - - thead,th1520-dwcmshc + oneOf: + - items: + - const: rockchip,rk3576-dwcmshc + - const: rockchip,rk3588-dwcmshc + - enum: + - rockchip,rk3568-dwcmshc + - rockchip,rk3588-dwcmshc + - snps,dwcmshc-sdhci + - sophgo,cv1800b-dwcmshc + - sophgo,sg2002-dwcmshc + - sophgo,sg2042-dwcmshc + - thead,th1520-dwcmshc reg: maxItems: 1 @@ -35,6 +39,9 @@ properties: minItems: 1 maxItems: 5 + power-domains: + maxItems: 1 + resets: maxItems: 5 @@ -97,6 +104,20 @@ allOf: - const: block - const: timer + - if: + properties: + compatible: + contains: + const: rockchip,rk3576-dwcmshc + + then: + required: + - power-domains + + else: + properties: + power-domains: false + unevaluatedProperties: false examples: From 901d16e462963cb0d824144be7448505b15ff4d6 Mon Sep 17 00:00:00 2001 From: Judith Mendez Date: Wed, 4 Sep 2024 18:25:11 -0500 Subject: [PATCH 401/573] mmc: sdhci_am654: Add retry tuning Add retry tuning up to 10 times if we fail to find a failing region or no passing itapdly. This is necessary since some eMMC has been observed to never find a failing itapdly on the first couple of tuning iterations, but eventually does. Keep count of current tuning iteration using tuning_loop. It has been observed that the tuning algorithm does not need to loop more than 10 times before finding a failing itapdly. Signed-off-by: Judith Mendez Acked-by: Adrian Hunter Link: https://lore.kernel.org/r/20240904232512.830778-2-jm@ti.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 47 +++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 64e10f7c9faa..8eb6ce9f3b17 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -86,6 +86,7 @@ #define CLOCK_TOO_SLOW_HZ 50000000 #define SDHCI_AM654_AUTOSUSPEND_DELAY -1 +#define RETRY_TUNING_MAX 10 /* Command Queue Host Controller Interface Base address */ #define SDHCI_AM654_CQE_BASE_ADDR 0x200 @@ -151,6 +152,7 @@ struct sdhci_am654_data { u32 flags; u32 quirks; bool dll_enable; + u32 tuning_loop; #define SDHCI_AM654_QUIRK_FORCE_CDTEST BIT(0) }; @@ -443,22 +445,23 @@ static u32 sdhci_am654_cqhci_irq(struct sdhci_host *host, u32 intmask) #define ITAPDLY_LENGTH 32 #define ITAPDLY_LAST_INDEX (ITAPDLY_LENGTH - 1) -static u32 sdhci_am654_calculate_itap(struct sdhci_host *host, struct window +static int sdhci_am654_calculate_itap(struct sdhci_host *host, struct window *fail_window, u8 num_fails, bool circular_buffer) { u8 itap = 0, start_fail = 0, end_fail = 0, pass_length = 0; u8 first_fail_start = 0, last_fail_end = 0; - struct device *dev = mmc_dev(host->mmc); struct window pass_window = {0, 0, 0}; int prev_fail_end = -1; u8 i; - if (!num_fails) - return ITAPDLY_LAST_INDEX >> 1; + if (!num_fails) { + /* Retry tuning */ + return -1; + } if (fail_window->length == ITAPDLY_LENGTH) { - dev_err(dev, "No passing ITAPDLY, return 0\n"); - return 0; + /* Retry tuning */ + return -1; } first_fail_start = fail_window->start; @@ -494,8 +497,8 @@ static u32 sdhci_am654_calculate_itap(struct sdhci_host *host, struct window return (itap > ITAPDLY_LAST_INDEX) ? ITAPDLY_LAST_INDEX >> 1 : itap; } -static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, - u32 opcode) +static int sdhci_am654_do_tuning(struct sdhci_host *host, + u32 opcode) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host); @@ -532,13 +535,30 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, if (fail_window[fail_index].length != 0) fail_index++; - itap = sdhci_am654_calculate_itap(host, fail_window, fail_index, - sdhci_am654->dll_enable); + return sdhci_am654_calculate_itap(host, fail_window, fail_index, + sdhci_am654->dll_enable); +} - sdhci_am654_write_itapdly(sdhci_am654, itap, sdhci_am654->itap_del_ena[timing]); +static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, + u32 opcode) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host); + unsigned char timing = host->mmc->ios.timing; + int itapdly; + do { + itapdly = sdhci_am654_do_tuning(host, opcode); + if (itapdly >= 0) + break; + } while (++sdhci_am654->tuning_loop < RETRY_TUNING_MAX); + + if (itapdly < 0) + return -1; + + sdhci_am654_write_itapdly(sdhci_am654, itapdly, sdhci_am654->itap_del_ena[timing]); /* Save ITAPDLY */ - sdhci_am654->itap_del_sel[timing] = itap; + sdhci_am654->itap_del_sel[timing] = itapdly; return 0; } @@ -742,6 +762,9 @@ static int sdhci_am654_init(struct sdhci_host *host) regmap_update_bits(sdhci_am654->base, CTL_CFG_3, TUNINGFORSDR50_MASK, TUNINGFORSDR50_MASK); + /* Use to re-execute tuning */ + sdhci_am654->tuning_loop = 0; + ret = sdhci_setup_host(host); if (ret) return ret; From cf6444ba528f043398b112ac36e041a4d8685cb1 Mon Sep 17 00:00:00 2001 From: Judith Mendez Date: Wed, 4 Sep 2024 18:25:12 -0500 Subject: [PATCH 402/573] mmc: sdhci_am654: Add prints to tuning algorithm Add debug prints to tuning algorithm for debugging. Also add error print if we fail tuning. Signed-off-by: Judith Mendez Link: https://lore.kernel.org/r/20240904232512.830778-3-jm@ti.com Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci_am654.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci_am654.c b/drivers/mmc/host/sdhci_am654.c index 8eb6ce9f3b17..0aa3c40ea6ed 100644 --- a/drivers/mmc/host/sdhci_am654.c +++ b/drivers/mmc/host/sdhci_am654.c @@ -450,17 +450,20 @@ static int sdhci_am654_calculate_itap(struct sdhci_host *host, struct window { u8 itap = 0, start_fail = 0, end_fail = 0, pass_length = 0; u8 first_fail_start = 0, last_fail_end = 0; + struct device *dev = mmc_dev(host->mmc); struct window pass_window = {0, 0, 0}; int prev_fail_end = -1; u8 i; if (!num_fails) { /* Retry tuning */ + dev_dbg(dev, "No failing region found, retry tuning\n"); return -1; } if (fail_window->length == ITAPDLY_LENGTH) { /* Retry tuning */ + dev_dbg(dev, "No passing itapdly, retry tuning\n"); return -1; } @@ -504,6 +507,7 @@ static int sdhci_am654_do_tuning(struct sdhci_host *host, struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host); unsigned char timing = host->mmc->ios.timing; struct window fail_window[ITAPDLY_LENGTH]; + struct device *dev = mmc_dev(host->mmc); u8 curr_pass, itap; u8 fail_index = 0; u8 prev_pass = 1; @@ -524,6 +528,7 @@ static int sdhci_am654_do_tuning(struct sdhci_host *host, if (!curr_pass) { fail_window[fail_index].end = itap; fail_window[fail_index].length++; + dev_dbg(dev, "Failed itapdly=%d\n", itap); } if (curr_pass && !prev_pass) @@ -545,6 +550,7 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host); unsigned char timing = host->mmc->ios.timing; + struct device *dev = mmc_dev(host->mmc); int itapdly; do { @@ -553,9 +559,12 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host, break; } while (++sdhci_am654->tuning_loop < RETRY_TUNING_MAX); - if (itapdly < 0) + if (itapdly < 0) { + dev_err(dev, "Failed to find itapdly, fail tuning\n"); return -1; + } + dev_dbg(dev, "Passed tuning, final itapdly=%d\n", itapdly); sdhci_am654_write_itapdly(sdhci_am654, itapdly, sdhci_am654->itap_del_ena[timing]); /* Save ITAPDLY */ sdhci_am654->itap_del_sel[timing] = itapdly; From e02147cb703412fa13dd31908c734d7fb2314f55 Mon Sep 17 00:00:00 2001 From: Xavier Date: Wed, 4 Sep 2024 15:40:37 +0800 Subject: [PATCH 403/573] mm/slab: Optimize the code logic in find_mergeable() We can first assess the flags, if it's unmergeable, there's no need to calculate the size and align. Signed-off-by: Xavier Signed-off-by: Vlastimil Babka --- mm/slab_common.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index ca694f5553b4..85afeb69b3c0 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -186,14 +186,15 @@ struct kmem_cache *find_mergeable(unsigned int size, unsigned int align, if (ctor) return NULL; - size = ALIGN(size, sizeof(void *)); - align = calculate_alignment(flags, align, size); - size = ALIGN(size, align); flags = kmem_cache_flags(flags, name); if (flags & SLAB_NEVER_MERGE) return NULL; + size = ALIGN(size, sizeof(void *)); + align = calculate_alignment(flags, align, size); + size = ALIGN(size, align); + list_for_each_entry_reverse(s, &slab_caches, list) { if (slab_unmergeable(s)) continue; From da426eda1b633b339c425b9501aa41485fd4a3c2 Mon Sep 17 00:00:00 2001 From: Zhang Zekun Date: Wed, 4 Sep 2024 17:23:08 +0800 Subject: [PATCH 404/573] gpio: cadence: Use helper function devm_clk_get_enabled() devm_clk_get() and clk_prepare_enable() can be replaced by helper function devm_clk_get_enabled(). Let's use devm_clk_get_enabled() to simplify code and avoid calling clk_disable_unprepare(). Signed-off-by: Zhang Zekun Link: https://lore.kernel.org/r/20240904092311.9544-2-zhangzekun11@huawei.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-cadence.c | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/drivers/gpio/gpio-cadence.c b/drivers/gpio/gpio-cadence.c index 6a439cf78459..1b8ffd0ddab6 100644 --- a/drivers/gpio/gpio-cadence.c +++ b/drivers/gpio/gpio-cadence.c @@ -31,7 +31,6 @@ struct cdns_gpio_chip { struct gpio_chip gc; - struct clk *pclk; void __iomem *regs; u32 bypass_orig; }; @@ -155,6 +154,7 @@ static int cdns_gpio_probe(struct platform_device *pdev) int ret, irq; u32 dir_prev; u32 num_gpios = 32; + struct clk *clk; cgpio = devm_kzalloc(&pdev->dev, sizeof(*cgpio), GFP_KERNEL); if (!cgpio) @@ -203,21 +203,14 @@ static int cdns_gpio_probe(struct platform_device *pdev) cgpio->gc.request = cdns_gpio_request; cgpio->gc.free = cdns_gpio_free; - cgpio->pclk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(cgpio->pclk)) { - ret = PTR_ERR(cgpio->pclk); + clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); dev_err(&pdev->dev, "Failed to retrieve peripheral clock, %d\n", ret); goto err_revert_dir; } - ret = clk_prepare_enable(cgpio->pclk); - if (ret) { - dev_err(&pdev->dev, - "Failed to enable the peripheral clock, %d\n", ret); - goto err_revert_dir; - } - /* * Optional irq_chip support */ @@ -234,7 +227,7 @@ static int cdns_gpio_probe(struct platform_device *pdev) GFP_KERNEL); if (!girq->parents) { ret = -ENOMEM; - goto err_disable_clk; + goto err_revert_dir; } girq->parents[0] = irq; girq->default_type = IRQ_TYPE_NONE; @@ -244,7 +237,7 @@ static int cdns_gpio_probe(struct platform_device *pdev) ret = devm_gpiochip_add_data(&pdev->dev, &cgpio->gc, cgpio); if (ret < 0) { dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret); - goto err_disable_clk; + goto err_revert_dir; } cgpio->bypass_orig = ioread32(cgpio->regs + CDNS_GPIO_BYPASS_MODE); @@ -259,9 +252,6 @@ static int cdns_gpio_probe(struct platform_device *pdev) platform_set_drvdata(pdev, cgpio); return 0; -err_disable_clk: - clk_disable_unprepare(cgpio->pclk); - err_revert_dir: iowrite32(dir_prev, cgpio->regs + CDNS_GPIO_DIRECTION_MODE); @@ -273,7 +263,6 @@ static void cdns_gpio_remove(struct platform_device *pdev) struct cdns_gpio_chip *cgpio = platform_get_drvdata(pdev); iowrite32(cgpio->bypass_orig, cgpio->regs + CDNS_GPIO_BYPASS_MODE); - clk_disable_unprepare(cgpio->pclk); } static const struct of_device_id cdns_of_ids[] = { From 8abc67adc9ac1ba4d322b24e00027b8ccdba25c0 Mon Sep 17 00:00:00 2001 From: Zhang Zekun Date: Wed, 4 Sep 2024 17:23:09 +0800 Subject: [PATCH 405/573] gpio: lpc18xx: Use helper function devm_clk_get_enabled() devm_clk_get() and clk_prepare_enable() can be replaced by helper function devm_clk_get_enabled(). Let's use devm_clk_get_enabled() to simplify code and avoid calling clk_disable_unprepare(). Signed-off-by: Zhang Zekun Link: https://lore.kernel.org/r/20240904092311.9544-3-zhangzekun11@huawei.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-lpc18xx.c | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/drivers/gpio/gpio-lpc18xx.c b/drivers/gpio/gpio-lpc18xx.c index 5c6bb57a8c99..e7c0ef6e54fa 100644 --- a/drivers/gpio/gpio-lpc18xx.c +++ b/drivers/gpio/gpio-lpc18xx.c @@ -47,7 +47,6 @@ struct lpc18xx_gpio_pin_ic { struct lpc18xx_gpio_chip { struct gpio_chip gpio; void __iomem *base; - struct clk *clk; struct lpc18xx_gpio_pin_ic *pin_ic; spinlock_t lock; }; @@ -328,6 +327,7 @@ static int lpc18xx_gpio_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct lpc18xx_gpio_chip *gc; int index, ret; + struct clk *clk; gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL); if (!gc) @@ -352,16 +352,10 @@ static int lpc18xx_gpio_probe(struct platform_device *pdev) if (IS_ERR(gc->base)) return PTR_ERR(gc->base); - gc->clk = devm_clk_get(dev, NULL); - if (IS_ERR(gc->clk)) { + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) { dev_err(dev, "input clock not found\n"); - return PTR_ERR(gc->clk); - } - - ret = clk_prepare_enable(gc->clk); - if (ret) { - dev_err(dev, "unable to enable clock\n"); - return ret; + return PTR_ERR(clk); } spin_lock_init(&gc->lock); @@ -369,11 +363,8 @@ static int lpc18xx_gpio_probe(struct platform_device *pdev) gc->gpio.parent = dev; ret = devm_gpiochip_add_data(dev, &gc->gpio, gc); - if (ret) { - dev_err(dev, "failed to add gpio chip\n"); - clk_disable_unprepare(gc->clk); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to add gpio chip\n"); /* On error GPIO pin interrupt controller just won't be registered */ lpc18xx_gpio_pin_ic_probe(gc); @@ -387,8 +378,6 @@ static void lpc18xx_gpio_remove(struct platform_device *pdev) if (gc->pin_ic) irq_domain_remove(gc->pin_ic->domain); - - clk_disable_unprepare(gc->clk); } static const struct of_device_id lpc18xx_gpio_match[] = { From 162b169656039027986f16b82a20745523dbf891 Mon Sep 17 00:00:00 2001 From: Zhang Zekun Date: Wed, 4 Sep 2024 17:23:10 +0800 Subject: [PATCH 406/573] gpio: mb86s7x: Use helper function devm_clk_get_optional_enabled() devm_clk_get_optional() and clk_prepare_enable() can be replaced by helper function devm_clk_get_optional_enabled(). Let's simplify code with use of devm_clk_get_optional_enabled() and avoid calling clk_disable_unprepare(). Signed-off-by: Zhang Zekun Link: https://lore.kernel.org/r/20240904092311.9544-4-zhangzekun11@huawei.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mb86s7x.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/drivers/gpio/gpio-mb86s7x.c b/drivers/gpio/gpio-mb86s7x.c index 7fb298b4571b..ccbb63c21d6f 100644 --- a/drivers/gpio/gpio-mb86s7x.c +++ b/drivers/gpio/gpio-mb86s7x.c @@ -35,7 +35,6 @@ struct mb86s70_gpio_chip { struct gpio_chip gc; void __iomem *base; - struct clk *clk; spinlock_t lock; }; @@ -157,6 +156,7 @@ static int mb86s70_gpio_to_irq(struct gpio_chip *gc, unsigned int offset) static int mb86s70_gpio_probe(struct platform_device *pdev) { struct mb86s70_gpio_chip *gchip; + struct clk *clk; int ret; gchip = devm_kzalloc(&pdev->dev, sizeof(*gchip), GFP_KERNEL); @@ -169,13 +169,9 @@ static int mb86s70_gpio_probe(struct platform_device *pdev) if (IS_ERR(gchip->base)) return PTR_ERR(gchip->base); - gchip->clk = devm_clk_get_optional(&pdev->dev, NULL); - if (IS_ERR(gchip->clk)) - return PTR_ERR(gchip->clk); - - ret = clk_prepare_enable(gchip->clk); - if (ret) - return ret; + clk = devm_clk_get_optional_enabled(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); spin_lock_init(&gchip->lock); @@ -193,11 +189,9 @@ static int mb86s70_gpio_probe(struct platform_device *pdev) gchip->gc.base = -1; ret = gpiochip_add_data(&gchip->gc, gchip); - if (ret) { - dev_err(&pdev->dev, "couldn't register gpio driver\n"); - clk_disable_unprepare(gchip->clk); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "couldn't register gpio driver\n"); acpi_gpiochip_request_interrupts(&gchip->gc); @@ -210,7 +204,6 @@ static void mb86s70_gpio_remove(struct platform_device *pdev) acpi_gpiochip_free_interrupts(&gchip->gc); gpiochip_remove(&gchip->gc); - clk_disable_unprepare(gchip->clk); } static const struct of_device_id mb86s70_gpio_dt_ids[] = { From 4e26ddab828f82f5ebf0fe6d92a6983ca13c519a Mon Sep 17 00:00:00 2001 From: Zhang Zekun Date: Wed, 4 Sep 2024 17:23:11 +0800 Subject: [PATCH 407/573] gpio: xilinx: Use helper function devm_clk_get_optional_enabled() devm_clk_get_optional() and clk_prepare_enable() can be replaced by helper function devm_clk_get_optional_enabled(). Let's simplify code with use of devm_clk_get_optional_enabled() and avoid calling clk_disable_unprepare(). Signed-off-by: Zhang Zekun Link: https://lore.kernel.org/r/20240904092311.9544-5-zhangzekun11@huawei.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-xilinx.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/drivers/gpio/gpio-xilinx.c b/drivers/gpio/gpio-xilinx.c index 7348df385198..afcf432a1573 100644 --- a/drivers/gpio/gpio-xilinx.c +++ b/drivers/gpio/gpio-xilinx.c @@ -333,12 +333,9 @@ static int __maybe_unused xgpio_suspend(struct device *dev) */ static void xgpio_remove(struct platform_device *pdev) { - struct xgpio_instance *gpio = platform_get_drvdata(pdev); - pm_runtime_get_sync(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); - clk_disable_unprepare(gpio->clk); } /** @@ -644,15 +641,10 @@ static int xgpio_probe(struct platform_device *pdev) return PTR_ERR(chip->regs); } - chip->clk = devm_clk_get_optional(&pdev->dev, NULL); + chip->clk = devm_clk_get_optional_enabled(&pdev->dev, NULL); if (IS_ERR(chip->clk)) return dev_err_probe(&pdev->dev, PTR_ERR(chip->clk), "input clock not found.\n"); - status = clk_prepare_enable(chip->clk); - if (status < 0) { - dev_err(&pdev->dev, "Failed to prepare clk\n"); - return status; - } pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); @@ -699,7 +691,6 @@ skip_irq: err_pm_put: pm_runtime_disable(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); - clk_disable_unprepare(chip->clk); return status; } From 48f703d6a3d7cf345fe9c6209ea3703fe9024628 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 5 Sep 2024 16:28:59 +0300 Subject: [PATCH 408/573] power: supply: max1720x: fix a double free on error in probe() In this code, if devm_add_action_or_reset() fails, it will call max1720x_unregister_ancillary() which in turn calls i2c_unregister_device(). Thus the call to i2c_unregister_device() on the following line is not required and is a double unregister. Delete it. Fixes: 47271a935619 ("power: supply: max1720x: add read support for nvmem") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/9c2f76e7-5679-473b-9b9c-e11b492b96ac@stanley.mountain Signed-off-by: Sebastian Reichel --- drivers/power/supply/max1720x_battery.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/power/supply/max1720x_battery.c b/drivers/power/supply/max1720x_battery.c index 3e84e70340e4..2bc3dce963a3 100644 --- a/drivers/power/supply/max1720x_battery.c +++ b/drivers/power/supply/max1720x_battery.c @@ -427,7 +427,6 @@ static int max1720x_probe_nvmem(struct i2c_client *client, ret = devm_add_action_or_reset(dev, max1720x_unregister_ancillary, info); if (ret) { - i2c_unregister_device(info->ancillary); dev_err(dev, "Failed to add unregister callback\n"); return ret; } From eb1ea1351da0196e355391b4aa7f8a58536f16e6 Mon Sep 17 00:00:00 2001 From: Hongbo Li Date: Wed, 4 Sep 2024 09:14:34 +0800 Subject: [PATCH 409/573] power: supply: ab8500: Constify struct kobj_type This 'struct kobj_type' is not modified. It is only used in kobject_init_and_add() which takes a 'const struct kobj_type *ktype' parameter. Constifying this structure and moving it to a read-only section, and can increase over all security. Signed-off-by: Hongbo Li Link: https://lore.kernel.org/r/20240904011434.2010118-1-lihongbo22@huawei.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 270874eeb934..a71903b1bf78 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -2531,7 +2531,7 @@ static struct attribute *ab8500_fg_attrs[] = { }; ATTRIBUTE_GROUPS(ab8500_fg); -static struct kobj_type ab8500_fg_ktype = { +static const struct kobj_type ab8500_fg_ktype = { .sysfs_ops = &ab8500_fg_sysfs_ops, .default_groups = ab8500_fg_groups, }; From 62c0b1061593d7012292f781f11145b2d46f43ab Mon Sep 17 00:00:00 2001 From: Luo Gengkun Date: Sat, 31 Aug 2024 07:43:15 +0000 Subject: [PATCH 410/573] perf/core: Fix small negative period being ignored In perf_adjust_period, we will first calculate period, and then use this period to calculate delta. However, when delta is less than 0, there will be a deviation compared to when delta is greater than or equal to 0. For example, when delta is in the range of [-14,-1], the range of delta = delta + 7 is between [-7,6], so the final value of delta/8 is 0. Therefore, the impact of -1 and -2 will be ignored. This is unacceptable when the target period is very short, because we will lose a lot of samples. Here are some tests and analyzes: before: # perf record -e cs -F 1000 ./a.out [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.022 MB perf.data (518 samples) ] # perf script ... a.out 396 257.956048: 23 cs: ffffffff81f4eeec schedul> a.out 396 257.957891: 23 cs: ffffffff81f4eeec schedul> a.out 396 257.959730: 23 cs: ffffffff81f4eeec schedul> a.out 396 257.961545: 23 cs: ffffffff81f4eeec schedul> a.out 396 257.963355: 23 cs: ffffffff81f4eeec schedul> a.out 396 257.965163: 23 cs: ffffffff81f4eeec schedul> a.out 396 257.966973: 23 cs: ffffffff81f4eeec schedul> a.out 396 257.968785: 23 cs: ffffffff81f4eeec schedul> a.out 396 257.970593: 23 cs: ffffffff81f4eeec schedul> ... after: # perf record -e cs -F 1000 ./a.out [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.058 MB perf.data (1466 samples) ] # perf script ... a.out 395 59.338813: 11 cs: ffffffff81f4eeec schedul> a.out 395 59.339707: 12 cs: ffffffff81f4eeec schedul> a.out 395 59.340682: 13 cs: ffffffff81f4eeec schedul> a.out 395 59.341751: 13 cs: ffffffff81f4eeec schedul> a.out 395 59.342799: 12 cs: ffffffff81f4eeec schedul> a.out 395 59.343765: 11 cs: ffffffff81f4eeec schedul> a.out 395 59.344651: 11 cs: ffffffff81f4eeec schedul> a.out 395 59.345539: 12 cs: ffffffff81f4eeec schedul> a.out 395 59.346502: 13 cs: ffffffff81f4eeec schedul> ... test.c int main() { for (int i = 0; i < 20000; i++) usleep(10); return 0; } # time ./a.out real 0m1.583s user 0m0.040s sys 0m0.298s The above results were tested on x86-64 qemu with KVM enabled using test.c as test program. Ideally, we should have around 1500 samples, but the previous algorithm had only about 500, whereas the modified algorithm now has about 1400. Further more, the new version shows 1 sample per 0.001s, while the previous one is 1 sample per 0.002s.This indicates that the new algorithm is more sensitive to small negative values compared to old algorithm. Fixes: bd2b5b12849a ("perf_counter: More aggressive frequency adjustment") Signed-off-by: Luo Gengkun Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Adrian Hunter Reviewed-by: Kan Liang Cc: stable@vger.kernel.org Link: https://lkml.kernel.org/r/20240831074316.2106159-2-luogengkun@huaweicloud.com --- kernel/events/core.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index 4acec97e5448..67e115d4ef96 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -4183,7 +4183,11 @@ static void perf_adjust_period(struct perf_event *event, u64 nsec, u64 count, bo period = perf_calculate_period(event, nsec, count); delta = (s64)(period - hwc->sample_period); - delta = (delta + 7) / 8; /* low pass filter */ + if (delta >= 0) + delta += 7; + else + delta -= 7; + delta /= 8; /* low pass filter */ sample_period = hwc->sample_period + delta; From 5fe6e308abaea082c20fbf2aa5df8e14495622cf Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Tue, 13 Aug 2024 17:25:24 +0200 Subject: [PATCH 411/573] bpf: Fix use-after-free in bpf_uprobe_multi_link_attach() If bpf_link_prime() fails, bpf_uprobe_multi_link_attach() goes to the error_free label and frees the array of bpf_uprobe's without calling bpf_uprobe_unregister(). This leaks bpf_uprobe->uprobe and worse, this frees bpf_uprobe->consumer without removing it from the uprobe->consumers list. Fixes: 89ae89f53d20 ("bpf: Add multi uprobe link") Closes: https://lore.kernel.org/all/000000000000382d39061f59f2dd@google.com/ Reported-by: syzbot+f7a1c2c2711e4a780f19@syzkaller.appspotmail.com Signed-off-by: Oleg Nesterov Signed-off-by: Peter Zijlstra (Intel) Acked-by: Andrii Nakryiko Acked-by: Jiri Olsa Tested-by: syzbot+f7a1c2c2711e4a780f19@syzkaller.appspotmail.com Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20240813152524.GA7292@redhat.com --- kernel/trace/bpf_trace.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c index 4e391daafa64..90cd30e9723e 100644 --- a/kernel/trace/bpf_trace.c +++ b/kernel/trace/bpf_trace.c @@ -3484,17 +3484,20 @@ int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *pr &uprobes[i].consumer); if (IS_ERR(uprobes[i].uprobe)) { err = PTR_ERR(uprobes[i].uprobe); - bpf_uprobe_unregister(uprobes, i); - goto error_free; + link->cnt = i; + goto error_unregister; } } err = bpf_link_prime(&link->link, &link_primer); if (err) - goto error_free; + goto error_unregister; return bpf_link_settle(&link_primer); +error_unregister: + bpf_uprobe_unregister(uprobes, link->cnt); + error_free: kvfree(uprobes); kfree(link); From 3f7f1a64da731749f1bbd7424b44c1ec6191f21c Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Tue, 3 Sep 2024 10:45:56 -0700 Subject: [PATCH 412/573] uprobes: revamp uprobe refcounting and lifetime management Revamp how struct uprobe is refcounted, and thus how its lifetime is managed. Right now, there are a few possible "owners" of uprobe refcount: - uprobes_tree RB tree assumes one refcount when uprobe is registered and added to the lookup tree; - while uprobe is triggered and kernel is handling it in the breakpoint handler code, temporary refcount bump is done to keep uprobe from being freed; - if we have uretprobe requested on a given struct uprobe instance, we take another refcount to keep uprobe alive until user space code returns from the function and triggers return handler. The uprobe_tree's extra refcount of 1 is confusing and problematic. No matter how many actual consumers are attached, they all share the same refcount, and we have an extra logic to drop the "last" (which might not really be last) refcount once uprobe's consumer list becomes empty. This is unconventional and has to be kept in mind as a special case all the time. Further, because of this design we have the situations where find_uprobe() will find uprobe, bump refcount, return it to the caller, but that uprobe will still need uprobe_is_active() check, after which the caller is required to drop refcount and try again. This is just too many details leaking to the higher level logic. This patch changes refcounting scheme in such a way as to not have uprobes_tree keeping extra refcount for struct uprobe. Instead, each uprobe_consumer is assuming its own refcount, which will be dropped when consumer is unregistered. Other than that, all the active users of uprobe (entry and return uprobe handling code) keeps exactly the same refcounting approach. With the above setup, once uprobe's refcount drops to zero, we need to make sure that uprobe's "destructor" removes uprobe from uprobes_tree, of course. This, though, races with uprobe entry handling code in handle_swbp(), which, through find_active_uprobe()->find_uprobe() lookup, can race with uprobe being destroyed after refcount drops to zero (e.g., due to uprobe_consumer unregistering). So we add try_get_uprobe(), which will attempt to bump refcount, unless it already is zero. Caller needs to guarantee that uprobe instance won't be freed in parallel, which is the case while we keep uprobes_treelock (for read or write, doesn't matter). Note also, we now don't leak the race between registration and unregistration, so we remove the retry logic completely. If find_uprobe() returns valid uprobe, it's guaranteed to remain in uprobes_tree with properly incremented refcount. The race is handled inside __insert_uprobe() and put_uprobe() working together: __insert_uprobe() will remove uprobe from RB-tree, if it can't bump refcount and will retry to insert the new uprobe instance. put_uprobe() won't attempt to remove uprobe from RB-tree, if it's already not there. All that is protected by uprobes_treelock, which keeps things simple. Signed-off-by: Andrii Nakryiko Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Oleg Nesterov Link: https://lore.kernel.org/r/20240903174603.3554182-2-andrii@kernel.org --- kernel/events/uprobes.c | 179 +++++++++++++++++++++++----------------- 1 file changed, 101 insertions(+), 78 deletions(-) diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index cac45ea4c284..cd92e8dc3ed0 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -109,6 +109,11 @@ struct xol_area { unsigned long vaddr; /* Page(s) of instruction slots */ }; +static void uprobe_warn(struct task_struct *t, const char *msg) +{ + pr_warn("uprobe: %s:%d failed to %s\n", current->comm, current->pid, msg); +} + /* * valid_vma: Verify if the specified vma is an executable vma * Relax restrictions while unregistering: vm_flags might have @@ -587,25 +592,53 @@ set_orig_insn(struct arch_uprobe *auprobe, struct mm_struct *mm, unsigned long v *(uprobe_opcode_t *)&auprobe->insn); } +/* uprobe should have guaranteed positive refcount */ static struct uprobe *get_uprobe(struct uprobe *uprobe) { refcount_inc(&uprobe->ref); return uprobe; } +/* + * uprobe should have guaranteed lifetime, which can be either of: + * - caller already has refcount taken (and wants an extra one); + * - uprobe is RCU protected and won't be freed until after grace period; + * - we are holding uprobes_treelock (for read or write, doesn't matter). + */ +static struct uprobe *try_get_uprobe(struct uprobe *uprobe) +{ + if (refcount_inc_not_zero(&uprobe->ref)) + return uprobe; + return NULL; +} + +static inline bool uprobe_is_active(struct uprobe *uprobe) +{ + return !RB_EMPTY_NODE(&uprobe->rb_node); +} + static void put_uprobe(struct uprobe *uprobe) { - if (refcount_dec_and_test(&uprobe->ref)) { - /* - * If application munmap(exec_vma) before uprobe_unregister() - * gets called, we don't get a chance to remove uprobe from - * delayed_uprobe_list from remove_breakpoint(). Do it here. - */ - mutex_lock(&delayed_uprobe_lock); - delayed_uprobe_remove(uprobe, NULL); - mutex_unlock(&delayed_uprobe_lock); - kfree(uprobe); - } + if (!refcount_dec_and_test(&uprobe->ref)) + return; + + write_lock(&uprobes_treelock); + + if (uprobe_is_active(uprobe)) + rb_erase(&uprobe->rb_node, &uprobes_tree); + + write_unlock(&uprobes_treelock); + + /* + * If application munmap(exec_vma) before uprobe_unregister() + * gets called, we don't get a chance to remove uprobe from + * delayed_uprobe_list from remove_breakpoint(). Do it here. + */ + mutex_lock(&delayed_uprobe_lock); + delayed_uprobe_remove(uprobe, NULL); + mutex_unlock(&delayed_uprobe_lock); + + kfree(uprobe); } static __always_inline @@ -656,7 +689,7 @@ static struct uprobe *__find_uprobe(struct inode *inode, loff_t offset) struct rb_node *node = rb_find(&key, &uprobes_tree, __uprobe_cmp_key); if (node) - return get_uprobe(__node_2_uprobe(node)); + return try_get_uprobe(__node_2_uprobe(node)); return NULL; } @@ -676,26 +709,44 @@ static struct uprobe *find_uprobe(struct inode *inode, loff_t offset) return uprobe; } +/* + * Attempt to insert a new uprobe into uprobes_tree. + * + * If uprobe already exists (for given inode+offset), we just increment + * refcount of previously existing uprobe. + * + * If not, a provided new instance of uprobe is inserted into the tree (with + * assumed initial refcount == 1). + * + * In any case, we return a uprobe instance that ends up being in uprobes_tree. + * Caller has to clean up new uprobe instance, if it ended up not being + * inserted into the tree. + * + * We assume that uprobes_treelock is held for writing. + */ static struct uprobe *__insert_uprobe(struct uprobe *uprobe) { struct rb_node *node; - +again: node = rb_find_add(&uprobe->rb_node, &uprobes_tree, __uprobe_cmp); - if (node) - return get_uprobe(__node_2_uprobe(node)); + if (node) { + struct uprobe *u = __node_2_uprobe(node); - /* get access + creation ref */ - refcount_set(&uprobe->ref, 2); - return NULL; + if (!try_get_uprobe(u)) { + rb_erase(node, &uprobes_tree); + RB_CLEAR_NODE(&u->rb_node); + goto again; + } + + return u; + } + + return uprobe; } /* - * Acquire uprobes_treelock. - * Matching uprobe already exists in rbtree; - * increment (access refcount) and return the matching uprobe. - * - * No matching uprobe; insert the uprobe in rb_tree; - * get a double refcount (access + creation) and return NULL. + * Acquire uprobes_treelock and insert uprobe into uprobes_tree + * (or reuse existing one, see __insert_uprobe() comments above). */ static struct uprobe *insert_uprobe(struct uprobe *uprobe) { @@ -732,11 +783,13 @@ static struct uprobe *alloc_uprobe(struct inode *inode, loff_t offset, uprobe->ref_ctr_offset = ref_ctr_offset; init_rwsem(&uprobe->register_rwsem); init_rwsem(&uprobe->consumer_rwsem); + RB_CLEAR_NODE(&uprobe->rb_node); + refcount_set(&uprobe->ref, 1); /* add to uprobes_tree, sorted on inode:offset */ cur_uprobe = insert_uprobe(uprobe); /* a uprobe exists for this inode:offset combination */ - if (cur_uprobe) { + if (cur_uprobe != uprobe) { if (cur_uprobe->ref_ctr_offset != uprobe->ref_ctr_offset) { ref_ctr_mismatch_warn(cur_uprobe, uprobe); put_uprobe(cur_uprobe); @@ -921,26 +974,6 @@ remove_breakpoint(struct uprobe *uprobe, struct mm_struct *mm, unsigned long vad return set_orig_insn(&uprobe->arch, mm, vaddr); } -static inline bool uprobe_is_active(struct uprobe *uprobe) -{ - return !RB_EMPTY_NODE(&uprobe->rb_node); -} -/* - * There could be threads that have already hit the breakpoint. They - * will recheck the current insn and restart if find_uprobe() fails. - * See find_active_uprobe(). - */ -static void delete_uprobe(struct uprobe *uprobe) -{ - if (WARN_ON(!uprobe_is_active(uprobe))) - return; - - write_lock(&uprobes_treelock); - rb_erase(&uprobe->rb_node, &uprobes_tree); - write_unlock(&uprobes_treelock); - RB_CLEAR_NODE(&uprobe->rb_node); /* for uprobe_is_active() */ -} - struct map_info { struct map_info *next; struct mm_struct *mm; @@ -1094,17 +1127,13 @@ void uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc) int err; down_write(&uprobe->register_rwsem); - if (WARN_ON(!consumer_del(uprobe, uc))) + if (WARN_ON(!consumer_del(uprobe, uc))) { err = -ENOENT; - else + } else { err = register_for_each_vma(uprobe, NULL); - - /* TODO : cant unregister? schedule a worker thread */ - if (!err) { - if (!uprobe->consumers) - delete_uprobe(uprobe); - else - err = -EBUSY; + /* TODO : cant unregister? schedule a worker thread */ + if (unlikely(err)) + uprobe_warn(current, "unregister, leaking uprobe"); } up_write(&uprobe->register_rwsem); @@ -1159,27 +1188,16 @@ struct uprobe *uprobe_register(struct inode *inode, if (!IS_ALIGNED(ref_ctr_offset, sizeof(short))) return ERR_PTR(-EINVAL); - retry: uprobe = alloc_uprobe(inode, offset, ref_ctr_offset); if (IS_ERR(uprobe)) return uprobe; - /* - * We can race with uprobe_unregister()->delete_uprobe(). - * Check uprobe_is_active() and retry if it is false. - */ down_write(&uprobe->register_rwsem); - ret = -EAGAIN; - if (likely(uprobe_is_active(uprobe))) { - consumer_add(uprobe, uc); - ret = register_for_each_vma(uprobe, uc); - } + consumer_add(uprobe, uc); + ret = register_for_each_vma(uprobe, uc); up_write(&uprobe->register_rwsem); - put_uprobe(uprobe); if (ret) { - if (unlikely(ret == -EAGAIN)) - goto retry; uprobe_unregister(uprobe, uc); return ERR_PTR(ret); } @@ -1286,15 +1304,17 @@ static void build_probe_list(struct inode *inode, u = rb_entry(t, struct uprobe, rb_node); if (u->inode != inode || u->offset < min) break; - list_add(&u->pending_list, head); - get_uprobe(u); + /* if uprobe went away, it's safe to ignore it */ + if (try_get_uprobe(u)) + list_add(&u->pending_list, head); } for (t = n; (t = rb_next(t)); ) { u = rb_entry(t, struct uprobe, rb_node); if (u->inode != inode || u->offset > max) break; - list_add(&u->pending_list, head); - get_uprobe(u); + /* if uprobe went away, it's safe to ignore it */ + if (try_get_uprobe(u)) + list_add(&u->pending_list, head); } } read_unlock(&uprobes_treelock); @@ -1751,6 +1771,12 @@ static int dup_utask(struct task_struct *t, struct uprobe_task *o_utask) return -ENOMEM; *n = *o; + /* + * uprobe's refcnt has to be positive at this point, kept by + * utask->return_instances items; return_instances can't be + * removed right now, as task is blocked due to duping; so + * get_uprobe() is safe to use here. + */ get_uprobe(n->uprobe); n->next = NULL; @@ -1762,12 +1788,6 @@ static int dup_utask(struct task_struct *t, struct uprobe_task *o_utask) return 0; } -static void uprobe_warn(struct task_struct *t, const char *msg) -{ - pr_warn("uprobe: %s:%d failed to %s\n", - current->comm, current->pid, msg); -} - static void dup_xol_work(struct callback_head *work) { if (current->flags & PF_EXITING) @@ -1893,7 +1913,10 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs) } orig_ret_vaddr = utask->return_instances->orig_ret_vaddr; } - + /* + * uprobe's refcnt is positive, held by caller, so it's safe to + * unconditionally bump it one more time here + */ ri->uprobe = get_uprobe(uprobe); ri->func = instruction_pointer(regs); ri->stack = user_stack_pointer(regs); From 8617408f7a01e94ce1f73e40a7704530e5dfb25c Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Tue, 3 Sep 2024 10:45:57 -0700 Subject: [PATCH 413/573] uprobes: protected uprobe lifetime with SRCU To avoid unnecessarily taking a (brief) refcount on uprobe during breakpoint handling in handle_swbp for entry uprobes, make find_uprobe() not take refcount, but protect the lifetime of a uprobe instance with RCU. This improves scalability, as refcount gets quite expensive due to cache line bouncing between multiple CPUs. Specifically, we utilize our own uprobe-specific SRCU instance for this RCU protection. put_uprobe() will delay actual kfree() using call_srcu(). For now, uretprobe and single-stepping handling will still acquire refcount as necessary. We'll address these issues in follow up patches by making them use SRCU with timeout. Signed-off-by: Andrii Nakryiko Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Oleg Nesterov Link: https://lore.kernel.org/r/20240903174603.3554182-3-andrii@kernel.org --- kernel/events/uprobes.c | 94 +++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index cd92e8dc3ed0..d228d2ba30bb 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -41,6 +41,8 @@ static struct rb_root uprobes_tree = RB_ROOT; static DEFINE_RWLOCK(uprobes_treelock); /* serialize rbtree access */ +DEFINE_STATIC_SRCU(uprobes_srcu); + #define UPROBES_HASH_SZ 13 /* serialize uprobe->pending_list */ static struct mutex uprobes_mmap_mutex[UPROBES_HASH_SZ]; @@ -59,6 +61,7 @@ struct uprobe { struct list_head pending_list; struct uprobe_consumer *consumers; struct inode *inode; /* Also hold a ref to inode */ + struct rcu_head rcu; loff_t offset; loff_t ref_ctr_offset; unsigned long flags; @@ -617,6 +620,13 @@ static inline bool uprobe_is_active(struct uprobe *uprobe) return !RB_EMPTY_NODE(&uprobe->rb_node); } +static void uprobe_free_rcu(struct rcu_head *rcu) +{ + struct uprobe *uprobe = container_of(rcu, struct uprobe, rcu); + + kfree(uprobe); +} + static void put_uprobe(struct uprobe *uprobe) { if (!refcount_dec_and_test(&uprobe->ref)) @@ -638,7 +648,7 @@ static void put_uprobe(struct uprobe *uprobe) delayed_uprobe_remove(uprobe, NULL); mutex_unlock(&delayed_uprobe_lock); - kfree(uprobe); + call_srcu(&uprobes_srcu, &uprobe->rcu, uprobe_free_rcu); } static __always_inline @@ -680,33 +690,25 @@ static inline int __uprobe_cmp(struct rb_node *a, const struct rb_node *b) return uprobe_cmp(u->inode, u->offset, __node_2_uprobe(b)); } -static struct uprobe *__find_uprobe(struct inode *inode, loff_t offset) +/* + * Assumes being inside RCU protected region. + * No refcount is taken on returned uprobe. + */ +static struct uprobe *find_uprobe_rcu(struct inode *inode, loff_t offset) { struct __uprobe_key key = { .inode = inode, .offset = offset, }; - struct rb_node *node = rb_find(&key, &uprobes_tree, __uprobe_cmp_key); + struct rb_node *node; - if (node) - return try_get_uprobe(__node_2_uprobe(node)); - - return NULL; -} - -/* - * Find a uprobe corresponding to a given inode:offset - * Acquires uprobes_treelock - */ -static struct uprobe *find_uprobe(struct inode *inode, loff_t offset) -{ - struct uprobe *uprobe; + lockdep_assert(srcu_read_lock_held(&uprobes_srcu)); read_lock(&uprobes_treelock); - uprobe = __find_uprobe(inode, offset); + node = rb_find(&key, &uprobes_tree, __uprobe_cmp_key); read_unlock(&uprobes_treelock); - return uprobe; + return node ? __node_2_uprobe(node) : NULL; } /* @@ -1080,10 +1082,10 @@ register_for_each_vma(struct uprobe *uprobe, struct uprobe_consumer *new) goto free; /* * We take mmap_lock for writing to avoid the race with - * find_active_uprobe() which takes mmap_lock for reading. + * find_active_uprobe_rcu() which takes mmap_lock for reading. * Thus this install_breakpoint() can not make - * is_trap_at_addr() true right after find_uprobe() - * returns NULL in find_active_uprobe(). + * is_trap_at_addr() true right after find_uprobe_rcu() + * returns NULL in find_active_uprobe_rcu(). */ mmap_write_lock(mm); vma = find_vma(mm, info->vaddr); @@ -1884,9 +1886,13 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs) return; } + /* we need to bump refcount to store uprobe in utask */ + if (!try_get_uprobe(uprobe)) + return; + ri = kmalloc(sizeof(struct return_instance), GFP_KERNEL); if (!ri) - return; + goto fail; trampoline_vaddr = uprobe_get_trampoline_vaddr(); orig_ret_vaddr = arch_uretprobe_hijack_return_addr(trampoline_vaddr, regs); @@ -1913,11 +1919,7 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs) } orig_ret_vaddr = utask->return_instances->orig_ret_vaddr; } - /* - * uprobe's refcnt is positive, held by caller, so it's safe to - * unconditionally bump it one more time here - */ - ri->uprobe = get_uprobe(uprobe); + ri->uprobe = uprobe; ri->func = instruction_pointer(regs); ri->stack = user_stack_pointer(regs); ri->orig_ret_vaddr = orig_ret_vaddr; @@ -1928,8 +1930,9 @@ static void prepare_uretprobe(struct uprobe *uprobe, struct pt_regs *regs) utask->return_instances = ri; return; - fail: +fail: kfree(ri); + put_uprobe(uprobe); } /* Prepare to single-step probed instruction out of line. */ @@ -1944,9 +1947,14 @@ pre_ssout(struct uprobe *uprobe, struct pt_regs *regs, unsigned long bp_vaddr) if (!utask) return -ENOMEM; + if (!try_get_uprobe(uprobe)) + return -EINVAL; + xol_vaddr = xol_get_insn_slot(uprobe); - if (!xol_vaddr) - return -ENOMEM; + if (!xol_vaddr) { + err = -ENOMEM; + goto err_out; + } utask->xol_vaddr = xol_vaddr; utask->vaddr = bp_vaddr; @@ -1954,12 +1962,15 @@ pre_ssout(struct uprobe *uprobe, struct pt_regs *regs, unsigned long bp_vaddr) err = arch_uprobe_pre_xol(&uprobe->arch, regs); if (unlikely(err)) { xol_free_insn_slot(current); - return err; + goto err_out; } utask->active_uprobe = uprobe; utask->state = UTASK_SSTEP; return 0; +err_out: + put_uprobe(uprobe); + return err; } /* @@ -2043,7 +2054,8 @@ static int is_trap_at_addr(struct mm_struct *mm, unsigned long vaddr) return is_trap_insn(&opcode); } -static struct uprobe *find_active_uprobe(unsigned long bp_vaddr, int *is_swbp) +/* assumes being inside RCU protected region */ +static struct uprobe *find_active_uprobe_rcu(unsigned long bp_vaddr, int *is_swbp) { struct mm_struct *mm = current->mm; struct uprobe *uprobe = NULL; @@ -2056,7 +2068,7 @@ static struct uprobe *find_active_uprobe(unsigned long bp_vaddr, int *is_swbp) struct inode *inode = file_inode(vma->vm_file); loff_t offset = vaddr_to_offset(vma, bp_vaddr); - uprobe = find_uprobe(inode, offset); + uprobe = find_uprobe_rcu(inode, offset); } if (!uprobe) @@ -2202,13 +2214,15 @@ static void handle_swbp(struct pt_regs *regs) { struct uprobe *uprobe; unsigned long bp_vaddr; - int is_swbp; + int is_swbp, srcu_idx; bp_vaddr = uprobe_get_swbp_addr(regs); if (bp_vaddr == uprobe_get_trampoline_vaddr()) return uprobe_handle_trampoline(regs); - uprobe = find_active_uprobe(bp_vaddr, &is_swbp); + srcu_idx = srcu_read_lock(&uprobes_srcu); + + uprobe = find_active_uprobe_rcu(bp_vaddr, &is_swbp); if (!uprobe) { if (is_swbp > 0) { /* No matching uprobe; signal SIGTRAP. */ @@ -2224,7 +2238,7 @@ static void handle_swbp(struct pt_regs *regs) */ instruction_pointer_set(regs, bp_vaddr); } - return; + goto out; } /* change it in advance for ->handler() and restart */ @@ -2259,12 +2273,12 @@ static void handle_swbp(struct pt_regs *regs) if (arch_uprobe_skip_sstep(&uprobe->arch, regs)) goto out; - if (!pre_ssout(uprobe, regs, bp_vaddr)) - return; + if (pre_ssout(uprobe, regs, bp_vaddr)) + goto out; - /* arch_uprobe_skip_sstep() succeeded, or restart if can't singlestep */ out: - put_uprobe(uprobe); + /* arch_uprobe_skip_sstep() succeeded, or restart if can't singlestep */ + srcu_read_unlock(&uprobes_srcu, srcu_idx); } /* From 59da880afed211c989ef65da577b24215ce57774 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Tue, 3 Sep 2024 10:45:58 -0700 Subject: [PATCH 414/573] uprobes: get rid of enum uprobe_filter_ctx in uprobe filter callbacks It serves no purpose beyond adding unnecessray argument passed to the filter callback. Just get rid of it, no one is actually using it. Signed-off-by: Andrii Nakryiko Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Oleg Nesterov Link: https://lore.kernel.org/r/20240903174603.3554182-4-andrii@kernel.org --- include/linux/uprobes.h | 10 +--------- kernel/events/uprobes.c | 18 +++++++----------- kernel/trace/bpf_trace.c | 3 +-- kernel/trace/trace_uprobe.c | 9 +++------ 4 files changed, 12 insertions(+), 28 deletions(-) diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h index f50df1fa93e7..63ae2ade3487 100644 --- a/include/linux/uprobes.h +++ b/include/linux/uprobes.h @@ -28,20 +28,12 @@ struct page; #define MAX_URETPROBE_DEPTH 64 -enum uprobe_filter_ctx { - UPROBE_FILTER_REGISTER, - UPROBE_FILTER_UNREGISTER, - UPROBE_FILTER_MMAP, -}; - struct uprobe_consumer { int (*handler)(struct uprobe_consumer *self, struct pt_regs *regs); int (*ret_handler)(struct uprobe_consumer *self, unsigned long func, struct pt_regs *regs); - bool (*filter)(struct uprobe_consumer *self, - enum uprobe_filter_ctx ctx, - struct mm_struct *mm); + bool (*filter)(struct uprobe_consumer *self, struct mm_struct *mm); struct uprobe_consumer *next; }; diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index d228d2ba30bb..87b499cd2334 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -918,21 +918,19 @@ static int prepare_uprobe(struct uprobe *uprobe, struct file *file, return ret; } -static inline bool consumer_filter(struct uprobe_consumer *uc, - enum uprobe_filter_ctx ctx, struct mm_struct *mm) +static inline bool consumer_filter(struct uprobe_consumer *uc, struct mm_struct *mm) { - return !uc->filter || uc->filter(uc, ctx, mm); + return !uc->filter || uc->filter(uc, mm); } -static bool filter_chain(struct uprobe *uprobe, - enum uprobe_filter_ctx ctx, struct mm_struct *mm) +static bool filter_chain(struct uprobe *uprobe, struct mm_struct *mm) { struct uprobe_consumer *uc; bool ret = false; down_read(&uprobe->consumer_rwsem); for (uc = uprobe->consumers; uc; uc = uc->next) { - ret = consumer_filter(uc, ctx, mm); + ret = consumer_filter(uc, mm); if (ret) break; } @@ -1099,12 +1097,10 @@ register_for_each_vma(struct uprobe *uprobe, struct uprobe_consumer *new) if (is_register) { /* consult only the "caller", new consumer. */ - if (consumer_filter(new, - UPROBE_FILTER_REGISTER, mm)) + if (consumer_filter(new, mm)) err = install_breakpoint(uprobe, mm, vma, info->vaddr); } else if (test_bit(MMF_HAS_UPROBES, &mm->flags)) { - if (!filter_chain(uprobe, - UPROBE_FILTER_UNREGISTER, mm)) + if (!filter_chain(uprobe, mm)) err |= remove_breakpoint(uprobe, mm, info->vaddr); } @@ -1387,7 +1383,7 @@ int uprobe_mmap(struct vm_area_struct *vma) */ list_for_each_entry_safe(uprobe, u, &tmp_list, pending_list) { if (!fatal_signal_pending(current) && - filter_chain(uprobe, UPROBE_FILTER_MMAP, vma->vm_mm)) { + filter_chain(uprobe, vma->vm_mm)) { unsigned long vaddr = offset_to_vaddr(vma, uprobe->offset); install_breakpoint(uprobe, vma->vm_mm, vma, vaddr); } diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c index 90cd30e9723e..c99bf0617602 100644 --- a/kernel/trace/bpf_trace.c +++ b/kernel/trace/bpf_trace.c @@ -3320,8 +3320,7 @@ static int uprobe_prog_run(struct bpf_uprobe *uprobe, } static bool -uprobe_multi_link_filter(struct uprobe_consumer *con, enum uprobe_filter_ctx ctx, - struct mm_struct *mm) +uprobe_multi_link_filter(struct uprobe_consumer *con, struct mm_struct *mm) { struct bpf_uprobe *uprobe; diff --git a/kernel/trace/trace_uprobe.c b/kernel/trace/trace_uprobe.c index 52e76a73fa7c..7eb79e0a5352 100644 --- a/kernel/trace/trace_uprobe.c +++ b/kernel/trace/trace_uprobe.c @@ -1078,9 +1078,7 @@ print_uprobe_event(struct trace_iterator *iter, int flags, struct trace_event *e return trace_handle_return(s); } -typedef bool (*filter_func_t)(struct uprobe_consumer *self, - enum uprobe_filter_ctx ctx, - struct mm_struct *mm); +typedef bool (*filter_func_t)(struct uprobe_consumer *self, struct mm_struct *mm); static int trace_uprobe_enable(struct trace_uprobe *tu, filter_func_t filter) { @@ -1339,8 +1337,7 @@ static int uprobe_perf_open(struct trace_event_call *call, return err; } -static bool uprobe_perf_filter(struct uprobe_consumer *uc, - enum uprobe_filter_ctx ctx, struct mm_struct *mm) +static bool uprobe_perf_filter(struct uprobe_consumer *uc, struct mm_struct *mm) { struct trace_uprobe_filter *filter; struct trace_uprobe *tu; @@ -1426,7 +1423,7 @@ static void __uprobe_perf_func(struct trace_uprobe *tu, static int uprobe_perf_func(struct trace_uprobe *tu, struct pt_regs *regs, struct uprobe_cpu_buffer **ucbp) { - if (!uprobe_perf_filter(&tu->consumer, 0, current->mm)) + if (!uprobe_perf_filter(&tu->consumer, current->mm)) return UPROBE_HANDLER_REMOVE; if (!is_ret_probe(tu)) From cc01bd044e6a521d2cd128f685ee8d23ef0067f2 Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Tue, 3 Sep 2024 10:45:59 -0700 Subject: [PATCH 415/573] uprobes: travers uprobe's consumer list locklessly under SRCU protection uprobe->register_rwsem is one of a few big bottlenecks to scalability of uprobes, so we need to get rid of it to improve uprobe performance and multi-CPU scalability. First, we turn uprobe's consumer list to a typical doubly-linked list and utilize existing RCU-aware helpers for traversing such lists, as well as adding and removing elements from it. For entry uprobes we already have SRCU protection active since before uprobe lookup. For uretprobe we keep refcount, guaranteeing that uprobe won't go away from under us, but we add SRCU protection around consumer list traversal. Lastly, to keep handler_chain()'s UPROBE_HANDLER_REMOVE handling simple, we remember whether any removal was requested during handler calls, but then we double-check the decision under a proper register_rwsem using consumers' filter callbacks. Handler removal is very rare, so this extra lock won't hurt performance, overall, but we also avoid the need for any extra protection (e.g., seqcount locks). Signed-off-by: Andrii Nakryiko Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Oleg Nesterov Link: https://lore.kernel.org/r/20240903174603.3554182-5-andrii@kernel.org --- include/linux/uprobes.h | 10 +++- kernel/events/uprobes.c | 104 +++++++++++++++++++++++----------------- 2 files changed, 70 insertions(+), 44 deletions(-) diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h index 63ae2ade3487..f112b56e9479 100644 --- a/include/linux/uprobes.h +++ b/include/linux/uprobes.h @@ -29,13 +29,21 @@ struct page; #define MAX_URETPROBE_DEPTH 64 struct uprobe_consumer { + /* + * handler() can return UPROBE_HANDLER_REMOVE to signal the need to + * unregister uprobe for current process. If UPROBE_HANDLER_REMOVE is + * returned, filter() callback has to be implemented as well and it + * should return false to "confirm" the decision to uninstall uprobe + * for the current process. If filter() is omitted or returns true, + * UPROBE_HANDLER_REMOVE is effectively ignored. + */ int (*handler)(struct uprobe_consumer *self, struct pt_regs *regs); int (*ret_handler)(struct uprobe_consumer *self, unsigned long func, struct pt_regs *regs); bool (*filter)(struct uprobe_consumer *self, struct mm_struct *mm); - struct uprobe_consumer *next; + struct list_head cons_node; }; #ifdef CONFIG_UPROBES diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index 87b499cd2334..e15c0306e535 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -59,7 +59,7 @@ struct uprobe { struct rw_semaphore register_rwsem; struct rw_semaphore consumer_rwsem; struct list_head pending_list; - struct uprobe_consumer *consumers; + struct list_head consumers; struct inode *inode; /* Also hold a ref to inode */ struct rcu_head rcu; loff_t offset; @@ -783,6 +783,7 @@ static struct uprobe *alloc_uprobe(struct inode *inode, loff_t offset, uprobe->inode = inode; uprobe->offset = offset; uprobe->ref_ctr_offset = ref_ctr_offset; + INIT_LIST_HEAD(&uprobe->consumers); init_rwsem(&uprobe->register_rwsem); init_rwsem(&uprobe->consumer_rwsem); RB_CLEAR_NODE(&uprobe->rb_node); @@ -808,32 +809,19 @@ static struct uprobe *alloc_uprobe(struct inode *inode, loff_t offset, static void consumer_add(struct uprobe *uprobe, struct uprobe_consumer *uc) { down_write(&uprobe->consumer_rwsem); - uc->next = uprobe->consumers; - uprobe->consumers = uc; + list_add_rcu(&uc->cons_node, &uprobe->consumers); up_write(&uprobe->consumer_rwsem); } /* * For uprobe @uprobe, delete the consumer @uc. - * Return true if the @uc is deleted successfully - * or return false. + * Should never be called with consumer that's not part of @uprobe->consumers. */ -static bool consumer_del(struct uprobe *uprobe, struct uprobe_consumer *uc) +static void consumer_del(struct uprobe *uprobe, struct uprobe_consumer *uc) { - struct uprobe_consumer **con; - bool ret = false; - down_write(&uprobe->consumer_rwsem); - for (con = &uprobe->consumers; *con; con = &(*con)->next) { - if (*con == uc) { - *con = uc->next; - ret = true; - break; - } - } + list_del_rcu(&uc->cons_node); up_write(&uprobe->consumer_rwsem); - - return ret; } static int __copy_insn(struct address_space *mapping, struct file *filp, @@ -929,7 +917,8 @@ static bool filter_chain(struct uprobe *uprobe, struct mm_struct *mm) bool ret = false; down_read(&uprobe->consumer_rwsem); - for (uc = uprobe->consumers; uc; uc = uc->next) { + list_for_each_entry_srcu(uc, &uprobe->consumers, cons_node, + srcu_read_lock_held(&uprobes_srcu)) { ret = consumer_filter(uc, mm); if (ret) break; @@ -1125,18 +1114,29 @@ void uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc) int err; down_write(&uprobe->register_rwsem); - if (WARN_ON(!consumer_del(uprobe, uc))) { - err = -ENOENT; - } else { - err = register_for_each_vma(uprobe, NULL); - /* TODO : cant unregister? schedule a worker thread */ - if (unlikely(err)) - uprobe_warn(current, "unregister, leaking uprobe"); - } + consumer_del(uprobe, uc); + err = register_for_each_vma(uprobe, NULL); up_write(&uprobe->register_rwsem); - if (!err) - put_uprobe(uprobe); + /* TODO : cant unregister? schedule a worker thread */ + if (unlikely(err)) { + uprobe_warn(current, "unregister, leaking uprobe"); + goto out_sync; + } + + put_uprobe(uprobe); + +out_sync: + /* + * Now that handler_chain() and handle_uretprobe_chain() iterate over + * uprobe->consumers list under RCU protection without holding + * uprobe->register_rwsem, we need to wait for RCU grace period to + * make sure that we can't call into just unregistered + * uprobe_consumer's callbacks anymore. If we don't do that, fast and + * unlucky enough caller can free consumer's memory and cause + * handler_chain() or handle_uretprobe_chain() to do an use-after-free. + */ + synchronize_srcu(&uprobes_srcu); } EXPORT_SYMBOL_GPL(uprobe_unregister); @@ -1214,13 +1214,20 @@ EXPORT_SYMBOL_GPL(uprobe_register); int uprobe_apply(struct uprobe *uprobe, struct uprobe_consumer *uc, bool add) { struct uprobe_consumer *con; - int ret = -ENOENT; + int ret = -ENOENT, srcu_idx; down_write(&uprobe->register_rwsem); - for (con = uprobe->consumers; con && con != uc ; con = con->next) - ; - if (con) - ret = register_for_each_vma(uprobe, add ? uc : NULL); + + srcu_idx = srcu_read_lock(&uprobes_srcu); + list_for_each_entry_srcu(con, &uprobe->consumers, cons_node, + srcu_read_lock_held(&uprobes_srcu)) { + if (con == uc) { + ret = register_for_each_vma(uprobe, add ? uc : NULL); + break; + } + } + srcu_read_unlock(&uprobes_srcu, srcu_idx); + up_write(&uprobe->register_rwsem); return ret; @@ -2085,10 +2092,12 @@ static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs) struct uprobe_consumer *uc; int remove = UPROBE_HANDLER_REMOVE; bool need_prep = false; /* prepare return uprobe, when needed */ + bool has_consumers = false; - down_read(&uprobe->register_rwsem); current->utask->auprobe = &uprobe->arch; - for (uc = uprobe->consumers; uc; uc = uc->next) { + + list_for_each_entry_srcu(uc, &uprobe->consumers, cons_node, + srcu_read_lock_held(&uprobes_srcu)) { int rc = 0; if (uc->handler) { @@ -2101,17 +2110,24 @@ static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs) need_prep = true; remove &= rc; + has_consumers = true; } current->utask->auprobe = NULL; if (need_prep && !remove) prepare_uretprobe(uprobe, regs); /* put bp at return */ - if (remove && uprobe->consumers) { - WARN_ON(!uprobe_is_active(uprobe)); - unapply_uprobe(uprobe, current->mm); + if (remove && has_consumers) { + down_read(&uprobe->register_rwsem); + + /* re-check that removal is still required, this time under lock */ + if (!filter_chain(uprobe, current->mm)) { + WARN_ON(!uprobe_is_active(uprobe)); + unapply_uprobe(uprobe, current->mm); + } + + up_read(&uprobe->register_rwsem); } - up_read(&uprobe->register_rwsem); } static void @@ -2119,13 +2135,15 @@ handle_uretprobe_chain(struct return_instance *ri, struct pt_regs *regs) { struct uprobe *uprobe = ri->uprobe; struct uprobe_consumer *uc; + int srcu_idx; - down_read(&uprobe->register_rwsem); - for (uc = uprobe->consumers; uc; uc = uc->next) { + srcu_idx = srcu_read_lock(&uprobes_srcu); + list_for_each_entry_srcu(uc, &uprobe->consumers, cons_node, + srcu_read_lock_held(&uprobes_srcu)) { if (uc->ret_handler) uc->ret_handler(uc, ri->func, regs); } - up_read(&uprobe->register_rwsem); + srcu_read_unlock(&uprobes_srcu, srcu_idx); } static struct return_instance *find_next_ret_chain(struct return_instance *ri) From 04b01625da130c7521b768996cd5e48052198b97 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 3 Sep 2024 10:46:00 -0700 Subject: [PATCH 416/573] perf/uprobe: split uprobe_unregister() With uprobe_unregister() having grown a synchronize_srcu(), it becomes fairly slow to call. Esp. since both users of this API call it in a loop. Peel off the sync_srcu() and do it once, after the loop. We also need to add uprobe_unregister_sync() into uprobe_register()'s error handling path, as we need to be careful about returning to the caller before we have a guarantee that partially attached consumer won't be called anymore. This is an unlikely slow path and this should be totally fine to be slow in the case of a failed attach. Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: "Peter Zijlstra (Intel)" Co-developed-by: Andrii Nakryiko Signed-off-by: Andrii Nakryiko Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Oleg Nesterov Link: https://lore.kernel.org/r/20240903174603.3554182-6-andrii@kernel.org --- include/linux/uprobes.h | 8 +++++-- kernel/events/uprobes.c | 21 +++++++++++++------ kernel/trace/bpf_trace.c | 5 ++++- kernel/trace/trace_uprobe.c | 6 +++++- .../selftests/bpf/bpf_testmod/bpf_testmod.c | 3 ++- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h index f112b56e9479..2b294bf1881f 100644 --- a/include/linux/uprobes.h +++ b/include/linux/uprobes.h @@ -115,7 +115,8 @@ extern unsigned long uprobe_get_trap_addr(struct pt_regs *regs); extern int uprobe_write_opcode(struct arch_uprobe *auprobe, struct mm_struct *mm, unsigned long vaddr, uprobe_opcode_t); extern struct uprobe *uprobe_register(struct inode *inode, loff_t offset, loff_t ref_ctr_offset, struct uprobe_consumer *uc); extern int uprobe_apply(struct uprobe *uprobe, struct uprobe_consumer *uc, bool); -extern void uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc); +extern void uprobe_unregister_nosync(struct uprobe *uprobe, struct uprobe_consumer *uc); +extern void uprobe_unregister_sync(void); extern int uprobe_mmap(struct vm_area_struct *vma); extern void uprobe_munmap(struct vm_area_struct *vma, unsigned long start, unsigned long end); extern void uprobe_start_dup_mmap(void); @@ -164,7 +165,10 @@ uprobe_apply(struct uprobe* uprobe, struct uprobe_consumer *uc, bool add) return -ENOSYS; } static inline void -uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc) +uprobe_unregister_nosync(struct uprobe *uprobe, struct uprobe_consumer *uc) +{ +} +static inline void uprobe_unregister_sync(void) { } static inline int uprobe_mmap(struct vm_area_struct *vma) diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index e15c0306e535..694f6790e848 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -1105,11 +1105,11 @@ register_for_each_vma(struct uprobe *uprobe, struct uprobe_consumer *new) } /** - * uprobe_unregister - unregister an already registered probe. + * uprobe_unregister_nosync - unregister an already registered probe. * @uprobe: uprobe to remove * @uc: identify which probe if multiple probes are colocated. */ -void uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc) +void uprobe_unregister_nosync(struct uprobe *uprobe, struct uprobe_consumer *uc) { int err; @@ -1121,12 +1121,15 @@ void uprobe_unregister(struct uprobe *uprobe, struct uprobe_consumer *uc) /* TODO : cant unregister? schedule a worker thread */ if (unlikely(err)) { uprobe_warn(current, "unregister, leaking uprobe"); - goto out_sync; + return; } put_uprobe(uprobe); +} +EXPORT_SYMBOL_GPL(uprobe_unregister_nosync); -out_sync: +void uprobe_unregister_sync(void) +{ /* * Now that handler_chain() and handle_uretprobe_chain() iterate over * uprobe->consumers list under RCU protection without holding @@ -1138,7 +1141,7 @@ out_sync: */ synchronize_srcu(&uprobes_srcu); } -EXPORT_SYMBOL_GPL(uprobe_unregister); +EXPORT_SYMBOL_GPL(uprobe_unregister_sync); /** * uprobe_register - register a probe @@ -1196,7 +1199,13 @@ struct uprobe *uprobe_register(struct inode *inode, up_write(&uprobe->register_rwsem); if (ret) { - uprobe_unregister(uprobe, uc); + uprobe_unregister_nosync(uprobe, uc); + /* + * Registration might have partially succeeded, so we can have + * this consumer being called right at this time. We need to + * sync here. It's ok, it's unlikely slow path. + */ + uprobe_unregister_sync(); return ERR_PTR(ret); } diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c index c99bf0617602..ac0a01cc8634 100644 --- a/kernel/trace/bpf_trace.c +++ b/kernel/trace/bpf_trace.c @@ -3184,7 +3184,10 @@ static void bpf_uprobe_unregister(struct bpf_uprobe *uprobes, u32 cnt) u32 i; for (i = 0; i < cnt; i++) - uprobe_unregister(uprobes[i].uprobe, &uprobes[i].consumer); + uprobe_unregister_nosync(uprobes[i].uprobe, &uprobes[i].consumer); + + if (cnt) + uprobe_unregister_sync(); } static void bpf_uprobe_multi_link_release(struct bpf_link *link) diff --git a/kernel/trace/trace_uprobe.c b/kernel/trace/trace_uprobe.c index 7eb79e0a5352..f7443e996b1b 100644 --- a/kernel/trace/trace_uprobe.c +++ b/kernel/trace/trace_uprobe.c @@ -1097,6 +1097,7 @@ static int trace_uprobe_enable(struct trace_uprobe *tu, filter_func_t filter) static void __probe_event_disable(struct trace_probe *tp) { struct trace_uprobe *tu; + bool sync = false; tu = container_of(tp, struct trace_uprobe, tp); WARN_ON(!uprobe_filter_is_empty(tu->tp.event->filter)); @@ -1105,9 +1106,12 @@ static void __probe_event_disable(struct trace_probe *tp) if (!tu->uprobe) continue; - uprobe_unregister(tu->uprobe, &tu->consumer); + uprobe_unregister_nosync(tu->uprobe, &tu->consumer); + sync = true; tu->uprobe = NULL; } + if (sync) + uprobe_unregister_sync(); } static int probe_event_enable(struct trace_event_call *call, diff --git a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c index 3c0515a27842..1fc16657cf42 100644 --- a/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c +++ b/tools/testing/selftests/bpf/bpf_testmod/bpf_testmod.c @@ -475,7 +475,8 @@ static void testmod_unregister_uprobe(void) mutex_lock(&testmod_uprobe_mutex); if (uprobe.uprobe) { - uprobe_unregister(uprobe.uprobe, &uprobe.consumer); + uprobe_unregister_nosync(uprobe.uprobe, &uprobe.consumer); + uprobe_unregister_sync(); path_put(&uprobe.path); uprobe.uprobe = NULL; } From 50a38035ed5ccc2ab8a28eaf70c3c7a87e060345 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 3 Sep 2024 10:46:01 -0700 Subject: [PATCH 417/573] rbtree: provide rb_find_rcu() / rb_find_add_rcu() Much like latch_tree, add two RCU methods for the regular RB-tree, which can be used in conjunction with a seqcount to provide lockless lookups. Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: "Peter Zijlstra (Intel)" Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Oleg Nesterov Reviewed-by: "Masami Hiramatsu (Google)" Link: https://lore.kernel.org/r/20240903174603.3554182-7-andrii@kernel.org --- include/linux/rbtree.h | 67 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/include/linux/rbtree.h b/include/linux/rbtree.h index f7edca369eda..7c173aa64e1e 100644 --- a/include/linux/rbtree.h +++ b/include/linux/rbtree.h @@ -244,6 +244,42 @@ rb_find_add(struct rb_node *node, struct rb_root *tree, return NULL; } +/** + * rb_find_add_rcu() - find equivalent @node in @tree, or add @node + * @node: node to look-for / insert + * @tree: tree to search / modify + * @cmp: operator defining the node order + * + * Adds a Store-Release for link_node. + * + * Returns the rb_node matching @node, or NULL when no match is found and @node + * is inserted. + */ +static __always_inline struct rb_node * +rb_find_add_rcu(struct rb_node *node, struct rb_root *tree, + int (*cmp)(struct rb_node *, const struct rb_node *)) +{ + struct rb_node **link = &tree->rb_node; + struct rb_node *parent = NULL; + int c; + + while (*link) { + parent = *link; + c = cmp(node, parent); + + if (c < 0) + link = &parent->rb_left; + else if (c > 0) + link = &parent->rb_right; + else + return parent; + } + + rb_link_node_rcu(node, parent, link); + rb_insert_color(node, tree); + return NULL; +} + /** * rb_find() - find @key in tree @tree * @key: key to match @@ -272,6 +308,37 @@ rb_find(const void *key, const struct rb_root *tree, return NULL; } +/** + * rb_find_rcu() - find @key in tree @tree + * @key: key to match + * @tree: tree to search + * @cmp: operator defining the node order + * + * Notably, tree descent vs concurrent tree rotations is unsound and can result + * in false-negatives. + * + * Returns the rb_node matching @key or NULL. + */ +static __always_inline struct rb_node * +rb_find_rcu(const void *key, const struct rb_root *tree, + int (*cmp)(const void *key, const struct rb_node *)) +{ + struct rb_node *node = tree->rb_node; + + while (node) { + int c = cmp(key, node); + + if (c < 0) + node = rcu_dereference_raw(node->rb_left); + else if (c > 0) + node = rcu_dereference_raw(node->rb_right); + else + return node; + } + + return NULL; +} + /** * rb_find_first() - find the first @key in @tree * @key: key to match From cd7bdd9d46a9540f3a20a0e14c99aa37b2d4a1dd Mon Sep 17 00:00:00 2001 From: Andrii Nakryiko Date: Tue, 3 Sep 2024 10:46:02 -0700 Subject: [PATCH 418/573] uprobes: perform lockless SRCU-protected uprobes_tree lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Another big bottleneck to scalablity is uprobe_treelock that's taken in a very hot path in handle_swbp(). Now that uprobes are SRCU-protected, take advantage of that and make uprobes_tree RB-tree look up lockless. To make RB-tree RCU-protected lockless lookup correct, we need to take into account that such RB-tree lookup can return false negatives if there are parallel RB-tree modifications (rotations) going on. We use seqcount lock to detect whether RB-tree changed, and if we find nothing while RB-tree got modified inbetween, we just retry. If uprobe was found, then it's guaranteed to be a correct lookup. With all the lock-avoiding changes done, we get a pretty decent improvement in performance and scalability of uprobes with number of CPUs, even though we are still nowhere near linear scalability. This is due to SRCU not really scaling very well with number of CPUs on a particular hardware that was used for testing (80-core Intel Xeon Gold 6138 CPU @ 2.00GHz), but also due to the remaning mmap_lock, which is currently taken to resolve interrupt address to inode+offset and then uprobe instance. And, of course, uretprobes still need similar RCU to avoid refcount in the hot path, which will be addressed in the follow up patches. Nevertheless, the improvement is good. We used BPF selftest-based uprobe-nop and uretprobe-nop benchmarks to get the below numbers, varying number of CPUs on which uprobes and uretprobes are triggered. BASELINE ======== uprobe-nop ( 1 cpus): 3.032 ± 0.023M/s ( 3.032M/s/cpu) uprobe-nop ( 2 cpus): 3.452 ± 0.005M/s ( 1.726M/s/cpu) uprobe-nop ( 4 cpus): 3.663 ± 0.005M/s ( 0.916M/s/cpu) uprobe-nop ( 8 cpus): 3.718 ± 0.038M/s ( 0.465M/s/cpu) uprobe-nop (16 cpus): 3.344 ± 0.008M/s ( 0.209M/s/cpu) uprobe-nop (32 cpus): 2.288 ± 0.021M/s ( 0.071M/s/cpu) uprobe-nop (64 cpus): 3.205 ± 0.004M/s ( 0.050M/s/cpu) uretprobe-nop ( 1 cpus): 1.979 ± 0.005M/s ( 1.979M/s/cpu) uretprobe-nop ( 2 cpus): 2.361 ± 0.005M/s ( 1.180M/s/cpu) uretprobe-nop ( 4 cpus): 2.309 ± 0.002M/s ( 0.577M/s/cpu) uretprobe-nop ( 8 cpus): 2.253 ± 0.001M/s ( 0.282M/s/cpu) uretprobe-nop (16 cpus): 2.007 ± 0.000M/s ( 0.125M/s/cpu) uretprobe-nop (32 cpus): 1.624 ± 0.003M/s ( 0.051M/s/cpu) uretprobe-nop (64 cpus): 2.149 ± 0.001M/s ( 0.034M/s/cpu) SRCU CHANGES ============ uprobe-nop ( 1 cpus): 3.276 ± 0.005M/s ( 3.276M/s/cpu) uprobe-nop ( 2 cpus): 4.125 ± 0.002M/s ( 2.063M/s/cpu) uprobe-nop ( 4 cpus): 7.713 ± 0.002M/s ( 1.928M/s/cpu) uprobe-nop ( 8 cpus): 8.097 ± 0.006M/s ( 1.012M/s/cpu) uprobe-nop (16 cpus): 6.501 ± 0.056M/s ( 0.406M/s/cpu) uprobe-nop (32 cpus): 4.398 ± 0.084M/s ( 0.137M/s/cpu) uprobe-nop (64 cpus): 6.452 ± 0.000M/s ( 0.101M/s/cpu) uretprobe-nop ( 1 cpus): 2.055 ± 0.001M/s ( 2.055M/s/cpu) uretprobe-nop ( 2 cpus): 2.677 ± 0.000M/s ( 1.339M/s/cpu) uretprobe-nop ( 4 cpus): 4.561 ± 0.003M/s ( 1.140M/s/cpu) uretprobe-nop ( 8 cpus): 5.291 ± 0.002M/s ( 0.661M/s/cpu) uretprobe-nop (16 cpus): 5.065 ± 0.019M/s ( 0.317M/s/cpu) uretprobe-nop (32 cpus): 3.622 ± 0.003M/s ( 0.113M/s/cpu) uretprobe-nop (64 cpus): 3.723 ± 0.002M/s ( 0.058M/s/cpu) Peak througput increased from 3.7 mln/s (uprobe triggerings) up to about 8 mln/s. For uretprobes it's a bit more modest with bump from 2.4 mln/s to 5mln/s. Suggested-by: "Peter Zijlstra (Intel)" Signed-off-by: Andrii Nakryiko Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Oleg Nesterov Link: https://lore.kernel.org/r/20240903174603.3554182-8-andrii@kernel.org --- kernel/events/uprobes.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c index 694f6790e848..4b7e590dc428 100644 --- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -40,6 +40,7 @@ static struct rb_root uprobes_tree = RB_ROOT; #define no_uprobe_events() RB_EMPTY_ROOT(&uprobes_tree) static DEFINE_RWLOCK(uprobes_treelock); /* serialize rbtree access */ +static seqcount_rwlock_t uprobes_seqcount = SEQCNT_RWLOCK_ZERO(uprobes_seqcount, &uprobes_treelock); DEFINE_STATIC_SRCU(uprobes_srcu); @@ -634,8 +635,11 @@ static void put_uprobe(struct uprobe *uprobe) write_lock(&uprobes_treelock); - if (uprobe_is_active(uprobe)) + if (uprobe_is_active(uprobe)) { + write_seqcount_begin(&uprobes_seqcount); rb_erase(&uprobe->rb_node, &uprobes_tree); + write_seqcount_end(&uprobes_seqcount); + } write_unlock(&uprobes_treelock); @@ -701,14 +705,26 @@ static struct uprobe *find_uprobe_rcu(struct inode *inode, loff_t offset) .offset = offset, }; struct rb_node *node; + unsigned int seq; lockdep_assert(srcu_read_lock_held(&uprobes_srcu)); - read_lock(&uprobes_treelock); - node = rb_find(&key, &uprobes_tree, __uprobe_cmp_key); - read_unlock(&uprobes_treelock); + do { + seq = read_seqcount_begin(&uprobes_seqcount); + node = rb_find_rcu(&key, &uprobes_tree, __uprobe_cmp_key); + /* + * Lockless RB-tree lookups can result only in false negatives. + * If the element is found, it is correct and can be returned + * under RCU protection. If we find nothing, we need to + * validate that seqcount didn't change. If it did, we have to + * try again as we might have missed the element (false + * negative). If seqcount is unchanged, search truly failed. + */ + if (node) + return __node_2_uprobe(node); + } while (read_seqcount_retry(&uprobes_seqcount, seq)); - return node ? __node_2_uprobe(node) : NULL; + return NULL; } /* @@ -730,7 +746,7 @@ static struct uprobe *__insert_uprobe(struct uprobe *uprobe) { struct rb_node *node; again: - node = rb_find_add(&uprobe->rb_node, &uprobes_tree, __uprobe_cmp); + node = rb_find_add_rcu(&uprobe->rb_node, &uprobes_tree, __uprobe_cmp); if (node) { struct uprobe *u = __node_2_uprobe(node); @@ -755,7 +771,9 @@ static struct uprobe *insert_uprobe(struct uprobe *uprobe) struct uprobe *u; write_lock(&uprobes_treelock); + write_seqcount_begin(&uprobes_seqcount); u = __insert_uprobe(uprobe); + write_seqcount_end(&uprobes_seqcount); write_unlock(&uprobes_treelock); return u; From 2408b81f817ba6c278c5453eb9b43a167f35d471 Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Wed, 4 Sep 2024 19:40:42 -0700 Subject: [PATCH 419/573] iommu/tegra241-cmdqv: Drop static at local variable This is likely a typo. Drop it. Fixes: 918eb5c856f6 ("iommu/arm-smmu-v3: Add in-kernel support for NVIDIA Tegra241 (Grace) CMDQV") Signed-off-by: Nicolin Chen Reviewed-by: Jason Gunthorpe Link: https://lore.kernel.org/r/13fd3accb5b7ed6ec11cc6b7435f79f84af9f45f.1725503154.git.nicolinc@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c index 240b54192177..0766dc2789cb 100644 --- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c +++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c @@ -761,8 +761,8 @@ static struct arm_smmu_device * __tegra241_cmdqv_probe(struct arm_smmu_device *smmu, struct resource *res, int irq) { - static struct arm_smmu_device *new_smmu; struct tegra241_cmdqv *cmdqv = NULL; + struct arm_smmu_device *new_smmu; struct tegra241_vintf *vintf; void __iomem *base; u32 regval; From 483e0bd8883a40fd3dd3193997a4014337698d72 Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Wed, 4 Sep 2024 19:40:43 -0700 Subject: [PATCH 420/573] iommu/tegra241-cmdqv: Do not allocate vcmdq until dma_set_mask_and_coherent It's observed that, when the first 4GB of system memory was reserved, all VCMDQ allocations failed (even with the smallest qsz in the last attempt): arm-smmu-v3: found companion CMDQV device: NVDA200C:00 arm-smmu-v3: option mask 0x10 arm-smmu-v3: failed to allocate queue (0x8000 bytes) for vcmdq0 acpi NVDA200C:00: tegra241_cmdqv: Falling back to standard SMMU CMDQ arm-smmu-v3: ias 48-bit, oas 48-bit (features 0x001e1fbf) arm-smmu-v3: allocated 524288 entries for cmdq arm-smmu-v3: allocated 524288 entries for evtq arm-smmu-v3: allocated 524288 entries for priq This is because the 4GB reserved memory shifted the entire DMA zone from a lower 32-bit range (on a system without the 4GB carveout) to higher range, while the dev->coherent_dma_mask was set to DMA_BIT_MASK(32) by default. The dma_set_mask_and_coherent() call is done in arm_smmu_device_hw_probe() of the SMMU driver. So any DMA allocation from tegra241_cmdqv_probe() must wait until the coherent_dma_mask is correctly set. Move the vintf/vcmdq structure initialization routine into a different op, "init_structures". Call it at the end of arm_smmu_init_structures(), where standard SMMU queues get allocated. Most of the impl_ops aren't ready until vintf/vcmdq structure are init-ed. So replace the full impl_ops with an init_ops in __tegra241_cmdqv_probe(). And switch to tegra241_cmdqv_impl_ops later in arm_smmu_init_structures(). Note that tegra241_cmdqv_impl_ops does not link to the new init_structures op after this switch, since there is no point in having it once it's done. Fixes: 918eb5c856f6 ("iommu/arm-smmu-v3: Add in-kernel support for NVIDIA Tegra241 (Grace) CMDQV") Reported-by: Matt Ochs Signed-off-by: Nicolin Chen Reviewed-by: Jason Gunthorpe Link: https://lore.kernel.org/r/530993c3aafa1b0fc3d879b8119e13c629d12e2b.1725503154.git.nicolinc@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 9 +- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 + .../iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 83 ++++++++++++------- 3 files changed, 60 insertions(+), 33 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index b2de56dfceb9..df852ab04fd7 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -3744,7 +3744,14 @@ static int arm_smmu_init_structures(struct arm_smmu_device *smmu) if (ret) return ret; - return arm_smmu_init_strtab(smmu); + ret = arm_smmu_init_strtab(smmu); + if (ret) + return ret; + + if (smmu->impl_ops && smmu->impl_ops->init_structures) + return smmu->impl_ops->init_structures(smmu); + + return 0; } static int arm_smmu_write_reg_sync(struct arm_smmu_device *smmu, u32 val, diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index e044ce5b5372..e8320e9341d7 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -643,6 +643,7 @@ struct arm_smmu_strtab_cfg { struct arm_smmu_impl_ops { int (*device_reset)(struct arm_smmu_device *smmu); void (*device_remove)(struct arm_smmu_device *smmu); + int (*init_structures)(struct arm_smmu_device *smmu); struct arm_smmu_cmdq *(*get_secondary_cmdq)( struct arm_smmu_device *smmu, struct arm_smmu_cmdq_ent *ent); }; diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c index 0766dc2789cb..fcd13d301fff 100644 --- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c +++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c @@ -755,18 +755,65 @@ free_list: return res; } +static int tegra241_cmdqv_init_structures(struct arm_smmu_device *smmu) +{ + struct tegra241_cmdqv *cmdqv = + container_of(smmu, struct tegra241_cmdqv, smmu); + struct tegra241_vintf *vintf; + int lidx; + int ret; + + vintf = kzalloc(sizeof(*vintf), GFP_KERNEL); + if (!vintf) + goto out_fallback; + + /* Init VINTF0 for in-kernel use */ + ret = tegra241_cmdqv_init_vintf(cmdqv, 0, vintf); + if (ret) { + dev_err(cmdqv->dev, "failed to init vintf0: %d\n", ret); + goto free_vintf; + } + + /* Preallocate logical VCMDQs to VINTF0 */ + for (lidx = 0; lidx < cmdqv->num_lvcmdqs_per_vintf; lidx++) { + struct tegra241_vcmdq *vcmdq; + + vcmdq = tegra241_vintf_alloc_lvcmdq(vintf, lidx); + if (IS_ERR(vcmdq)) + goto free_lvcmdq; + } + + /* Now, we are ready to run all the impl ops */ + smmu->impl_ops = &tegra241_cmdqv_impl_ops; + return 0; + +free_lvcmdq: + for (lidx--; lidx >= 0; lidx--) + tegra241_vintf_free_lvcmdq(vintf, lidx); + tegra241_cmdqv_deinit_vintf(cmdqv, vintf->idx); +free_vintf: + kfree(vintf); +out_fallback: + dev_info(smmu->impl_dev, "Falling back to standard SMMU CMDQ\n"); + smmu->options &= ~ARM_SMMU_OPT_TEGRA241_CMDQV; + tegra241_cmdqv_remove(smmu); + return 0; +} + struct dentry *cmdqv_debugfs_dir; static struct arm_smmu_device * __tegra241_cmdqv_probe(struct arm_smmu_device *smmu, struct resource *res, int irq) { + static const struct arm_smmu_impl_ops init_ops = { + .init_structures = tegra241_cmdqv_init_structures, + .device_remove = tegra241_cmdqv_remove, + }; struct tegra241_cmdqv *cmdqv = NULL; struct arm_smmu_device *new_smmu; - struct tegra241_vintf *vintf; void __iomem *base; u32 regval; - int lidx; int ret; static_assert(offsetof(struct tegra241_cmdqv, smmu) == 0); @@ -815,26 +862,6 @@ __tegra241_cmdqv_probe(struct arm_smmu_device *smmu, struct resource *res, ida_init(&cmdqv->vintf_ids); - vintf = kzalloc(sizeof(*vintf), GFP_KERNEL); - if (!vintf) - goto destroy_ids; - - /* Init VINTF0 for in-kernel use */ - ret = tegra241_cmdqv_init_vintf(cmdqv, 0, vintf); - if (ret) { - dev_err(cmdqv->dev, "failed to init vintf0: %d\n", ret); - goto free_vintf; - } - - /* Preallocate logical VCMDQs to VINTF0 */ - for (lidx = 0; lidx < cmdqv->num_lvcmdqs_per_vintf; lidx++) { - struct tegra241_vcmdq *vcmdq; - - vcmdq = tegra241_vintf_alloc_lvcmdq(vintf, lidx); - if (IS_ERR(vcmdq)) - goto free_lvcmdq; - } - #ifdef CONFIG_IOMMU_DEBUGFS if (!cmdqv_debugfs_dir) { cmdqv_debugfs_dir = @@ -844,19 +871,11 @@ __tegra241_cmdqv_probe(struct arm_smmu_device *smmu, struct resource *res, } #endif - new_smmu->impl_ops = &tegra241_cmdqv_impl_ops; + /* Provide init-level ops only, until tegra241_cmdqv_init_structures */ + new_smmu->impl_ops = &init_ops; return new_smmu; -free_lvcmdq: - for (lidx--; lidx >= 0; lidx--) - tegra241_vintf_free_lvcmdq(vintf, lidx); - tegra241_cmdqv_deinit_vintf(cmdqv, vintf->idx); -free_vintf: - kfree(vintf); -destroy_ids: - ida_destroy(&cmdqv->vintf_ids); - kfree(cmdqv->vintfs); free_irq: if (cmdqv->irq > 0) free_irq(cmdqv->irq, cmdqv); From 6ff4cd1160afafc12ad1603e3d2f39256e4b708d Mon Sep 17 00:00:00 2001 From: Hongbo Li Date: Tue, 27 Aug 2024 10:45:15 +0800 Subject: [PATCH 421/573] lib/string_choices: Add str_true_false()/str_false_true() helper Add str_true_false()/str_false_true() helper to return "true" or "false" string literal. Signed-off-by: Hongbo Li Link: https://lore.kernel.org/r/20240827024517.914100-2-lihongbo22@huawei.com Signed-off-by: Kees Cook --- include/linux/string_choices.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/string_choices.h b/include/linux/string_choices.h index 1320bcdcb89c..ebcc56b28ede 100644 --- a/include/linux/string_choices.h +++ b/include/linux/string_choices.h @@ -48,6 +48,12 @@ static inline const char *str_up_down(bool v) } #define str_down_up(v) str_up_down(!(v)) +static inline const char *str_true_false(bool v) +{ + return v ? "true" : "false"; +} +#define str_false_true(v) str_true_false(!(v)) + /** * str_plural - Return the simple pluralization based on English counts * @num: Number used for deciding pluralization From c2708ba91c3c1fba424b77de83b6fc45cbf38c46 Mon Sep 17 00:00:00 2001 From: Hongbo Li Date: Thu, 5 Sep 2024 17:25:39 +0800 Subject: [PATCH 422/573] lib/string_choices: Introduce several opposite string choice helpers Similar to the exists helper: str_enable_disable/ str_enabled_disabled/str_on_off/str_yes_no helpers, we can add the opposite helpers. That's str_disable_enable, str_disabled_enabled, str_off_on and str_no_yes. There are more than 10 cases currently (expect str_disable_enable now has 3 use cases) exist in the code can be replaced with these helper. Signed-off-by: Hongbo Li Acked-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240905092540.2962122-2-lihongbo22@huawei.com Signed-off-by: Kees Cook --- include/linux/string_choices.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/linux/string_choices.h b/include/linux/string_choices.h index ebcc56b28ede..fd067992260a 100644 --- a/include/linux/string_choices.h +++ b/include/linux/string_choices.h @@ -8,11 +8,13 @@ static inline const char *str_enable_disable(bool v) { return v ? "enable" : "disable"; } +#define str_disable_enable(v) str_enable_disable(!(v)) static inline const char *str_enabled_disabled(bool v) { return v ? "enabled" : "disabled"; } +#define str_disabled_enabled(v) str_enabled_disabled(!(v)) static inline const char *str_hi_lo(bool v) { @@ -36,11 +38,13 @@ static inline const char *str_on_off(bool v) { return v ? "on" : "off"; } +#define str_off_on(v) str_on_off(!(v)) static inline const char *str_yes_no(bool v) { return v ? "yes" : "no"; } +#define str_no_yes(v) str_yes_no(!(v)) static inline const char *str_up_down(bool v) { From c121d5cc3a993cdbfab46a152bdd50227a4d5e8c Mon Sep 17 00:00:00 2001 From: Hongbo Li Date: Thu, 5 Sep 2024 17:25:40 +0800 Subject: [PATCH 423/573] lib/string_choices: Add some comments to make more clear for string choices helpers. Add some comments to explain why we should use string_choices helpers. Signed-off-by: Hongbo Li Acked-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240905092540.2962122-3-lihongbo22@huawei.com Signed-off-by: Kees Cook --- include/linux/string_choices.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/include/linux/string_choices.h b/include/linux/string_choices.h index fd067992260a..120ca0f28e95 100644 --- a/include/linux/string_choices.h +++ b/include/linux/string_choices.h @@ -2,6 +2,19 @@ #ifndef _LINUX_STRING_CHOICES_H_ #define _LINUX_STRING_CHOICES_H_ +/* + * Here provide a series of helpers in the str_$TRUE_$FALSE format (you can + * also expand some helpers as needed), where $TRUE and $FALSE are their + * corresponding literal strings. These helpers can be used in the printing + * and also in other places where constant strings are required. Using these + * helpers offers the following benefits: + * 1) Reducing the hardcoding of strings, which makes the code more elegant + * through these simple literal-meaning helpers. + * 2) Unifying the output, which prevents the same string from being printed + * in various forms, such as enable/disable, enabled/disabled, en/dis. + * 3) Deduping by the linker, which results in a smaller binary file. + */ + #include static inline const char *str_enable_disable(bool v) From 6b5e97c020060c2b8ad286002415106ab7034435 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 4 Sep 2024 16:07:06 +0200 Subject: [PATCH 424/573] gpio: mpc8xxx: switch to using DEFINE_RUNTIME_DEV_PM_OPS() Use the preferred API for assigning system sleep pm callbacks in drivers. Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240904140706.70359-1-brgl@bgdev.pl Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mpc8xxx.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/drivers/gpio/gpio-mpc8xxx.c b/drivers/gpio/gpio-mpc8xxx.c index e084e08f5438..685ec31db409 100644 --- a/drivers/gpio/gpio-mpc8xxx.c +++ b/drivers/gpio/gpio-mpc8xxx.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include #include @@ -431,30 +433,28 @@ static void mpc8xxx_remove(struct platform_device *pdev) } } -#ifdef CONFIG_PM -static int mpc8xxx_suspend(struct platform_device *pdev, pm_message_t state) +static int mpc8xxx_suspend(struct device *dev) { - struct mpc8xxx_gpio_chip *mpc8xxx_gc = platform_get_drvdata(pdev); + struct mpc8xxx_gpio_chip *mpc8xxx_gc = dev_get_drvdata(dev); - if (mpc8xxx_gc->irqn && device_may_wakeup(&pdev->dev)) + if (mpc8xxx_gc->irqn && device_may_wakeup(dev)) enable_irq_wake(mpc8xxx_gc->irqn); return 0; } -static int mpc8xxx_resume(struct platform_device *pdev) +static int mpc8xxx_resume(struct device *dev) { - struct mpc8xxx_gpio_chip *mpc8xxx_gc = platform_get_drvdata(pdev); + struct mpc8xxx_gpio_chip *mpc8xxx_gc = dev_get_drvdata(dev); - if (mpc8xxx_gc->irqn && device_may_wakeup(&pdev->dev)) + if (mpc8xxx_gc->irqn && device_may_wakeup(dev)) disable_irq_wake(mpc8xxx_gc->irqn); return 0; } -#else -#define mpc8xxx_suspend NULL -#define mpc8xxx_resume NULL -#endif + +static DEFINE_RUNTIME_DEV_PM_OPS(mpc8xx_pm_ops, + mpc8xxx_suspend, mpc8xxx_resume, NULL); #ifdef CONFIG_ACPI static const struct acpi_device_id gpio_acpi_ids[] = { @@ -467,12 +467,11 @@ MODULE_DEVICE_TABLE(acpi, gpio_acpi_ids); static struct platform_driver mpc8xxx_plat_driver = { .probe = mpc8xxx_probe, .remove_new = mpc8xxx_remove, - .suspend = mpc8xxx_suspend, - .resume = mpc8xxx_resume, .driver = { .name = "gpio-mpc8xxx", .of_match_table = mpc8xxx_gpio_ids, .acpi_match_table = ACPI_PTR(gpio_acpi_ids), + .pm = pm_ptr(&mpc8xx_pm_ops), }, }; From 757c5ceedc69b089021f1093f03bad8a5543dc09 Mon Sep 17 00:00:00 2001 From: Nikunj Kela Date: Thu, 5 Sep 2024 12:36:56 -0700 Subject: [PATCH 425/573] dt-bindings: arm-smmu: document the support on SA8255p Add compatible for smmu representing support on SA8255p. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Nikunj Kela Link: https://lore.kernel.org/r/20240905193656.3785537-1-quic_nkela@quicinc.com Signed-off-by: Will Deacon --- Documentation/devicetree/bindings/iommu/arm,smmu.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml index 280b4e49f219..3353c2d37841 100644 --- a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml +++ b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml @@ -37,6 +37,7 @@ properties: - enum: - qcom,qcm2290-smmu-500 - qcom,qdu1000-smmu-500 + - qcom,sa8255p-smmu-500 - qcom,sa8775p-smmu-500 - qcom,sc7180-smmu-500 - qcom,sc7280-smmu-500 @@ -84,6 +85,7 @@ properties: items: - enum: - qcom,qcm2290-smmu-500 + - qcom,sa8255p-smmu-500 - qcom,sa8775p-smmu-500 - qcom,sc7280-smmu-500 - qcom,sc8180x-smmu-500 @@ -553,6 +555,7 @@ allOf: - marvell,ap806-smmu-500 - nvidia,smmu-500 - qcom,qdu1000-smmu-500 + - qcom,sa8255p-smmu-500 - qcom,sc7180-smmu-500 - qcom,sdm670-smmu-500 - qcom,sdm845-smmu-500 From a2bb820e862d61f9ca1499e500915f9f505a2655 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Tue, 6 Aug 2024 20:31:15 -0300 Subject: [PATCH 426/573] iommu/arm-smmu-v3: Use the new rb tree helpers Since v5.12 the rbtree has gained some simplifying helpers aimed at making rb tree users write less convoluted boiler plate code. Instead the caller provides a single comparison function and the helpers generate the prior open-coded stuff. Update smmu->streams to use rb_find_add() and rb_find(). Tested-by: Nicolin Chen Reviewed-by: Mostafa Saleh Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/1-v3-9fef8cdc2ff6+150d1-smmuv3_tidy_jgg@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 68 ++++++++++----------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index df852ab04fd7..fb8b71b02d96 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -1735,26 +1735,37 @@ static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid) return 0; } +static int arm_smmu_streams_cmp_key(const void *lhs, const struct rb_node *rhs) +{ + struct arm_smmu_stream *stream_rhs = + rb_entry(rhs, struct arm_smmu_stream, node); + const u32 *sid_lhs = lhs; + + if (*sid_lhs < stream_rhs->id) + return -1; + if (*sid_lhs > stream_rhs->id) + return 1; + return 0; +} + +static int arm_smmu_streams_cmp_node(struct rb_node *lhs, + const struct rb_node *rhs) +{ + return arm_smmu_streams_cmp_key( + &rb_entry(lhs, struct arm_smmu_stream, node)->id, rhs); +} + static struct arm_smmu_master * arm_smmu_find_master(struct arm_smmu_device *smmu, u32 sid) { struct rb_node *node; - struct arm_smmu_stream *stream; lockdep_assert_held(&smmu->streams_mutex); - node = smmu->streams.rb_node; - while (node) { - stream = rb_entry(node, struct arm_smmu_stream, node); - if (stream->id < sid) - node = node->rb_right; - else if (stream->id > sid) - node = node->rb_left; - else - return stream->master; - } - - return NULL; + node = rb_find(&sid, &smmu->streams, arm_smmu_streams_cmp_key); + if (!node) + return NULL; + return rb_entry(node, struct arm_smmu_stream, node)->master; } /* IRQ and event handlers */ @@ -3210,8 +3221,6 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu, { int i; int ret = 0; - struct arm_smmu_stream *new_stream, *cur_stream; - struct rb_node **new_node, *parent_node = NULL; struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(master->dev); master->streams = kcalloc(fwspec->num_ids, sizeof(*master->streams), @@ -3222,9 +3231,9 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu, mutex_lock(&smmu->streams_mutex); for (i = 0; i < fwspec->num_ids; i++) { + struct arm_smmu_stream *new_stream = &master->streams[i]; u32 sid = fwspec->ids[i]; - new_stream = &master->streams[i]; new_stream->id = sid; new_stream->master = master; @@ -3233,28 +3242,13 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu, break; /* Insert into SID tree */ - new_node = &(smmu->streams.rb_node); - while (*new_node) { - cur_stream = rb_entry(*new_node, struct arm_smmu_stream, - node); - parent_node = *new_node; - if (cur_stream->id > new_stream->id) { - new_node = &((*new_node)->rb_left); - } else if (cur_stream->id < new_stream->id) { - new_node = &((*new_node)->rb_right); - } else { - dev_warn(master->dev, - "stream %u already in tree\n", - cur_stream->id); - ret = -EINVAL; - break; - } - } - if (ret) + if (rb_find_add(&new_stream->node, &smmu->streams, + arm_smmu_streams_cmp_node)) { + dev_warn(master->dev, "stream %u already in tree\n", + sid); + ret = -EINVAL; break; - - rb_link_node(&new_stream->node, parent_node, new_node); - rb_insert_color(&new_stream->node, &smmu->streams); + } } if (ret) { From d43b24f50da293009050e172e7c3769571db79c6 Mon Sep 17 00:00:00 2001 From: Chen Ridong Date: Mon, 26 Aug 2024 02:43:39 +0000 Subject: [PATCH 427/573] mtd: rawnand: denali: Fix missing pci_release_regions in probe and remove The pci_release_regions was miss at error case, just add it. Signed-off-by: Chen Ridong Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826024339.476921-1-chenridong@huawei.com --- drivers/mtd/nand/raw/denali_pci.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/denali_pci.c b/drivers/mtd/nand/raw/denali_pci.c index de7e722d3826..e22094e39546 100644 --- a/drivers/mtd/nand/raw/denali_pci.c +++ b/drivers/mtd/nand/raw/denali_pci.c @@ -77,18 +77,20 @@ static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) denali->reg = devm_ioremap(denali->dev, csr_base, csr_len); if (!denali->reg) { dev_err(&dev->dev, "Spectra: Unable to remap memory region\n"); - return -ENOMEM; + ret = -ENOMEM; + goto regions_release; } denali->host = devm_ioremap(denali->dev, mem_base, mem_len); if (!denali->host) { dev_err(&dev->dev, "Spectra: ioremap failed!"); - return -ENOMEM; + ret = -ENOMEM; + goto regions_release; } ret = denali_init(denali); if (ret) - return ret; + goto regions_release; nsels = denali->nbanks; @@ -116,6 +118,8 @@ static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) out_remove_denali: denali_remove(denali); +regions_release: + pci_release_regions(dev); return ret; } @@ -123,6 +127,7 @@ static void denali_pci_remove(struct pci_dev *dev) { struct denali_controller *denali = pci_get_drvdata(dev); + pci_release_regions(dev); denali_remove(denali); } From 3c0e167c21721901413e8bc2293642f4ee6f983f Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 26 Aug 2024 16:04:08 +0800 Subject: [PATCH 428/573] mtd: rawnand: denali: Use the devm_clk_get_enabled() helper function The devm_clk_get_enabled() helper: - calls devm_clk_get() - calls clk_prepare_enable() and registers what is needed in order to call clk_disable_unprepare() when needed, as a managed resource. This simplifies the code. Signed-off-by: Jinjie Ruan Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826080408.2522978-1-ruanjinjie@huawei.com --- drivers/mtd/nand/raw/denali_dt.c | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/drivers/mtd/nand/raw/denali_dt.c b/drivers/mtd/nand/raw/denali_dt.c index edac8749bb93..2f5666511fda 100644 --- a/drivers/mtd/nand/raw/denali_dt.c +++ b/drivers/mtd/nand/raw/denali_dt.c @@ -145,15 +145,15 @@ static int denali_dt_probe(struct platform_device *pdev) if (IS_ERR(denali->host)) return PTR_ERR(denali->host); - dt->clk = devm_clk_get(dev, "nand"); + dt->clk = devm_clk_get_enabled(dev, "nand"); if (IS_ERR(dt->clk)) return PTR_ERR(dt->clk); - dt->clk_x = devm_clk_get(dev, "nand_x"); + dt->clk_x = devm_clk_get_enabled(dev, "nand_x"); if (IS_ERR(dt->clk_x)) return PTR_ERR(dt->clk_x); - dt->clk_ecc = devm_clk_get(dev, "ecc"); + dt->clk_ecc = devm_clk_get_enabled(dev, "ecc"); if (IS_ERR(dt->clk_ecc)) return PTR_ERR(dt->clk_ecc); @@ -165,18 +165,6 @@ static int denali_dt_probe(struct platform_device *pdev) if (IS_ERR(dt->rst_reg)) return PTR_ERR(dt->rst_reg); - ret = clk_prepare_enable(dt->clk); - if (ret) - return ret; - - ret = clk_prepare_enable(dt->clk_x); - if (ret) - goto out_disable_clk; - - ret = clk_prepare_enable(dt->clk_ecc); - if (ret) - goto out_disable_clk_x; - denali->clk_rate = clk_get_rate(dt->clk); denali->clk_x_rate = clk_get_rate(dt->clk_x); @@ -187,7 +175,7 @@ static int denali_dt_probe(struct platform_device *pdev) */ ret = reset_control_deassert(dt->rst_reg); if (ret) - goto out_disable_clk_ecc; + return ret; ret = reset_control_deassert(dt->rst); if (ret) @@ -222,12 +210,6 @@ out_assert_rst: reset_control_assert(dt->rst); out_assert_rst_reg: reset_control_assert(dt->rst_reg); -out_disable_clk_ecc: - clk_disable_unprepare(dt->clk_ecc); -out_disable_clk_x: - clk_disable_unprepare(dt->clk_x); -out_disable_clk: - clk_disable_unprepare(dt->clk); return ret; } @@ -239,9 +221,6 @@ static void denali_dt_remove(struct platform_device *pdev) denali_remove(&dt->controller); reset_control_assert(dt->rst); reset_control_assert(dt->rst_reg); - clk_disable_unprepare(dt->clk_ecc); - clk_disable_unprepare(dt->clk_x); - clk_disable_unprepare(dt->clk); } static struct platform_driver denali_dt_driver = { From 7021a797689d288a06cb48d0da44880d1dcaff8d Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 26 Aug 2024 17:43:19 +0800 Subject: [PATCH 429/573] mtd: rawnand: arasan: Use for_each_child_of_node_scoped() Avoids the need for manual cleanup of_node_put() in early exits from the loop. Signed-off-by: Jinjie Ruan Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826094328.2991664-2-ruanjinjie@huawei.com --- drivers/mtd/nand/raw/arasan-nand-controller.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c index 2ff1d2b13e3c..5436ec4a8fde 100644 --- a/drivers/mtd/nand/raw/arasan-nand-controller.c +++ b/drivers/mtd/nand/raw/arasan-nand-controller.c @@ -1360,7 +1360,7 @@ static void anfc_chips_cleanup(struct arasan_nfc *nfc) static int anfc_chips_init(struct arasan_nfc *nfc) { - struct device_node *np = nfc->dev->of_node, *nand_np; + struct device_node *np = nfc->dev->of_node; int nchips = of_get_child_count(np); int ret; @@ -1370,10 +1370,9 @@ static int anfc_chips_init(struct arasan_nfc *nfc) return -EINVAL; } - for_each_child_of_node(np, nand_np) { + for_each_child_of_node_scoped(np, nand_np) { ret = anfc_chip_init(nfc, nand_np); if (ret) { - of_node_put(nand_np); anfc_chips_cleanup(nfc); break; } From fc214e50e292550dcd77e459e59a2a385bda4735 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 26 Aug 2024 17:43:20 +0800 Subject: [PATCH 430/573] mtd: rawnand: cadence: Use for_each_child_of_node_scoped() Avoids the need for manual cleanup of_node_put() in early exits from the loop by using for_each_child_of_node_scoped(). Signed-off-by: Jinjie Ruan Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826094328.2991664-3-ruanjinjie@huawei.com --- drivers/mtd/nand/raw/cadence-nand-controller.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/cadence-nand-controller.c b/drivers/mtd/nand/raw/cadence-nand-controller.c index ff92c17def83..3bc89b356963 100644 --- a/drivers/mtd/nand/raw/cadence-nand-controller.c +++ b/drivers/mtd/nand/raw/cadence-nand-controller.c @@ -2836,7 +2836,6 @@ static void cadence_nand_chips_cleanup(struct cdns_nand_ctrl *cdns_ctrl) static int cadence_nand_chips_init(struct cdns_nand_ctrl *cdns_ctrl) { struct device_node *np = cdns_ctrl->dev->of_node; - struct device_node *nand_np; int max_cs = cdns_ctrl->caps2.max_banks; int nchips, ret; @@ -2849,10 +2848,9 @@ static int cadence_nand_chips_init(struct cdns_nand_ctrl *cdns_ctrl) return -EINVAL; } - for_each_child_of_node(np, nand_np) { + for_each_child_of_node_scoped(np, nand_np) { ret = cadence_nand_chip_init(cdns_ctrl, nand_np); if (ret) { - of_node_put(nand_np); cadence_nand_chips_cleanup(cdns_ctrl); return ret; } From e2e4eddf7b16a0cb431878e95e8bb80d893e4077 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 26 Aug 2024 17:43:21 +0800 Subject: [PATCH 431/573] mtd: rawnand: pl353: Use for_each_child_of_node_scoped() Avoids the need for manual cleanup of_node_put() in early exits from the loop by using for_each_child_of_node_scoped(). Signed-off-by: Jinjie Ruan Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826094328.2991664-4-ruanjinjie@huawei.com --- drivers/mtd/nand/raw/pl35x-nand-controller.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/pl35x-nand-controller.c b/drivers/mtd/nand/raw/pl35x-nand-controller.c index 1c76ee98efb7..2570fd0beea0 100644 --- a/drivers/mtd/nand/raw/pl35x-nand-controller.c +++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c @@ -1111,7 +1111,7 @@ static void pl35x_nand_chips_cleanup(struct pl35x_nandc *nfc) static int pl35x_nand_chips_init(struct pl35x_nandc *nfc) { - struct device_node *np = nfc->dev->of_node, *nand_np; + struct device_node *np = nfc->dev->of_node; int nchips = of_get_child_count(np); int ret; @@ -1121,10 +1121,9 @@ static int pl35x_nand_chips_init(struct pl35x_nandc *nfc) return -EINVAL; } - for_each_child_of_node(np, nand_np) { + for_each_child_of_node_scoped(np, nand_np) { ret = pl35x_nand_chip_init(nfc, nand_np); if (ret) { - of_node_put(nand_np); pl35x_nand_chips_cleanup(nfc); break; } From 707f2d0720ed6831d736cca084b62ba97eb7fa23 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 26 Aug 2024 17:43:22 +0800 Subject: [PATCH 432/573] mtd: rawnand: marvell: drm/rockchip: Use for_each_child_of_node_scoped() Avoids the need for manual cleanup of_node_put() in early exits from the loop. Signed-off-by: Jinjie Ruan Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826094328.2991664-5-ruanjinjie@huawei.com --- drivers/mtd/nand/raw/marvell_nand.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/mtd/nand/raw/marvell_nand.c b/drivers/mtd/nand/raw/marvell_nand.c index 5b0f5a9cef81..26648b72e691 100644 --- a/drivers/mtd/nand/raw/marvell_nand.c +++ b/drivers/mtd/nand/raw/marvell_nand.c @@ -2771,7 +2771,6 @@ static void marvell_nand_chips_cleanup(struct marvell_nfc *nfc) static int marvell_nand_chips_init(struct device *dev, struct marvell_nfc *nfc) { struct device_node *np = dev->of_node; - struct device_node *nand_np; int max_cs = nfc->caps->max_cs_nb; int nchips; int ret; @@ -2798,20 +2797,15 @@ static int marvell_nand_chips_init(struct device *dev, struct marvell_nfc *nfc) return ret; } - for_each_child_of_node(np, nand_np) { + for_each_child_of_node_scoped(np, nand_np) { ret = marvell_nand_chip_init(dev, nfc, nand_np); if (ret) { - of_node_put(nand_np); - goto cleanup_chips; + marvell_nand_chips_cleanup(nfc); + return ret; } } return 0; - -cleanup_chips: - marvell_nand_chips_cleanup(nfc); - - return ret; } static int marvell_nfc_init_dma(struct marvell_nfc *nfc) From 0d5c32b5c877d61afc954c760dc58f3b8626ec04 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 26 Aug 2024 17:43:23 +0800 Subject: [PATCH 433/573] mtd: rawnand: rockchip: Use for_each_child_of_node_scoped() Avoids the need for manual cleanup of_node_put() in early exits from the loop. Signed-off-by: Jinjie Ruan Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826094328.2991664-6-ruanjinjie@huawei.com --- drivers/mtd/nand/raw/rockchip-nand-controller.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c index 55580447633b..51c9cf9013dc 100644 --- a/drivers/mtd/nand/raw/rockchip-nand-controller.c +++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c @@ -1211,7 +1211,7 @@ static void rk_nfc_chips_cleanup(struct rk_nfc *nfc) static int rk_nfc_nand_chips_init(struct device *dev, struct rk_nfc *nfc) { - struct device_node *np = dev->of_node, *nand_np; + struct device_node *np = dev->of_node; int nchips = of_get_child_count(np); int ret; @@ -1221,10 +1221,9 @@ static int rk_nfc_nand_chips_init(struct device *dev, struct rk_nfc *nfc) return -EINVAL; } - for_each_child_of_node(np, nand_np) { + for_each_child_of_node_scoped(np, nand_np) { ret = rk_nfc_nand_chip_init(dev, nfc, nand_np); if (ret) { - of_node_put(nand_np); rk_nfc_chips_cleanup(nfc); return ret; } From ee06c7821d0426d0f998593cbc442757247e7cd6 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 26 Aug 2024 17:43:24 +0800 Subject: [PATCH 434/573] mtd: rawnand: meson: Use for_each_child_of_node_scoped() Avoids the need for manual cleanup of_node_put() in early exits from the loop. Signed-off-by: Jinjie Ruan Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826094328.2991664-7-ruanjinjie@huawei.com --- drivers/mtd/nand/raw/meson_nand.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/meson_nand.c b/drivers/mtd/nand/raw/meson_nand.c index 9eb5470344d0..8806a06462ac 100644 --- a/drivers/mtd/nand/raw/meson_nand.c +++ b/drivers/mtd/nand/raw/meson_nand.c @@ -1495,14 +1495,12 @@ static int meson_nfc_nand_chips_init(struct device *dev, struct meson_nfc *nfc) { struct device_node *np = dev->of_node; - struct device_node *nand_np; int ret; - for_each_child_of_node(np, nand_np) { + for_each_child_of_node_scoped(np, nand_np) { ret = meson_nfc_nand_chip_init(dev, nfc, nand_np); if (ret) { meson_nfc_nand_chip_cleanup(nfc); - of_node_put(nand_np); return ret; } } From 8795952679494b111b7b2ba08bb54ac408daca3b Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 26 Aug 2024 17:43:25 +0800 Subject: [PATCH 435/573] mtd: rawnand: mtk: Use for_each_child_of_node_scoped() Avoids the need for manual cleanup of_node_put() in early exits from the loop. Signed-off-by: Jinjie Ruan Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826094328.2991664-8-ruanjinjie@huawei.com --- drivers/mtd/nand/raw/mtk_nand.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/raw/mtk_nand.c b/drivers/mtd/nand/raw/mtk_nand.c index 17477bb2d48f..d65e6371675b 100644 --- a/drivers/mtd/nand/raw/mtk_nand.c +++ b/drivers/mtd/nand/raw/mtk_nand.c @@ -1432,15 +1432,12 @@ static int mtk_nfc_nand_chip_init(struct device *dev, struct mtk_nfc *nfc, static int mtk_nfc_nand_chips_init(struct device *dev, struct mtk_nfc *nfc) { struct device_node *np = dev->of_node; - struct device_node *nand_np; int ret; - for_each_child_of_node(np, nand_np) { + for_each_child_of_node_scoped(np, nand_np) { ret = mtk_nfc_nand_chip_init(dev, nfc, nand_np); - if (ret) { - of_node_put(nand_np); + if (ret) return ret; - } } return 0; From f3b3c47ca41f696623e71596416d34fe1671d229 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 26 Aug 2024 17:43:26 +0800 Subject: [PATCH 436/573] mtd: rawnand: renesas: Use for_each_child_of_node_scoped() Avoids the need for manual cleanup of_node_put() in early exits from the loop. Signed-off-by: Jinjie Ruan Reviewed-by: Geert Uytterhoeven Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826094328.2991664-9-ruanjinjie@huawei.com --- drivers/mtd/nand/raw/renesas-nand-controller.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/mtd/nand/raw/renesas-nand-controller.c b/drivers/mtd/nand/raw/renesas-nand-controller.c index c9a01feff8df..0e92d50c5249 100644 --- a/drivers/mtd/nand/raw/renesas-nand-controller.c +++ b/drivers/mtd/nand/raw/renesas-nand-controller.c @@ -1297,23 +1297,17 @@ static void rnandc_chips_cleanup(struct rnandc *rnandc) static int rnandc_chips_init(struct rnandc *rnandc) { - struct device_node *np; int ret; - for_each_child_of_node(rnandc->dev->of_node, np) { + for_each_child_of_node_scoped(rnandc->dev->of_node, np) { ret = rnandc_chip_init(rnandc, np); if (ret) { - of_node_put(np); - goto cleanup_chips; + rnandc_chips_cleanup(rnandc); + return ret; } } return 0; - -cleanup_chips: - rnandc_chips_cleanup(rnandc); - - return ret; } static int rnandc_probe(struct platform_device *pdev) From f5b30c7f47f25751b4c35b69d5ad42b1ed81adac Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 26 Aug 2024 17:43:27 +0800 Subject: [PATCH 437/573] mtd: rawnand: stm32_fmc2: Use for_each_child_of_node_scoped() Avoids the need for manual cleanup of_node_put() in early exits from the loop. Signed-off-by: Jinjie Ruan Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826094328.2991664-10-ruanjinjie@huawei.com --- drivers/mtd/nand/raw/stm32_fmc2_nand.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/raw/stm32_fmc2_nand.c b/drivers/mtd/nand/raw/stm32_fmc2_nand.c index 264556939a00..0f67e96cc240 100644 --- a/drivers/mtd/nand/raw/stm32_fmc2_nand.c +++ b/drivers/mtd/nand/raw/stm32_fmc2_nand.c @@ -1851,7 +1851,6 @@ static int stm32_fmc2_nfc_parse_child(struct stm32_fmc2_nfc *nfc, static int stm32_fmc2_nfc_parse_dt(struct stm32_fmc2_nfc *nfc) { struct device_node *dn = nfc->dev->of_node; - struct device_node *child; int nchips = of_get_child_count(dn); int ret = 0; @@ -1865,12 +1864,10 @@ static int stm32_fmc2_nfc_parse_dt(struct stm32_fmc2_nfc *nfc) return -EINVAL; } - for_each_child_of_node(dn, child) { + for_each_child_of_node_scoped(dn, child) { ret = stm32_fmc2_nfc_parse_child(nfc, child); - if (ret < 0) { - of_node_put(child); + if (ret < 0) return ret; - } } return ret; From b59fdc7f38812fa811a1f8d63ca2d42c9728c09c Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 26 Aug 2024 17:43:28 +0800 Subject: [PATCH 438/573] mtd: rawnand: sunxi: Use for_each_child_of_node_scoped() Avoids the need for manual cleanup of_node_put() in early exits from the loop. Signed-off-by: Jinjie Ruan Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826094328.2991664-11-ruanjinjie@huawei.com --- drivers/mtd/nand/raw/sunxi_nand.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c index 4ec17c8bce5a..c28634e20abf 100644 --- a/drivers/mtd/nand/raw/sunxi_nand.c +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -2025,13 +2025,11 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc, static int sunxi_nand_chips_init(struct device *dev, struct sunxi_nfc *nfc) { struct device_node *np = dev->of_node; - struct device_node *nand_np; int ret; - for_each_child_of_node(np, nand_np) { + for_each_child_of_node_scoped(np, nand_np) { ret = sunxi_nand_chip_init(dev, nfc, nand_np); if (ret) { - of_node_put(nand_np); sunxi_nand_chips_cleanup(nfc); return ret; } From 3f4c0ad490cc55db5b375a2e19c67159cb255795 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 26 Aug 2024 12:14:04 +0200 Subject: [PATCH 439/573] mtd: nand: Rename the NAND IO iteration helper Soon a helper for iterating over blocks will be needed (for continuous read purposes). In order to clarify the intend of this helper, let's rename it with the "page" wording inside. While at it, improve the doc and fix a typo. Signed-off-by: Miquel Raynal Reviewed-by: Pratyush Yadav Link: https://lore.kernel.org/linux-mtd/20240826101412.20644-2-miquel.raynal@bootlin.com --- include/linux/mtd/nand.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index b2996dc987ff..92e06ac33815 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -906,19 +906,19 @@ static inline void nanddev_pos_next_page(struct nand_device *nand, } /** - * nand_io_iter_init - Initialize a NAND I/O iterator + * nand_io_page_iter_init - Initialize a NAND I/O iterator * @nand: NAND device * @offs: absolute offset * @req: MTD request * @iter: NAND I/O iterator * * Initializes a NAND iterator based on the information passed by the MTD - * layer. + * layer for page jumps. */ -static inline void nanddev_io_iter_init(struct nand_device *nand, - enum nand_page_io_req_type reqtype, - loff_t offs, struct mtd_oob_ops *req, - struct nand_io_iter *iter) +static inline void nanddev_io_page_iter_init(struct nand_device *nand, + enum nand_page_io_req_type reqtype, + loff_t offs, struct mtd_oob_ops *req, + struct nand_io_iter *iter) { struct mtd_info *mtd = nanddev_to_mtd(nand); @@ -990,10 +990,10 @@ static inline bool nanddev_io_iter_end(struct nand_device *nand, * @req: MTD I/O request * @iter: NAND I/O iterator * - * Should be used for iterate over pages that are contained in an MTD request. + * Should be used for iterating over pages that are contained in an MTD request. */ #define nanddev_io_for_each_page(nand, type, start, req, iter) \ - for (nanddev_io_iter_init(nand, type, start, req, iter); \ + for (nanddev_io_page_iter_init(nand, type, start, req, iter); \ !nanddev_io_iter_end(nand, iter); \ nanddev_io_iter_next_page(nand, iter)) From 8adf1ac24ba8ee34b8fd36ebf159004ec472fca0 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 26 Aug 2024 12:14:05 +0200 Subject: [PATCH 440/573] mtd: nand: Introduce a block iterator In order to be able to iterate easily across eraseblocks rather than pages, let's introduce a block iterator inspired from the page iterator. The main usage of this iterator will be for continuous/sequential reads, where it is interesting to use a single request rather than split the requests in smaller chunks (ie. pages) that can be hardly optimized. So a "continuous" boolean get's added for this purpose. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826101412.20644-3-miquel.raynal@bootlin.com --- include/linux/mtd/nand.h | 74 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 92e06ac33815..1e4208040956 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -103,6 +103,8 @@ enum nand_page_io_req_type { * @ooblen: the number of OOB bytes to read from/write to this page * @oobbuf: buffer to store OOB data in or get OOB data from * @mode: one of the %MTD_OPS_XXX mode + * @continuous: no need to start over the operation at the end of each page, the + * NAND device will automatically prepare the next one * * This object is used to pass per-page I/O requests to NAND sub-layers. This * way all useful information are already formatted in a useful way and @@ -125,6 +127,7 @@ struct nand_page_io_req { void *in; } oobbuf; int mode; + bool continuous; }; const struct mtd_ooblayout_ops *nand_get_small_page_ooblayout(void); @@ -937,6 +940,43 @@ static inline void nanddev_io_page_iter_init(struct nand_device *nand, iter->req.ooblen = min_t(unsigned int, iter->oobbytes_per_page - iter->req.ooboffs, iter->oobleft); + iter->req.continuous = false; +} + +/** + * nand_io_block_iter_init - Initialize a NAND I/O iterator + * @nand: NAND device + * @offs: absolute offset + * @req: MTD request + * @iter: NAND I/O iterator + * + * Initializes a NAND iterator based on the information passed by the MTD + * layer for block jumps (no OOB) + * + * In practice only reads may leverage this iterator. + */ +static inline void nanddev_io_block_iter_init(struct nand_device *nand, + enum nand_page_io_req_type reqtype, + loff_t offs, struct mtd_oob_ops *req, + struct nand_io_iter *iter) +{ + unsigned int offs_in_eb; + + iter->req.type = reqtype; + iter->req.mode = req->mode; + iter->req.dataoffs = nanddev_offs_to_pos(nand, offs, &iter->req.pos); + iter->req.ooboffs = 0; + iter->oobbytes_per_page = 0; + iter->dataleft = req->len; + iter->oobleft = 0; + iter->req.databuf.in = req->datbuf; + offs_in_eb = (nand->memorg.pagesize * iter->req.pos.page) + iter->req.dataoffs; + iter->req.datalen = min_t(unsigned int, + nanddev_eraseblock_size(nand) - offs_in_eb, + iter->dataleft); + iter->req.oobbuf.in = NULL; + iter->req.ooblen = 0; + iter->req.continuous = true; } /** @@ -962,6 +1002,25 @@ static inline void nanddev_io_iter_next_page(struct nand_device *nand, iter->oobleft); } +/** + * nand_io_iter_next_block - Move to the next block + * @nand: NAND device + * @iter: NAND I/O iterator + * + * Updates the @iter to point to the next block. + * No OOB handling available. + */ +static inline void nanddev_io_iter_next_block(struct nand_device *nand, + struct nand_io_iter *iter) +{ + nanddev_pos_next_eraseblock(nand, &iter->req.pos); + iter->dataleft -= iter->req.datalen; + iter->req.databuf.in += iter->req.datalen; + iter->req.dataoffs = 0; + iter->req.datalen = min_t(unsigned int, nanddev_eraseblock_size(nand), + iter->dataleft); +} + /** * nand_io_iter_end - Should end iteration or not * @nand: NAND device @@ -997,6 +1056,21 @@ static inline bool nanddev_io_iter_end(struct nand_device *nand, !nanddev_io_iter_end(nand, iter); \ nanddev_io_iter_next_page(nand, iter)) +/** + * nand_io_for_each_block - Iterate over all NAND pages contained in an MTD I/O + * request, one block at a time + * @nand: NAND device + * @start: start address to read/write from + * @req: MTD I/O request + * @iter: NAND I/O iterator + * + * Should be used for iterating over blocks that are contained in an MTD request. + */ +#define nanddev_io_for_each_block(nand, type, start, req, iter) \ + for (nanddev_io_block_iter_init(nand, type, start, req, iter); \ + !nanddev_io_iter_end(nand, iter); \ + nanddev_io_iter_next_block(nand, iter)) + bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos); bool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos); int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos); From 79da17072e22a802a321ca44c9082ee2e855e72b Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 26 Aug 2024 12:14:06 +0200 Subject: [PATCH 441/573] mtd: spi-nand: Isolate the MTD read logic in a helper There is currently only a single path for performing page reads as requested by the MTD layer. Soon there will be two: - a "regular" page read - a continuous page read Let's extract the page read logic in a dedicated helper, so the introduction of continuous page reads will be as easy as checking whether continuous reads shall/can be used and calling one helper or the other. There is not behavioral change intended. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826101412.20644-4-miquel.raynal@bootlin.com --- drivers/mtd/nand/spi/core.c | 40 ++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 018c854d0619..1f468ed93c8e 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -630,25 +630,20 @@ static int spinand_write_page(struct spinand_device *spinand, return nand_ecc_finish_io_req(nand, (struct nand_page_io_req *)req); } -static int spinand_mtd_read(struct mtd_info *mtd, loff_t from, - struct mtd_oob_ops *ops) +static int spinand_mtd_regular_page_read(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops, + unsigned int *max_bitflips) { struct spinand_device *spinand = mtd_to_spinand(mtd); struct nand_device *nand = mtd_to_nanddev(mtd); - struct mtd_ecc_stats old_stats; - unsigned int max_bitflips = 0; struct nand_io_iter iter; bool disable_ecc = false; bool ecc_failed = false; - int ret = 0; + int ret; - if (ops->mode == MTD_OPS_RAW || !spinand->eccinfo.ooblayout) + if (ops->mode == MTD_OPS_RAW || !mtd->ooblayout) disable_ecc = true; - mutex_lock(&spinand->lock); - - old_stats = mtd->ecc_stats; - nanddev_io_for_each_page(nand, NAND_PAGE_READ, from, ops, &iter) { if (disable_ecc) iter.req.mode = MTD_OPS_RAW; @@ -664,13 +659,33 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t from, if (ret == -EBADMSG) ecc_failed = true; else - max_bitflips = max_t(unsigned int, max_bitflips, ret); + *max_bitflips = max_t(unsigned int, *max_bitflips, ret); ret = 0; ops->retlen += iter.req.datalen; ops->oobretlen += iter.req.ooblen; } + if (ecc_failed && !ret) + ret = -EBADMSG; + + return ret; +} + +static int spinand_mtd_read(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + struct mtd_ecc_stats old_stats; + unsigned int max_bitflips = 0; + int ret; + + mutex_lock(&spinand->lock); + + old_stats = mtd->ecc_stats; + + ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips); + if (ops->stats) { ops->stats->uncorrectable_errors += mtd->ecc_stats.failed - old_stats.failed; @@ -680,9 +695,6 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t from, mutex_unlock(&spinand->lock); - if (ecc_failed && !ret) - ret = -EBADMSG; - return ret ? ret : max_bitflips; } From 631cfdd0520d19b7f4fc13b834fd9c8b46c6dbac Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 26 Aug 2024 12:14:07 +0200 Subject: [PATCH 442/573] mtd: spi-nand: Add continuous read support A regular page read consist in: - Asking one page of content from the NAND array to be loaded in the chip's SRAM, - Waiting for the operation to be done, - Retrieving the data (I/O phase) from the chip's SRAM. When reading several sequential pages, the above operation is repeated over and over. There is however a way to optimize these accesses, by enabling continuous reads. The feature requires the NAND chip to have a second internal SRAM area plus a bit of additional internal logic to trigger another internal transfer between the NAND array and the second SRAM area while the I/O phase is ongoing. Once the first I/O phase is done, the host can continue reading more data, continuously, as the chip will automatically switch to the second SRAM content (which has already been loaded) and in turns trigger the next load into the first SRAM area again. From an instruction perspective, the command op-codes are different, but the same cycles are required. The only difference is that after a continuous read (which is stopped by a CS deassert), the host must observe a delay of tRST. However, because there is no guarantee in Linux regarding the actual state of the CS pin after a transfer (in order to speed-up the next transfer if targeting the same device), it was necessary to manually end the continuous read with a configuration register write operation. Continuous reads have two main drawbacks: * They only work on full pages (column address ignored) * Only the main data area is pulled, out-of-band bytes are not accessible. Said otherwise, the feature can only be useful with on-die ECC engines. Performance wise, measures have been performed on a Zynq platform using Macronix SPI-NAND controller with a Macronix chip (based on the flash_speed tool modified for testing sequential reads): - 1-1-1 mode: performances improved from +3% (2-pages) up to +10% after a dozen pages. - 1-1-4 mode: performances improved from +15% (2-pages) up to +40% after a dozen pages. This series is based on a previous work from Macronix engineer Jaime Liao. Signed-off-by: Miquel Raynal Reviewed-by: Pratyush Yadav Link: https://lore.kernel.org/linux-mtd/20240826101412.20644-5-miquel.raynal@bootlin.com --- drivers/mtd/nand/spi/core.c | 176 ++++++++++++++++++++++++++++++++++-- include/linux/mtd/spinand.h | 16 ++++ 2 files changed, 184 insertions(+), 8 deletions(-) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 1f468ed93c8e..04041287b129 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -200,6 +200,12 @@ static int spinand_ecc_enable(struct spinand_device *spinand, enable ? CFG_ECC_ENABLE : 0); } +static int spinand_cont_read_enable(struct spinand_device *spinand, + bool enable) +{ + return spinand->set_cont_read(spinand, enable); +} + static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status) { struct nand_device *nand = spinand_to_nand(spinand); @@ -311,10 +317,22 @@ static int spinand_ondie_ecc_finish_io_req(struct nand_device *nand, /* Finish a page read: check the status, report errors/bitflips */ ret = spinand_check_ecc_status(spinand, engine_conf->status); - if (ret == -EBADMSG) + if (ret == -EBADMSG) { mtd->ecc_stats.failed++; - else if (ret > 0) - mtd->ecc_stats.corrected += ret; + } else if (ret > 0) { + unsigned int pages; + + /* + * Continuous reads don't allow us to get the detail, + * so we may exagerate the actual number of corrected bitflips. + */ + if (!req->continuous) + pages = 1; + else + pages = req->datalen / nanddev_page_size(nand); + + mtd->ecc_stats.corrected += ret * pages; + } return ret; } @@ -369,7 +387,11 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand, if (req->datalen) { buf = spinand->databuf; - nbytes = nanddev_page_size(nand); + if (!req->continuous) + nbytes = nanddev_page_size(nand); + else + nbytes = round_up(req->dataoffs + req->datalen, + nanddev_page_size(nand)); column = 0; } @@ -397,6 +419,13 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand, nbytes -= ret; column += ret; buf += ret; + + /* + * Dirmap accesses are allowed to toggle the CS. + * Toggling the CS during a continuous read is forbidden. + */ + if (nbytes && req->continuous) + return -EIO; } if (req->datalen) @@ -672,6 +701,125 @@ static int spinand_mtd_regular_page_read(struct mtd_info *mtd, loff_t from, return ret; } +static int spinand_mtd_continuous_page_read(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops, + unsigned int *max_bitflips) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nanddev(mtd); + struct nand_io_iter iter; + u8 status; + int ret; + + ret = spinand_cont_read_enable(spinand, true); + if (ret) + return ret; + + /* + * The cache is divided into two halves. While one half of the cache has + * the requested data, the other half is loaded with the next chunk of data. + * Therefore, the host can read out the data continuously from page to page. + * Each data read must be a multiple of 4-bytes and full pages should be read; + * otherwise, the data output might get out of sequence from one read command + * to another. + */ + nanddev_io_for_each_block(nand, NAND_PAGE_READ, from, ops, &iter) { + ret = spinand_select_target(spinand, iter.req.pos.target); + if (ret) + goto end_cont_read; + + ret = nand_ecc_prepare_io_req(nand, &iter.req); + if (ret) + goto end_cont_read; + + ret = spinand_load_page_op(spinand, &iter.req); + if (ret) + goto end_cont_read; + + ret = spinand_wait(spinand, SPINAND_READ_INITIAL_DELAY_US, + SPINAND_READ_POLL_DELAY_US, NULL); + if (ret < 0) + goto end_cont_read; + + ret = spinand_read_from_cache_op(spinand, &iter.req); + if (ret) + goto end_cont_read; + + ops->retlen += iter.req.datalen; + + ret = spinand_read_status(spinand, &status); + if (ret) + goto end_cont_read; + + spinand_ondie_ecc_save_status(nand, status); + + ret = nand_ecc_finish_io_req(nand, &iter.req); + if (ret < 0) + goto end_cont_read; + + *max_bitflips = max_t(unsigned int, *max_bitflips, ret); + ret = 0; + } + +end_cont_read: + /* + * Once all the data has been read out, the host can either pull CS# + * high and wait for tRST or manually clear the bit in the configuration + * register to terminate the continuous read operation. We have no + * guarantee the SPI controller drivers will effectively deassert the CS + * when we expect them to, so take the register based approach. + */ + spinand_cont_read_enable(spinand, false); + + return ret; +} + +static void spinand_cont_read_init(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + enum nand_ecc_engine_type engine_type = nand->ecc.ctx.conf.engine_type; + + /* OOBs cannot be retrieved so external/on-host ECC engine won't work */ + if (spinand->set_cont_read && + (engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE || + engine_type == NAND_ECC_ENGINE_TYPE_NONE)) { + spinand->cont_read_possible = true; + } +} + +static bool spinand_use_cont_read(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct spinand_device *spinand = nand_to_spinand(nand); + struct nand_pos start_pos, end_pos; + + if (!spinand->cont_read_possible) + return false; + + /* OOBs won't be retrieved */ + if (ops->ooblen || ops->oobbuf) + return false; + + nanddev_offs_to_pos(nand, from, &start_pos); + nanddev_offs_to_pos(nand, from + ops->len - 1, &end_pos); + + /* + * Continuous reads never cross LUN boundaries. Some devices don't + * support crossing planes boundaries. Some devices don't even support + * crossing blocks boundaries. The common case being to read through UBI, + * we will very rarely read two consequent blocks or more, so it is safer + * and easier (can be improved) to only enable continuous reads when + * reading within the same erase block. + */ + if (start_pos.target != end_pos.target || + start_pos.plane != end_pos.plane || + start_pos.eraseblock != end_pos.eraseblock) + return false; + + return start_pos.page < end_pos.page; +} + static int spinand_mtd_read(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) { @@ -684,7 +832,10 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t from, old_stats = mtd->ecc_stats; - ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips); + if (spinand_use_cont_read(mtd, from, ops)) + ret = spinand_mtd_continuous_page_read(mtd, from, ops, &max_bitflips); + else + ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips); if (ops->stats) { ops->stats->uncorrectable_errors += @@ -874,6 +1025,9 @@ static int spinand_create_dirmap(struct spinand_device *spinand, }; struct spi_mem_dirmap_desc *desc; + if (spinand->cont_read_possible) + info.length = nanddev_eraseblock_size(nand); + /* The plane number is passed in MSB just above the column address */ info.offset = plane << fls(nand->memorg.pagesize); @@ -1107,6 +1261,7 @@ int spinand_match_and_init(struct spinand_device *spinand, spinand->flags = table[i].flags; spinand->id.len = 1 + table[i].devid.len; spinand->select_target = table[i].select_target; + spinand->set_cont_read = table[i].set_cont_read; op = spinand_select_op_variant(spinand, info->op_variants.read_cache); @@ -1248,9 +1403,8 @@ static int spinand_init(struct spinand_device *spinand) * may use this buffer for DMA access. * Memory allocated by devm_ does not guarantee DMA-safe alignment. */ - spinand->databuf = kzalloc(nanddev_page_size(nand) + - nanddev_per_page_oobsize(nand), - GFP_KERNEL); + spinand->databuf = kzalloc(nanddev_eraseblock_size(nand), + GFP_KERNEL); if (!spinand->databuf) { ret = -ENOMEM; goto err_free_bufs; @@ -1279,6 +1433,12 @@ static int spinand_init(struct spinand_device *spinand) if (ret) goto err_cleanup_nanddev; + /* + * Continuous read can only be enabled with an on-die ECC engine, so the + * ECC initialization must have happened previously. + */ + spinand_cont_read_init(spinand); + mtd->_read_oob = spinand_mtd_read; mtd->_write_oob = spinand_mtd_write; mtd->_block_isbad = spinand_mtd_block_isbad; diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 5c19ead60499..14dce347dc46 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -336,6 +336,7 @@ struct spinand_ondie_ecc_conf { * @op_variants.update_cache: variants of the update-cache operation * @select_target: function used to select a target/die. Required only for * multi-die chips + * @set_cont_read: enable/disable continuous cached reads * * Each SPI NAND manufacturer driver should have a spinand_info table * describing all the chips supported by the driver. @@ -354,6 +355,8 @@ struct spinand_info { } op_variants; int (*select_target)(struct spinand_device *spinand, unsigned int target); + int (*set_cont_read)(struct spinand_device *spinand, + bool enable); }; #define SPINAND_ID(__method, ...) \ @@ -379,6 +382,9 @@ struct spinand_info { #define SPINAND_SELECT_TARGET(__func) \ .select_target = __func, +#define SPINAND_CONT_READ(__set_cont_read) \ + .set_cont_read = __set_cont_read, + #define SPINAND_INFO(__model, __id, __memorg, __eccreq, __op_variants, \ __flags, ...) \ { \ @@ -422,6 +428,12 @@ struct spinand_dirmap { * passed in spi_mem_op be DMA-able, so we can't based the bufs on * the stack * @manufacturer: SPI NAND manufacturer information + * @cont_read_possible: Field filled by the core once the whole system + * configuration is known to tell whether continuous reads are + * suitable to use or not in general with this chip/configuration. + * A per-transfer check must of course be done to ensure it is + * actually relevant to enable this feature. + * @set_cont_read: Enable/disable the continuous read feature * @priv: manufacturer private data */ struct spinand_device { @@ -451,6 +463,10 @@ struct spinand_device { u8 *scratchbuf; const struct spinand_manufacturer *manufacturer; void *priv; + + bool cont_read_possible; + int (*set_cont_read)(struct spinand_device *spinand, + bool enable); }; /** From a06f2e7cc4de104b14971e08d37f9d08c256b053 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 26 Aug 2024 12:14:08 +0200 Subject: [PATCH 443/573] mtd: spi-nand: Expose spinand_write_reg_op() This helper function will soon be used from a vendor driver, let's export it through the spinand.h header. No need for any export, as there is currently no reason for any module to need it. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826101412.20644-6-miquel.raynal@bootlin.com --- drivers/mtd/nand/spi/core.c | 2 +- include/linux/mtd/spinand.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 04041287b129..b5c6d755ade1 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -34,7 +34,7 @@ static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val) return 0; } -static int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val) +int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val) { struct spi_mem_op op = SPINAND_SET_FEATURE_OP(reg, spinand->scratchbuf); diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 14dce347dc46..8dbf67ca710a 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -533,6 +533,7 @@ int spinand_match_and_init(struct spinand_device *spinand, enum spinand_readid_method rdid_method); int spinand_upd_cfg(struct spinand_device *spinand, u8 mask, u8 val); +int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val); int spinand_select_target(struct spinand_device *spinand, unsigned int target); #endif /* __LINUX_MTD_SPINAND_H */ From ed148d30eaf4ff55c95ae495ad9b9ffb970f7826 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 26 Aug 2024 12:14:09 +0200 Subject: [PATCH 444/573] mtd: spi-nand: macronix: Fix helper name Use "macronix_" instead of "mx35lf1ge4ab_" as common prefix for the ->get_status() callback name. This callback is used by many different families, there is no variation in the implementation so far. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826101412.20644-7-miquel.raynal@bootlin.com --- drivers/mtd/nand/spi/macronix.c | 49 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c index 3f9e9c572854..7b1fb4b32340 100644 --- a/drivers/mtd/nand/spi/macronix.c +++ b/drivers/mtd/nand/spi/macronix.c @@ -49,7 +49,7 @@ static const struct mtd_ooblayout_ops mx35lfxge4ab_ooblayout = { .free = mx35lfxge4ab_ooblayout_free, }; -static int mx35lf1ge4ab_get_eccsr(struct spinand_device *spinand, u8 *eccsr) +static int macronix_get_eccsr(struct spinand_device *spinand, u8 *eccsr) { struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0x7c, 1), SPI_MEM_OP_NO_ADDR, @@ -64,8 +64,8 @@ static int mx35lf1ge4ab_get_eccsr(struct spinand_device *spinand, u8 *eccsr) return 0; } -static int mx35lf1ge4ab_ecc_get_status(struct spinand_device *spinand, - u8 status) +static int macronix_ecc_get_status(struct spinand_device *spinand, + u8 status) { struct nand_device *nand = spinand_to_nand(spinand); u8 eccsr; @@ -83,7 +83,7 @@ static int mx35lf1ge4ab_ecc_get_status(struct spinand_device *spinand, * in order to avoid forcing the wear-leveling layer to move * data around if it's not necessary. */ - if (mx35lf1ge4ab_get_eccsr(spinand, spinand->scratchbuf)) + if (macronix_get_eccsr(spinand, spinand->scratchbuf)) return nanddev_get_ecc_conf(nand)->strength; eccsr = *spinand->scratchbuf; @@ -110,7 +110,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35LF2GE4AB", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x22), NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), @@ -129,7 +129,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35LF4GE4AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x37, 0x03), NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1), @@ -139,7 +139,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35LF1G24AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14, 0x03), NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), @@ -194,7 +194,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX31UF1GE4BC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x9e), NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), @@ -204,7 +204,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35LF2G14AC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x20), @@ -215,7 +215,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35UF4G24AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb5, 0x03), NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 2, 1, 1), @@ -225,7 +225,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35UF4G24AD-Z4I8", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xf5, 0x03), NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), @@ -235,7 +235,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35UF4GE4AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb7, 0x03), NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), @@ -245,7 +245,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35UF2G14AC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa0), NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), @@ -255,7 +255,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35UF2G24AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa4, 0x03), NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), @@ -265,7 +265,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35UF2G24AD-Z4I8", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe4, 0x03), NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), @@ -275,7 +275,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35UF2GE4AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa6, 0x03), NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), @@ -285,7 +285,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35UF2GE4AC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa2, 0x01), NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), @@ -295,7 +295,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35UF1G14AC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x90), NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), @@ -305,7 +305,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35UF1G24AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x94, 0x03), NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), @@ -315,7 +315,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35UF1GE4AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x96, 0x03), NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), @@ -325,7 +325,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX35UF1GE4AC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x92, 0x01), NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), @@ -335,8 +335,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), - + macronix_ecc_get_status)), SPINAND_INFO("MX31LF2GE4BC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x2e), NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), @@ -346,7 +345,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), SPINAND_INFO("MX3UF2GE4BC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xae), NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), @@ -356,7 +355,7 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - mx35lf1ge4ab_ecc_get_status)), + macronix_ecc_get_status)), }; static const struct spinand_manufacturer_ops macronix_spinand_manuf_ops = { From 18073e395cd6d0de1427c4248380ccdbf937375d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 26 Aug 2024 12:14:10 +0200 Subject: [PATCH 445/573] mtd: spi-nand: macronix: Extract the bitflip retrieval logic With GET_STATUS commands, SPI-NAND devices can tell the status of the last read operation, in particular if there was: - no bitflips - corrected bitflips - uncorrectable bitflips The next step then to read an ECC status register and retrieve the amount of bitflips, when relevant, if possible. The logic used here works well for now, but will no longer apply to continuous reads. In order to prepare the introduction of continuous reads, let's factorize out the code that is specific to single-page reads. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826101412.20644-8-miquel.raynal@bootlin.com --- drivers/mtd/nand/spi/macronix.c | 37 ++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c index 7b1fb4b32340..d26e8a8c5850 100644 --- a/drivers/mtd/nand/spi/macronix.c +++ b/drivers/mtd/nand/spi/macronix.c @@ -64,12 +64,29 @@ static int macronix_get_eccsr(struct spinand_device *spinand, u8 *eccsr) return 0; } -static int macronix_ecc_get_status(struct spinand_device *spinand, - u8 status) +static int macronix_get_bf(struct spinand_device *spinand, u8 status) { struct nand_device *nand = spinand_to_nand(spinand); u8 eccsr; + /* + * Let's try to retrieve the real maximum number of bitflips + * in order to avoid forcing the wear-leveling layer to move + * data around if it's not necessary. + */ + if (macronix_get_eccsr(spinand, spinand->scratchbuf)) + return nanddev_get_ecc_conf(nand)->strength; + + eccsr = *spinand->scratchbuf; + if (WARN_ON(eccsr > nanddev_get_ecc_conf(nand)->strength || !eccsr)) + return nanddev_get_ecc_conf(nand)->strength; + + return eccsr; +} + +static int macronix_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ switch (status & STATUS_ECC_MASK) { case STATUS_ECC_NO_BITFLIPS: return 0; @@ -78,21 +95,7 @@ static int macronix_ecc_get_status(struct spinand_device *spinand, return -EBADMSG; case STATUS_ECC_HAS_BITFLIPS: - /* - * Let's try to retrieve the real maximum number of bitflips - * in order to avoid forcing the wear-leveling layer to move - * data around if it's not necessary. - */ - if (macronix_get_eccsr(spinand, spinand->scratchbuf)) - return nanddev_get_ecc_conf(nand)->strength; - - eccsr = *spinand->scratchbuf; - if (WARN_ON(eccsr > nanddev_get_ecc_conf(nand)->strength || - !eccsr)) - return nanddev_get_ecc_conf(nand)->strength; - - return eccsr; - + return macronix_get_bf(spinand, status); default: break; } From e1f251e1aad8b1cfb322cbd6057300e4613534f0 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 26 Aug 2024 12:14:11 +0200 Subject: [PATCH 446/573] mtd: spi-nand: macronix: Add a possible bitflip status flag Macronix SPI-NANDs encode the ECC status into two bits. There are three standard situations (no bitflip, bitflips, error), and an additional possible situation which is only triggered when configuring the 0x10 configuration register, allowing to know, if there have been bitflips, whether the maximum amount of bitflips was above a configurable threshold or not. In all cases, for now, s this configuration register is unset, it means the same as "there are bitflips". This value is maybe standard, maybe not. For now, let's define it in the Macronix driver, we can safely move it to a shared place later if that is relevant. Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826101412.20644-9-miquel.raynal@bootlin.com --- drivers/mtd/nand/spi/macronix.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c index d26e8a8c5850..a4feeb030258 100644 --- a/drivers/mtd/nand/spi/macronix.c +++ b/drivers/mtd/nand/spi/macronix.c @@ -12,6 +12,8 @@ #define SPINAND_MFR_MACRONIX 0xC2 #define MACRONIX_ECCSR_MASK 0x0F +#define STATUS_ECC_HAS_BITFLIPS_THRESHOLD (3 << 4) + static SPINAND_OP_VARIANTS(read_cache_variants, SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), @@ -95,6 +97,7 @@ static int macronix_ecc_get_status(struct spinand_device *spinand, return -EBADMSG; case STATUS_ECC_HAS_BITFLIPS: + case STATUS_ECC_HAS_BITFLIPS_THRESHOLD: return macronix_get_bf(spinand, status); default: break; From 11813857864f4326c4f23ea5b2e3f276f83af5f7 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 26 Aug 2024 12:14:12 +0200 Subject: [PATCH 447/573] mtd: spi-nand: macronix: Continuous read support Enabling continuous read support implies several changes which must be done atomically in order to keep the code base consistent and bisectable. 1/ Retrieving bitflips differently Improve the helper retrieving the number of bitflips to support the case where many pages have been read instead of just one. In this case, if there is one page with bitflips, we cannot know the detail and just get the information of the maximum number of bitflips corrected in the most corrupted chunk. Compatible Macronix flashes return: - the ECC status for the last page read (bits 0-3), - the amount of bitflips for the whole read operation (bits 4-7). Hence, when reading two consecutive pages, if there was 2 bits corrected at most in one chunk, we return this amount times (arbitrary) the number read pages. It is probably a very pessimistic calculation in most cases, but still less pessimistic than if we multiplied this amount by the number of chunks. Anyway, this is just for statistics, the important data is the maximum amount of bitflips, which leads to wear leveling. 2/ Configuring, enabling and disabling the feature Create an init function for allocating a vendor structure. Use this vendor structure to cache the internal continuous read state. The state is being used to discriminate between the two bitflips retrieval methods. Finally, helpers for enabling and disabling sequential reads are also created. 3/ Fill the chips table Flag all the chips supporting the feature with the ->set_cont_read() helper. In order to validate the changes, I modified the mtd-utils test suite with extended versions of nandbiterrs, nanddump and flash_speed in order to support, test and benchmark continuous reads. I also ran all the UBI tests successfully. The nandbiterrs tool allows to track the ECC efficiency and feedback. Here is its default output (stripped): Successfully corrected 0 bit errors per subpage Read reported 1 corrected bit errors Successfully corrected 1 bit errors per subpage Read reported 2 corrected bit errors Successfully corrected 2 bit errors per subpage Read reported 3 corrected bit errors Successfully corrected 3 bit errors per subpage Read reported 4 corrected bit errors Successfully corrected 4 bit errors per subpage Read reported 5 corrected bit errors Successfully corrected 5 bit errors per subpage Read reported 6 corrected bit errors Successfully corrected 6 bit errors per subpage Read reported 7 corrected bit errors Successfully corrected 7 bit errors per subpage Read reported 8 corrected bit errors Successfully corrected 8 bit errors per subpage Failed to recover 1 bitflips Read error after 9 bit errors per page The output using the continuous option over two pages (the second page is kept intact): Successfully corrected 0 bit errors per subpage Read reported 2 corrected bit errors Successfully corrected 1 bit errors per subpage Read reported 4 corrected bit errors Successfully corrected 2 bit errors per subpage Read reported 6 corrected bit errors Successfully corrected 3 bit errors per subpage Read reported 8 corrected bit errors Successfully corrected 4 bit errors per subpage Read reported 10 corrected bit errors Successfully corrected 5 bit errors per subpage Read reported 12 corrected bit errors Successfully corrected 6 bit errors per subpage Read reported 14 corrected bit errors Successfully corrected 7 bit errors per subpage Read reported 16 corrected bit errors Successfully corrected 8 bit errors per subpage Failed to recover 1 bitflips Read error after 9 bit errors per page Regarding the throughput improvements, tests have been conducted in 1-1-1 and 1-1-4 modes, reading a full block X pages at a time, X ranging from 1 to 64 (size of a block with the tested device). The percent value on the right is the comparison of the same test conducted without the continuous read feature, ie. reading X pages in one single user request, which got naturally split by the core whit the continuous read optimization disabled into single-page reads. * 1-1-1 result: 1 page read speed is 2634 KiB/s 2 page read speed is 2704 KiB/s (+3%) 3 page read speed is 2747 KiB/s (+5%) 4 page read speed is 2804 KiB/s (+7%) 5 page read speed is 2782 KiB/s 6 page read speed is 2826 KiB/s 7 page read speed is 2834 KiB/s 8 page read speed is 2821 KiB/s 9 page read speed is 2846 KiB/s 10 page read speed is 2819 KiB/s 11 page read speed is 2871 KiB/s (+10%) 12 page read speed is 2823 KiB/s 13 page read speed is 2880 KiB/s 14 page read speed is 2842 KiB/s 15 page read speed is 2862 KiB/s 16 page read speed is 2837 KiB/s 32 page read speed is 2879 KiB/s 64 page read speed is 2842 KiB/s * 1-1-4 result: 1 page read speed is 7562 KiB/s 2 page read speed is 8904 KiB/s (+15%) 3 page read speed is 9655 KiB/s (+25%) 4 page read speed is 10118 KiB/s (+30%) 5 page read speed is 10084 KiB/s 6 page read speed is 10300 KiB/s 7 page read speed is 10434 KiB/s (+35%) 8 page read speed is 10406 KiB/s 9 page read speed is 10769 KiB/s (+40%) 10 page read speed is 10666 KiB/s 11 page read speed is 10757 KiB/s 12 page read speed is 10835 KiB/s 13 page read speed is 10976 KiB/s 14 page read speed is 11200 KiB/s 15 page read speed is 11009 KiB/s 16 page read speed is 11082 KiB/s 32 page read speed is 11352 KiB/s (+45%) 64 page read speed is 11403 KiB/s This work has received support and could be achieved thanks to Alvin Zhou . Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240826101412.20644-10-miquel.raynal@bootlin.com --- drivers/mtd/nand/spi/macronix.c | 115 ++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 29 deletions(-) diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c index a4feeb030258..9c0c72b913ed 100644 --- a/drivers/mtd/nand/spi/macronix.c +++ b/drivers/mtd/nand/spi/macronix.c @@ -5,15 +5,26 @@ * Author: Boris Brezillon */ +#include #include #include #include #define SPINAND_MFR_MACRONIX 0xC2 -#define MACRONIX_ECCSR_MASK 0x0F +#define MACRONIX_ECCSR_BF_LAST_PAGE(eccsr) FIELD_GET(GENMASK(3, 0), eccsr) +#define MACRONIX_ECCSR_BF_ACCUMULATED_PAGES(eccsr) FIELD_GET(GENMASK(7, 4), eccsr) +#define MACRONIX_CFG_CONT_READ BIT(2) #define STATUS_ECC_HAS_BITFLIPS_THRESHOLD (3 << 4) +/* Bitflip theshold configuration register */ +#define REG_CFG_BFT 0x10 +#define CFG_BFT(x) FIELD_PREP(GENMASK(7, 4), (x)) + +struct macronix_priv { + bool cont_read; +}; + static SPINAND_OP_VARIANTS(read_cache_variants, SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), @@ -53,6 +64,7 @@ static const struct mtd_ooblayout_ops mx35lfxge4ab_ooblayout = { static int macronix_get_eccsr(struct spinand_device *spinand, u8 *eccsr) { + struct macronix_priv *priv = spinand->priv; struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0x7c, 1), SPI_MEM_OP_NO_ADDR, SPI_MEM_OP_DUMMY(1, 1), @@ -62,33 +74,25 @@ static int macronix_get_eccsr(struct spinand_device *spinand, u8 *eccsr) if (ret) return ret; - *eccsr &= MACRONIX_ECCSR_MASK; - return 0; -} - -static int macronix_get_bf(struct spinand_device *spinand, u8 status) -{ - struct nand_device *nand = spinand_to_nand(spinand); - u8 eccsr; - /* - * Let's try to retrieve the real maximum number of bitflips - * in order to avoid forcing the wear-leveling layer to move - * data around if it's not necessary. + * ECCSR exposes the number of bitflips for the last read page in bits [3:0]. + * Continuous read compatible chips also expose the maximum number of + * bitflips for the whole (continuous) read operation in bits [7:4]. */ - if (macronix_get_eccsr(spinand, spinand->scratchbuf)) - return nanddev_get_ecc_conf(nand)->strength; + if (!priv->cont_read) + *eccsr = MACRONIX_ECCSR_BF_LAST_PAGE(*eccsr); + else + *eccsr = MACRONIX_ECCSR_BF_ACCUMULATED_PAGES(*eccsr); - eccsr = *spinand->scratchbuf; - if (WARN_ON(eccsr > nanddev_get_ecc_conf(nand)->strength || !eccsr)) - return nanddev_get_ecc_conf(nand)->strength; - - return eccsr; + return 0; } static int macronix_ecc_get_status(struct spinand_device *spinand, u8 status) { + struct nand_device *nand = spinand_to_nand(spinand); + u8 eccsr; + switch (status & STATUS_ECC_MASK) { case STATUS_ECC_NO_BITFLIPS: return 0; @@ -97,8 +101,19 @@ static int macronix_ecc_get_status(struct spinand_device *spinand, return -EBADMSG; case STATUS_ECC_HAS_BITFLIPS: - case STATUS_ECC_HAS_BITFLIPS_THRESHOLD: - return macronix_get_bf(spinand, status); + /* + * Let's try to retrieve the real maximum number of bitflips + * in order to avoid forcing the wear-leveling layer to move + * data around if it's not necessary. + */ + if (macronix_get_eccsr(spinand, spinand->scratchbuf)) + return nanddev_get_ecc_conf(nand)->strength; + + eccsr = *spinand->scratchbuf; + if (WARN_ON(eccsr > nanddev_get_ecc_conf(nand)->strength || !eccsr)) + return nanddev_get_ecc_conf(nand)->strength; + + return eccsr; default: break; } @@ -106,6 +121,21 @@ static int macronix_ecc_get_status(struct spinand_device *spinand, return -EINVAL; } +static int macronix_set_cont_read(struct spinand_device *spinand, bool enable) +{ + struct macronix_priv *priv = spinand->priv; + int ret; + + ret = spinand_upd_cfg(spinand, MACRONIX_CFG_CONT_READ, + enable ? MACRONIX_CFG_CONT_READ : 0); + if (ret) + return ret; + + priv->cont_read = enable; + + return 0; +} + static const struct spinand_info macronix_spinand_table[] = { SPINAND_INFO("MX35LF1GE4AB", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x12), @@ -135,7 +165,8 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - macronix_ecc_get_status)), + macronix_ecc_get_status), + SPINAND_CONT_READ(macronix_set_cont_read)), SPINAND_INFO("MX35LF4GE4AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x37, 0x03), NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1), @@ -145,7 +176,8 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - macronix_ecc_get_status)), + macronix_ecc_get_status), + SPINAND_CONT_READ(macronix_set_cont_read)), SPINAND_INFO("MX35LF1G24AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14, 0x03), NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), @@ -251,7 +283,8 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - macronix_ecc_get_status)), + macronix_ecc_get_status), + SPINAND_CONT_READ(macronix_set_cont_read)), SPINAND_INFO("MX35UF2G14AC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa0), NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), @@ -291,7 +324,8 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - macronix_ecc_get_status)), + macronix_ecc_get_status), + SPINAND_CONT_READ(macronix_set_cont_read)), SPINAND_INFO("MX35UF2GE4AC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa2, 0x01), NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), @@ -301,7 +335,8 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - macronix_ecc_get_status)), + macronix_ecc_get_status), + SPINAND_CONT_READ(macronix_set_cont_read)), SPINAND_INFO("MX35UF1G14AC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x90), NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), @@ -331,7 +366,8 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - macronix_ecc_get_status)), + macronix_ecc_get_status), + SPINAND_CONT_READ(macronix_set_cont_read)), SPINAND_INFO("MX35UF1GE4AC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x92, 0x01), NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), @@ -341,7 +377,8 @@ static const struct spinand_info macronix_spinand_table[] = { &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, - macronix_ecc_get_status)), + macronix_ecc_get_status), + SPINAND_CONT_READ(macronix_set_cont_read)), SPINAND_INFO("MX31LF2GE4BC", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x2e), NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), @@ -364,7 +401,27 @@ static const struct spinand_info macronix_spinand_table[] = { macronix_ecc_get_status)), }; +static int macronix_spinand_init(struct spinand_device *spinand) +{ + struct macronix_priv *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spinand->priv = priv; + + return 0; +} + +static void macronix_spinand_cleanup(struct spinand_device *spinand) +{ + kfree(spinand->priv); +} + static const struct spinand_manufacturer_ops macronix_spinand_manuf_ops = { + .init = macronix_spinand_init, + .cleanup = macronix_spinand_cleanup, }; const struct spinand_manufacturer macronix_spinand_manufacturer = { From 9ab52d9800b0d71deafb0f9a3ed4d29b69089ad2 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 26 Aug 2024 17:31:58 +0200 Subject: [PATCH 448/573] mtd: rawnand: meson: Fix typo in function name There is a reason why sometime we write "NAND chip" with an 's'. It usually means several chips can be managed by the same controller. So when initializing a single chip at a time, the wording "chip" must be used, otherwise when talking about all the chips managed by the controller, we want to use "chips". Fix the function name to clarify the meson_nfc_nand_chip*s*_cleanup() helper intend. Signed-off-by: Miquel Raynal Reviewed-by: Pratyush Yadav Link: https://lore.kernel.org/linux-mtd/20240826153158.67334-1-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/meson_nand.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/meson_nand.c b/drivers/mtd/nand/raw/meson_nand.c index 8806a06462ac..fbb06aa305cb 100644 --- a/drivers/mtd/nand/raw/meson_nand.c +++ b/drivers/mtd/nand/raw/meson_nand.c @@ -1475,7 +1475,7 @@ meson_nfc_nand_chip_init(struct device *dev, return 0; } -static void meson_nfc_nand_chip_cleanup(struct meson_nfc *nfc) +static void meson_nfc_nand_chips_cleanup(struct meson_nfc *nfc) { struct meson_nfc_nand_chip *meson_chip; struct mtd_info *mtd; @@ -1500,7 +1500,7 @@ static int meson_nfc_nand_chips_init(struct device *dev, for_each_child_of_node_scoped(np, nand_np) { ret = meson_nfc_nand_chip_init(dev, nfc, nand_np); if (ret) { - meson_nfc_nand_chip_cleanup(nfc); + meson_nfc_nand_chips_cleanup(nfc); return ret; } } @@ -1614,7 +1614,7 @@ static void meson_nfc_remove(struct platform_device *pdev) { struct meson_nfc *nfc = platform_get_drvdata(pdev); - meson_nfc_nand_chip_cleanup(nfc); + meson_nfc_nand_chips_cleanup(nfc); meson_nfc_disable_clk(nfc); } From 9b3c395096dcfd180877776d492173419a5251d2 Mon Sep 17 00:00:00 2001 From: Alexander Dahl Date: Wed, 28 Aug 2024 08:37:06 +0200 Subject: [PATCH 449/573] mtd: rawnand: atmel: Add message on DMA usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Like for other atmel drivers (serial, crypto, mmc, …), too. Signed-off-by: Alexander Dahl Acked-by: Nicolas Ferre Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240828063707.73869-1-ada@thorsis.com --- drivers/mtd/nand/raw/atmel/nand-controller.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c index dc75d50d52e8..f9ccfd02e804 100644 --- a/drivers/mtd/nand/raw/atmel/nand-controller.c +++ b/drivers/mtd/nand/raw/atmel/nand-controller.c @@ -2049,7 +2049,10 @@ static int atmel_nand_controller_init(struct atmel_nand_controller *nc, dma_cap_set(DMA_MEMCPY, mask); nc->dmac = dma_request_channel(mask, NULL, NULL); - if (!nc->dmac) + if (nc->dmac) + dev_info(nc->dev, "using %s for DMA transfers\n", + dma_chan_name(nc->dmac)); + else dev_err(nc->dev, "Failed to request DMA channel\n"); } From 81cb3be3261e766a1f8efab9e3154a4f4fd9d03d Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 26 Aug 2024 17:30:18 +0200 Subject: [PATCH 450/573] mtd: rawnand: mtk: Factorize out the logic cleaning mtk chips There are some un-freed resources in one of the error path which would benefit from a helper going through all the registered mtk chips one by one and perform all the necessary cleanup. This is precisely what the remove path does, so let's extract the logic in a helper. There is no functional change. Signed-off-by: Miquel Raynal Reviewed-by: Pratyush Yadav Reviewed-by: Matthias Brugger Link: https://lore.kernel.org/linux-mtd/20240826153019.67106-1-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/mtk_nand.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/drivers/mtd/nand/raw/mtk_nand.c b/drivers/mtd/nand/raw/mtk_nand.c index d65e6371675b..bf845dd16737 100644 --- a/drivers/mtd/nand/raw/mtk_nand.c +++ b/drivers/mtd/nand/raw/mtk_nand.c @@ -1429,6 +1429,23 @@ static int mtk_nfc_nand_chip_init(struct device *dev, struct mtk_nfc *nfc, return 0; } +static void mtk_nfc_nand_chips_cleanup(struct mtk_nfc *nfc) +{ + struct mtk_nfc_nand_chip *mtk_chip; + struct nand_chip *chip; + int ret; + + while (!list_empty(&nfc->chips)) { + mtk_chip = list_first_entry(&nfc->chips, + struct mtk_nfc_nand_chip, node); + chip = &mtk_chip->nand; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + list_del(&mtk_chip->node); + } +} + static int mtk_nfc_nand_chips_init(struct device *dev, struct mtk_nfc *nfc) { struct device_node *np = dev->of_node; @@ -1567,20 +1584,8 @@ release_ecc: static void mtk_nfc_remove(struct platform_device *pdev) { struct mtk_nfc *nfc = platform_get_drvdata(pdev); - struct mtk_nfc_nand_chip *mtk_chip; - struct nand_chip *chip; - int ret; - - while (!list_empty(&nfc->chips)) { - mtk_chip = list_first_entry(&nfc->chips, - struct mtk_nfc_nand_chip, node); - chip = &mtk_chip->nand; - ret = mtd_device_unregister(nand_to_mtd(chip)); - WARN_ON(ret); - nand_cleanup(chip); - list_del(&mtk_chip->node); - } + mtk_nfc_nand_chips_cleanup(nfc); mtk_ecc_release(nfc->ecc); } From 395999829880a106bb95f0ce34e6e4c2b43c6a5d Mon Sep 17 00:00:00 2001 From: Charles Han Date: Wed, 28 Aug 2024 17:24:27 +0800 Subject: [PATCH 451/573] mtd: powernv: Add check devm_kasprintf() returned value devm_kasprintf() can return a NULL pointer on failure but this returned value is not checked. Fixes: acfe63ec1c59 ("mtd: Convert to using %pOFn instead of device_node.name") Signed-off-by: Charles Han Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240828092427.128177-1-hanchunchao@inspur.com --- drivers/mtd/devices/powernv_flash.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mtd/devices/powernv_flash.c b/drivers/mtd/devices/powernv_flash.c index 66044f4f5bad..10cd1d9b4885 100644 --- a/drivers/mtd/devices/powernv_flash.c +++ b/drivers/mtd/devices/powernv_flash.c @@ -207,6 +207,9 @@ static int powernv_flash_set_driver_info(struct device *dev, * get them */ mtd->name = devm_kasprintf(dev, GFP_KERNEL, "%pOFP", dev->of_node); + if (!mtd->name) + return -ENOMEM; + mtd->type = MTD_NORFLASH; mtd->flags = MTD_WRITEABLE; mtd->size = size; From d22bd451d5606411895ef55cb105277e4f4f6e54 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 8 Sep 2024 10:32:38 +0200 Subject: [PATCH 452/573] hwmon: (pmbus/mpq7932) Constify struct regulator_desc 'struct regulator_desc' is not modified in this driver. Constifying this structure moves some data to a read-only section, so increase overall security, especially when the structure holds some function pointers. This also makes mpq7932_regulators_desc consistent with mpq7932_regulators_desc_one which is already a "static const struct regulator_desc". On a x86_64, with allmodconfig: Before: ====== text data bss dec hex filename 3516 2264 0 5780 1694 drivers/hwmon/pmbus/mpq7932.o After: ===== text data bss dec hex filename 5396 384 0 5780 1694 drivers/hwmon/pmbus/mpq7932.o Signed-off-by: Christophe JAILLET Message-ID: Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/mpq7932.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/mpq7932.c b/drivers/hwmon/pmbus/mpq7932.c index 67487867c70f..2dcb6da853bd 100644 --- a/drivers/hwmon/pmbus/mpq7932.c +++ b/drivers/hwmon/pmbus/mpq7932.c @@ -35,7 +35,7 @@ struct mpq7932_data { }; #if IS_ENABLED(CONFIG_SENSORS_MPQ7932_REGULATOR) -static struct regulator_desc mpq7932_regulators_desc[] = { +static const struct regulator_desc mpq7932_regulators_desc[] = { PMBUS_REGULATOR_STEP("buck", 0, MPQ7932_N_VOLTAGES, MPQ7932_UV_STEP, MPQ7932_BUCK_UV_MIN), PMBUS_REGULATOR_STEP("buck", 1, MPQ7932_N_VOLTAGES, From 2073ae37d550ea32e8545edaa94ef10b4fef7235 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Mon, 26 Aug 2024 17:30:19 +0200 Subject: [PATCH 453/573] mtd: rawnand: mtk: Fix init error path Reviewing a series converting the for_each_chil_of_node() loops into their _scoped variants made me realize there was no cleanup of the already registered NAND devices upon error which may leak memory on systems with more than a chip when this error occurs. We should call the _nand_chips_cleanup() function when this happens. Fixes: 1d6b1e464950 ("mtd: mediatek: driver for MTK Smart Device") Signed-off-by: Miquel Raynal Reviewed-by: Pratyush Yadav Reviewed-by: Matthias Brugger Link: https://lore.kernel.org/linux-mtd/20240826153019.67106-2-miquel.raynal@bootlin.com --- drivers/mtd/nand/raw/mtk_nand.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/raw/mtk_nand.c b/drivers/mtd/nand/raw/mtk_nand.c index bf845dd16737..586868b4139f 100644 --- a/drivers/mtd/nand/raw/mtk_nand.c +++ b/drivers/mtd/nand/raw/mtk_nand.c @@ -1453,8 +1453,10 @@ static int mtk_nfc_nand_chips_init(struct device *dev, struct mtk_nfc *nfc) for_each_child_of_node_scoped(np, nand_np) { ret = mtk_nfc_nand_chip_init(dev, nfc, nand_np); - if (ret) + if (ret) { + mtk_nfc_nand_chips_cleanup(nfc); return ret; + } } return 0; From d53c35931b95b707df3b6cff061ff975843837d5 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Tue, 3 Sep 2024 19:29:57 +0300 Subject: [PATCH 454/573] dt-bindings: mtd: ti, gpmc-nand: support partitions node Allow fixed-partitions to be specified through a partitions node. Fixes the below dtbs_check warning: arch/arm64/boot/dts/ti/k3-am642-evm-nand.dtb: nand@0,0: Unevaluated properties are not allowed ('partitions' was unexpected) Signed-off-by: Roger Quadros Reviewed-by: Rob Herring (Arm) Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240903-gpmc-dtb-v2-1-8046c1915b96@kernel.org --- Documentation/devicetree/bindings/mtd/ti,gpmc-nand.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/mtd/ti,gpmc-nand.yaml b/Documentation/devicetree/bindings/mtd/ti,gpmc-nand.yaml index 115682fa81b7..00540302bcae 100644 --- a/Documentation/devicetree/bindings/mtd/ti,gpmc-nand.yaml +++ b/Documentation/devicetree/bindings/mtd/ti,gpmc-nand.yaml @@ -61,12 +61,9 @@ properties: GPIO connection to R/B signal from NAND chip maxItems: 1 -patternProperties: - "@[0-9a-f]+$": - $ref: /schemas/mtd/partitions/partition.yaml - allOf: - $ref: /schemas/memory-controllers/ti,gpmc-child.yaml + - $ref: mtd.yaml# required: - compatible From ca229bdbef29be207c8666f106acc3bf50736c05 Mon Sep 17 00:00:00 2001 From: Cheng Ming Lin Date: Mon, 9 Sep 2024 17:26:42 +0800 Subject: [PATCH 455/573] mtd: spinand: Add support for setting plane select bits Add two flags for inserting the Plane Select bit into the column address during the write_to_cache and the read_from_cache operation. Add the SPINAND_HAS_PROG_PLANE_SELECT_BIT flag for serial NAND flash that require inserting the Plane Select bit into the column address during the write_to_cache operation. Add the SPINAND_HAS_READ_PLANE_SELECT_BIT flag for serial NAND flash that require inserting the Plane Select bit into the column address during the read_from_cache operation. Signed-off-by: Cheng Ming Lin Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240909092643.2434479-2-linchengming884@gmail.com --- drivers/mtd/nand/spi/core.c | 6 ++++++ include/linux/mtd/spinand.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index b5c6d755ade1..4d76f9f71a0e 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -408,6 +408,9 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand, else rdesc = spinand->dirmaps[req->pos.plane].rdesc_ecc; + if (spinand->flags & SPINAND_HAS_READ_PLANE_SELECT_BIT) + column |= req->pos.plane << fls(nanddev_page_size(nand)); + while (nbytes) { ret = spi_mem_dirmap_read(rdesc, column, nbytes, buf); if (ret < 0) @@ -489,6 +492,9 @@ static int spinand_write_to_cache_op(struct spinand_device *spinand, else wdesc = spinand->dirmaps[req->pos.plane].wdesc_ecc; + if (spinand->flags & SPINAND_HAS_PROG_PLANE_SELECT_BIT) + column |= req->pos.plane << fls(nanddev_page_size(nand)); + while (nbytes) { ret = spi_mem_dirmap_write(wdesc, column, nbytes, buf); if (ret < 0) diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 8dbf67ca710a..702e5fb13dae 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -312,6 +312,8 @@ struct spinand_ecc_info { #define SPINAND_HAS_QE_BIT BIT(0) #define SPINAND_HAS_CR_FEAT_BIT BIT(1) +#define SPINAND_HAS_PROG_PLANE_SELECT_BIT BIT(2) +#define SPINAND_HAS_READ_PLANE_SELECT_BIT BIT(3) /** * struct spinand_ondie_ecc_conf - private SPI-NAND on-die ECC engine structure From 475aadeba5df687524bd9bc5c77cba44c1736e08 Mon Sep 17 00:00:00 2001 From: Cheng Ming Lin Date: Mon, 9 Sep 2024 17:26:43 +0800 Subject: [PATCH 456/573] mtd: spinand: macronix: Flag parts needing explicit plane select Macronix serial NAND flash with a two-plane structure requires insertion of the Plane Select bit into the column address during the write_to_cache operation. Additionally, for MX35{U,F}2G14AC and MX35LF2GE4AB, insertion of the Plane Select bit into the column address is required during the read_from_cache operation. Signed-off-by: Cheng Ming Lin Signed-off-by: Miquel Raynal Link: https://lore.kernel.org/linux-mtd/20240909092643.2434479-3-linchengming884@gmail.com --- drivers/mtd/nand/spi/macronix.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c index 9c0c72b913ed..d277c3220fdc 100644 --- a/drivers/mtd/nand/spi/macronix.c +++ b/drivers/mtd/nand/spi/macronix.c @@ -154,7 +154,9 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_variants, &write_cache_variants, &update_cache_variants), - SPINAND_HAS_QE_BIT, + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT | + SPINAND_HAS_READ_PLANE_SELECT_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), SPINAND_INFO("MX35LF2GE4AD", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x26, 0x03), @@ -194,7 +196,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_variants, &write_cache_variants, &update_cache_variants), - SPINAND_HAS_QE_BIT, + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), SPINAND_INFO("MX35LF2G24AD-Z4I8", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x64, 0x03), @@ -212,7 +215,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_variants, &write_cache_variants, &update_cache_variants), - SPINAND_HAS_QE_BIT, + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), SPINAND_INFO("MX35LF4G24AD-Z4I8", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x75, 0x03), @@ -251,7 +255,9 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_variants, &write_cache_variants, &update_cache_variants), - SPINAND_HAS_QE_BIT, + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT | + SPINAND_HAS_READ_PLANE_SELECT_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status)), SPINAND_INFO("MX35UF4G24AD", @@ -261,7 +267,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_variants, &write_cache_variants, &update_cache_variants), - SPINAND_HAS_QE_BIT, + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status)), SPINAND_INFO("MX35UF4G24AD-Z4I8", @@ -292,7 +299,9 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_variants, &write_cache_variants, &update_cache_variants), - SPINAND_HAS_QE_BIT, + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT | + SPINAND_HAS_READ_PLANE_SELECT_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status)), SPINAND_INFO("MX35UF2G24AD", @@ -302,7 +311,8 @@ static const struct spinand_info macronix_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_variants, &write_cache_variants, &update_cache_variants), - SPINAND_HAS_QE_BIT, + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT, SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, macronix_ecc_get_status)), SPINAND_INFO("MX35UF2G24AD-Z4I8", From 73048a832587520c6222bacbe1bc7121409d8861 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 5 Sep 2024 16:17:06 +0300 Subject: [PATCH 457/573] optee: Fix a NULL vs IS_ERR() check The tee_shm_get_va() function never returns NULL, it returns error pointers. Update the check to match. Fixes: f0c8431568ee ("optee: probe RPMB device using RPMB subsystem") Signed-off-by: Dan Carpenter Reviewed-by: Jens Wiklander Link: https://lore.kernel.org/r/f8c12aed-b5d1-4522-bf95-622b8569706d@stanley.mountain Signed-off-by: Ulf Hansson --- drivers/tee/optee/rpc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tee/optee/rpc.c b/drivers/tee/optee/rpc.c index a4b49fd1d46d..ebbbd42b0e3e 100644 --- a/drivers/tee/optee/rpc.c +++ b/drivers/tee/optee/rpc.c @@ -332,7 +332,7 @@ static void handle_rpc_func_rpmb_probe_next(struct tee_context *ctx, } buf = tee_shm_get_va(params[1].u.memref.shm, params[1].u.memref.shm_offs); - if (!buf) { + if (IS_ERR(buf)) { arg->ret = TEEC_ERROR_BAD_PARAMETERS; return; } From 19eb465c969f3d6ed1b98506d3e470e863b41e16 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sat, 7 Sep 2024 21:48:12 +0300 Subject: [PATCH 458/573] iommu/arm-smmu-qcom: apply num_context_bank fixes for SDM630 / SDM660 The Qualcomm SDM630 / SDM660 platform requires the same kind of workaround as MSM8998: some IOMMUs have context banks reserved by firmware / TZ, touching those banks resets the board. Apply the num_context_bank workaround to those two SMMU devices in order to allow them to be used by Linux. Fixes: b812834b5329 ("iommu: arm-smmu-qcom: Add sdm630/msm8998 compatibles for qcom quirks") Signed-off-by: Dmitry Baryshkov Reviewed-by: Bjorn Andersson Link: https://lore.kernel.org/r/20240907-sdm660-wifi-v1-1-e316055142f8@linaro.org Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c index 087fb4f6f4d3..6372f3e25c4b 100644 --- a/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c +++ b/drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c @@ -286,8 +286,15 @@ static int qcom_smmu_cfg_probe(struct arm_smmu_device *smmu) * MSM8998 LPASS SMMU reports 13 context banks, but accessing * the last context bank crashes the system. */ - if (of_device_is_compatible(smmu->dev->of_node, "qcom,msm8998-smmu-v2") && smmu->num_context_banks == 13) + if (of_device_is_compatible(smmu->dev->of_node, "qcom,msm8998-smmu-v2") && + smmu->num_context_banks == 13) { smmu->num_context_banks = 12; + } else if (of_device_is_compatible(smmu->dev->of_node, "qcom,sdm630-smmu-v2")) { + if (smmu->num_context_banks == 21) /* SDM630 / SDM660 A2NOC SMMU */ + smmu->num_context_banks = 7; + else if (smmu->num_context_banks == 14) /* SDM630 / SDM660 LPASS SMMU */ + smmu->num_context_banks = 13; + } /* * Some platforms support more than the Arm SMMU architected maximum of @@ -350,6 +357,11 @@ static int qcom_adreno_smmuv2_cfg_probe(struct arm_smmu_device *smmu) /* Support for 16K pages is advertised on some SoCs, but it doesn't seem to work */ smmu->features &= ~ARM_SMMU_FEAT_FMT_AARCH64_16K; + /* TZ protects several last context banks, hide them from Linux */ + if (of_device_is_compatible(smmu->dev->of_node, "qcom,sdm630-smmu-v2") && + smmu->num_context_banks == 5) + smmu->num_context_banks = 2; + return 0; } From ce410410f1a7db0259ca9282a285fb80fd553b8c Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 6 Sep 2024 12:47:48 -0300 Subject: [PATCH 459/573] iommu/arm-smmu-v3: Add arm_smmu_strtab_l1/2_idx() Don't open code the calculations of the indexes for each level, provide two functions to do that math and call them in all the places. Update all the places computing indexes. Calculate the L1 table size directly based on the max required index from the cap. Remove STRTAB_L1_SZ_SHIFT in favour of STRTAB_NUM_L2_STES. Use STRTAB_NUM_L2_STES to replace remaining open coded 1 << STRTAB_SPLIT. Tested-by: Nicolin Chen Reviewed-by: Nicolin Chen Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/1-v4-6416877274e1+1af-smmuv3_tidy_jgg@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 45 +++++++++------------ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 14 ++++++- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index fb8b71b02d96..cdf146bad5bd 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -1710,17 +1710,15 @@ static void arm_smmu_init_initial_stes(struct arm_smmu_ste *strtab, static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid) { size_t size; - void *strtab; dma_addr_t l2ptr_dma; struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg; - struct arm_smmu_strtab_l1_desc *desc = &cfg->l1_desc[sid >> STRTAB_SPLIT]; + struct arm_smmu_strtab_l1_desc *desc; + desc = &cfg->l1_desc[arm_smmu_strtab_l1_idx(sid)]; if (desc->l2ptr) return 0; - size = 1 << (STRTAB_SPLIT + ilog2(STRTAB_STE_DWORDS) + 3); - strtab = &cfg->strtab[(sid >> STRTAB_SPLIT) * STRTAB_L1_DESC_DWORDS]; - + size = STRTAB_NUM_L2_STES * sizeof(struct arm_smmu_ste); desc->l2ptr = dmam_alloc_coherent(smmu->dev, size, &l2ptr_dma, GFP_KERNEL); if (!desc->l2ptr) { @@ -1730,8 +1728,9 @@ static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid) return -ENOMEM; } - arm_smmu_init_initial_stes(desc->l2ptr, 1 << STRTAB_SPLIT); - arm_smmu_write_strtab_l1_desc(strtab, l2ptr_dma); + arm_smmu_init_initial_stes(desc->l2ptr, STRTAB_NUM_L2_STES); + arm_smmu_write_strtab_l1_desc(&cfg->strtab[arm_smmu_strtab_l1_idx(sid)], + l2ptr_dma); return 0; } @@ -2486,12 +2485,9 @@ arm_smmu_get_step_for_sid(struct arm_smmu_device *smmu, u32 sid) struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg; if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) { - unsigned int idx1, idx2; - /* Two-level walk */ - idx1 = (sid >> STRTAB_SPLIT) * STRTAB_L1_DESC_DWORDS; - idx2 = sid & ((1 << STRTAB_SPLIT) - 1); - return &cfg->l1_desc[idx1].l2ptr[idx2]; + return &cfg->l1_desc[arm_smmu_strtab_l1_idx(sid)] + .l2ptr[arm_smmu_strtab_l2_idx(sid)]; } else { /* Simple linear lookup */ return (struct arm_smmu_ste *)&cfg @@ -3195,12 +3191,9 @@ struct arm_smmu_device *arm_smmu_get_by_fwnode(struct fwnode_handle *fwnode) static bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid) { - unsigned long limit = smmu->strtab_cfg.num_l1_ents; - if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) - limit *= 1UL << STRTAB_SPLIT; - - return sid < limit; + return arm_smmu_strtab_l1_idx(sid) < smmu->strtab_cfg.num_l1_ents; + return sid < smmu->strtab_cfg.num_l1_ents; } static int arm_smmu_init_sid_strtab(struct arm_smmu_device *smmu, u32 sid) @@ -3637,19 +3630,18 @@ static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu) { void *strtab; u64 reg; - u32 size, l1size; + u32 l1size; struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg; + unsigned int last_sid_idx = + arm_smmu_strtab_l1_idx((1 << smmu->sid_bits) - 1); /* Calculate the L1 size, capped to the SIDSIZE. */ - size = STRTAB_L1_SZ_SHIFT - (ilog2(STRTAB_L1_DESC_DWORDS) + 3); - size = min(size, smmu->sid_bits - STRTAB_SPLIT); - cfg->num_l1_ents = 1 << size; - - size += STRTAB_SPLIT; - if (size < smmu->sid_bits) + cfg->num_l1_ents = min(last_sid_idx + 1, STRTAB_MAX_L1_ENTRIES); + if (cfg->num_l1_ents <= last_sid_idx) dev_warn(smmu->dev, "2-level strtab only covers %u/%u bits of SID\n", - size, smmu->sid_bits); + ilog2(cfg->num_l1_ents * STRTAB_NUM_L2_STES), + smmu->sid_bits); l1size = cfg->num_l1_ents * (STRTAB_L1_DESC_DWORDS << 3); strtab = dmam_alloc_coherent(smmu->dev, l1size, &cfg->strtab_dma, @@ -3664,7 +3656,8 @@ static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu) /* Configure strtab_base_cfg for 2 levels */ reg = FIELD_PREP(STRTAB_BASE_CFG_FMT, STRTAB_BASE_CFG_FMT_2LVL); - reg |= FIELD_PREP(STRTAB_BASE_CFG_LOG2SIZE, size); + reg |= FIELD_PREP(STRTAB_BASE_CFG_LOG2SIZE, + ilog2(cfg->num_l1_ents) + STRTAB_SPLIT); reg |= FIELD_PREP(STRTAB_BASE_CFG_SPLIT, STRTAB_SPLIT); cfg->strtab_base_cfg = reg; diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index e8320e9341d7..7d8ed274af40 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -204,7 +204,6 @@ struct arm_smmu_device; * 2lvl: 128k L1 entries, * 256 lazy entries per table (each table covers a PCI bus) */ -#define STRTAB_L1_SZ_SHIFT 20 #define STRTAB_SPLIT 8 #define STRTAB_L1_DESC_DWORDS 1 @@ -217,6 +216,19 @@ struct arm_smmu_ste { __le64 data[STRTAB_STE_DWORDS]; }; +#define STRTAB_NUM_L2_STES (1 << STRTAB_SPLIT) +#define STRTAB_MAX_L1_ENTRIES (1 << 17) + +static inline u32 arm_smmu_strtab_l1_idx(u32 sid) +{ + return sid / STRTAB_NUM_L2_STES; +} + +static inline u32 arm_smmu_strtab_l2_idx(u32 sid) +{ + return sid % STRTAB_NUM_L2_STES; +} + #define STRTAB_STE_0_V (1UL << 0) #define STRTAB_STE_0_CFG GENMASK_ULL(3, 1) #define STRTAB_STE_0_CFG_ABORT 0 From abb4f9d323a8d53870cc842d3c5024f71c2d4951 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 6 Sep 2024 12:47:49 -0300 Subject: [PATCH 460/573] iommu/arm-smmu-v3: Add types for each level of the 2 level stream table Add types struct arm_smmu_strtab_l1 and l2 to represent the HW layout of the descriptors, and use them in most places, following patches will get the remaing places. The size of the l1 and l2 HW allocations are sizeof(struct arm_smmu_strtab_l1/2). This provides some more clarity than having raw __le64 *'s and sizes computed via macros. Remove STRTAB_L1_DESC_DWORDS. Tested-by: Nicolin Chen Reviewed-by: Nicolin Chen Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/2-v4-6416877274e1+1af-smmuv3_tidy_jgg@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 21 +++++++++++---------- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 10 ++++++++-- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index cdf146bad5bd..f9faaec946c2 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -1496,7 +1496,8 @@ static void arm_smmu_free_cd_tables(struct arm_smmu_master *master) } /* Stream table manipulation functions */ -static void arm_smmu_write_strtab_l1_desc(__le64 *dst, dma_addr_t l2ptr_dma) +static void arm_smmu_write_strtab_l1_desc(struct arm_smmu_strtab_l1 *dst, + dma_addr_t l2ptr_dma) { u64 val = 0; @@ -1504,7 +1505,7 @@ static void arm_smmu_write_strtab_l1_desc(__le64 *dst, dma_addr_t l2ptr_dma) val |= l2ptr_dma & STRTAB_L1_DESC_L2PTR_MASK; /* The HW has 64 bit atomicity with stores to the L2 STE table */ - WRITE_ONCE(*dst, cpu_to_le64(val)); + WRITE_ONCE(dst->l2ptr, cpu_to_le64(val)); } struct arm_smmu_ste_writer { @@ -1709,18 +1710,17 @@ static void arm_smmu_init_initial_stes(struct arm_smmu_ste *strtab, static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid) { - size_t size; dma_addr_t l2ptr_dma; struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg; struct arm_smmu_strtab_l1_desc *desc; + __le64 *dst; desc = &cfg->l1_desc[arm_smmu_strtab_l1_idx(sid)]; if (desc->l2ptr) return 0; - size = STRTAB_NUM_L2_STES * sizeof(struct arm_smmu_ste); - desc->l2ptr = dmam_alloc_coherent(smmu->dev, size, &l2ptr_dma, - GFP_KERNEL); + desc->l2ptr = dmam_alloc_coherent(smmu->dev, sizeof(*desc->l2ptr), + &l2ptr_dma, GFP_KERNEL); if (!desc->l2ptr) { dev_err(smmu->dev, "failed to allocate l2 stream table for SID %u\n", @@ -1728,8 +1728,9 @@ static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid) return -ENOMEM; } - arm_smmu_init_initial_stes(desc->l2ptr, STRTAB_NUM_L2_STES); - arm_smmu_write_strtab_l1_desc(&cfg->strtab[arm_smmu_strtab_l1_idx(sid)], + arm_smmu_init_initial_stes(desc->l2ptr->stes, STRTAB_NUM_L2_STES); + dst = &cfg->strtab[arm_smmu_strtab_l1_idx(sid)]; + arm_smmu_write_strtab_l1_desc((struct arm_smmu_strtab_l1 *)dst, l2ptr_dma); return 0; } @@ -2487,7 +2488,7 @@ arm_smmu_get_step_for_sid(struct arm_smmu_device *smmu, u32 sid) if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) { /* Two-level walk */ return &cfg->l1_desc[arm_smmu_strtab_l1_idx(sid)] - .l2ptr[arm_smmu_strtab_l2_idx(sid)]; + .l2ptr->stes[arm_smmu_strtab_l2_idx(sid)]; } else { /* Simple linear lookup */ return (struct arm_smmu_ste *)&cfg @@ -3643,7 +3644,7 @@ static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu) ilog2(cfg->num_l1_ents * STRTAB_NUM_L2_STES), smmu->sid_bits); - l1size = cfg->num_l1_ents * (STRTAB_L1_DESC_DWORDS << 3); + l1size = cfg->num_l1_ents * sizeof(struct arm_smmu_strtab_l1); strtab = dmam_alloc_coherent(smmu->dev, l1size, &cfg->strtab_dma, GFP_KERNEL); if (!strtab) { diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 7d8ed274af40..b25dd7217941 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -206,7 +206,6 @@ struct arm_smmu_device; */ #define STRTAB_SPLIT 8 -#define STRTAB_L1_DESC_DWORDS 1 #define STRTAB_L1_DESC_SPAN GENMASK_ULL(4, 0) #define STRTAB_L1_DESC_L2PTR_MASK GENMASK_ULL(51, 6) @@ -217,6 +216,13 @@ struct arm_smmu_ste { }; #define STRTAB_NUM_L2_STES (1 << STRTAB_SPLIT) +struct arm_smmu_strtab_l2 { + struct arm_smmu_ste stes[STRTAB_NUM_L2_STES]; +}; + +struct arm_smmu_strtab_l1 { + __le64 l2ptr; +}; #define STRTAB_MAX_L1_ENTRIES (1 << 17) static inline u32 arm_smmu_strtab_l1_idx(u32 sid) @@ -608,7 +614,7 @@ struct arm_smmu_priq { /* High-level stream table and context descriptor structures */ struct arm_smmu_strtab_l1_desc { - struct arm_smmu_ste *l2ptr; + struct arm_smmu_strtab_l2 *l2ptr; }; struct arm_smmu_ctx_desc { From 85196f54743d97b0678e7889df72fdcc58ab2b02 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 6 Sep 2024 12:47:50 -0300 Subject: [PATCH 461/573] iommu/arm-smmu-v3: Reorganize struct arm_smmu_strtab_cfg The members here are being used for both the linear and the 2 level case, with the meaning of each item slightly different in the two cases. Split it into a clean union where both cases have their own struct with their own logical names and correct types. Adjust all the users to detect linear/2lvl and use the right sub structure and types consistently. Remove STRTAB_STE_DWORDS by changing the last places to use sizeof(struct arm_smmu_ste). Tested-by: Nicolin Chen Reviewed-by: Nicolin Chen Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/3-v4-6416877274e1+1af-smmuv3_tidy_jgg@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 78 ++++++++++----------- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 22 +++--- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index f9faaec946c2..336803fb1f89 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -1712,25 +1712,24 @@ static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid) { dma_addr_t l2ptr_dma; struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg; - struct arm_smmu_strtab_l1_desc *desc; - __le64 *dst; + struct arm_smmu_strtab_l2 **l2table; - desc = &cfg->l1_desc[arm_smmu_strtab_l1_idx(sid)]; - if (desc->l2ptr) + l2table = &cfg->l2.l2ptrs[arm_smmu_strtab_l1_idx(sid)]; + if (*l2table) return 0; - desc->l2ptr = dmam_alloc_coherent(smmu->dev, sizeof(*desc->l2ptr), - &l2ptr_dma, GFP_KERNEL); - if (!desc->l2ptr) { + *l2table = dmam_alloc_coherent(smmu->dev, sizeof(**l2table), + &l2ptr_dma, GFP_KERNEL); + if (!*l2table) { dev_err(smmu->dev, "failed to allocate l2 stream table for SID %u\n", sid); return -ENOMEM; } - arm_smmu_init_initial_stes(desc->l2ptr->stes, STRTAB_NUM_L2_STES); - dst = &cfg->strtab[arm_smmu_strtab_l1_idx(sid)]; - arm_smmu_write_strtab_l1_desc((struct arm_smmu_strtab_l1 *)dst, + arm_smmu_init_initial_stes((*l2table)->stes, + ARRAY_SIZE((*l2table)->stes)); + arm_smmu_write_strtab_l1_desc(&cfg->l2.l1tab[arm_smmu_strtab_l1_idx(sid)], l2ptr_dma); return 0; } @@ -2487,12 +2486,11 @@ arm_smmu_get_step_for_sid(struct arm_smmu_device *smmu, u32 sid) if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) { /* Two-level walk */ - return &cfg->l1_desc[arm_smmu_strtab_l1_idx(sid)] - .l2ptr->stes[arm_smmu_strtab_l2_idx(sid)]; + return &cfg->l2.l2ptrs[arm_smmu_strtab_l1_idx(sid)] + ->stes[arm_smmu_strtab_l2_idx(sid)]; } else { /* Simple linear lookup */ - return (struct arm_smmu_ste *)&cfg - ->strtab[sid * STRTAB_STE_DWORDS]; + return &cfg->linear.table[sid]; } } @@ -3193,8 +3191,8 @@ struct arm_smmu_device *arm_smmu_get_by_fwnode(struct fwnode_handle *fwnode) static bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid) { if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) - return arm_smmu_strtab_l1_idx(sid) < smmu->strtab_cfg.num_l1_ents; - return sid < smmu->strtab_cfg.num_l1_ents; + return arm_smmu_strtab_l1_idx(sid) < smmu->strtab_cfg.l2.num_l1_ents; + return sid < smmu->strtab_cfg.linear.num_ents; } static int arm_smmu_init_sid_strtab(struct arm_smmu_device *smmu, u32 sid) @@ -3629,7 +3627,6 @@ static int arm_smmu_init_queues(struct arm_smmu_device *smmu) static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu) { - void *strtab; u64 reg; u32 l1size; struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg; @@ -3637,34 +3634,33 @@ static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu) arm_smmu_strtab_l1_idx((1 << smmu->sid_bits) - 1); /* Calculate the L1 size, capped to the SIDSIZE. */ - cfg->num_l1_ents = min(last_sid_idx + 1, STRTAB_MAX_L1_ENTRIES); - if (cfg->num_l1_ents <= last_sid_idx) + cfg->l2.num_l1_ents = min(last_sid_idx + 1, STRTAB_MAX_L1_ENTRIES); + if (cfg->l2.num_l1_ents <= last_sid_idx) dev_warn(smmu->dev, "2-level strtab only covers %u/%u bits of SID\n", - ilog2(cfg->num_l1_ents * STRTAB_NUM_L2_STES), + ilog2(cfg->l2.num_l1_ents * STRTAB_NUM_L2_STES), smmu->sid_bits); - l1size = cfg->num_l1_ents * sizeof(struct arm_smmu_strtab_l1); - strtab = dmam_alloc_coherent(smmu->dev, l1size, &cfg->strtab_dma, - GFP_KERNEL); - if (!strtab) { + l1size = cfg->l2.num_l1_ents * sizeof(struct arm_smmu_strtab_l1); + cfg->l2.l1tab = dmam_alloc_coherent(smmu->dev, l1size, &cfg->l2.l1_dma, + GFP_KERNEL); + if (!cfg->l2.l1tab) { dev_err(smmu->dev, "failed to allocate l1 stream table (%u bytes)\n", l1size); return -ENOMEM; } - cfg->strtab = strtab; /* Configure strtab_base_cfg for 2 levels */ reg = FIELD_PREP(STRTAB_BASE_CFG_FMT, STRTAB_BASE_CFG_FMT_2LVL); reg |= FIELD_PREP(STRTAB_BASE_CFG_LOG2SIZE, - ilog2(cfg->num_l1_ents) + STRTAB_SPLIT); + ilog2(cfg->l2.num_l1_ents) + STRTAB_SPLIT); reg |= FIELD_PREP(STRTAB_BASE_CFG_SPLIT, STRTAB_SPLIT); cfg->strtab_base_cfg = reg; - cfg->l1_desc = devm_kcalloc(smmu->dev, cfg->num_l1_ents, - sizeof(*cfg->l1_desc), GFP_KERNEL); - if (!cfg->l1_desc) + cfg->l2.l2ptrs = devm_kcalloc(smmu->dev, cfg->l2.num_l1_ents, + sizeof(*cfg->l2.l2ptrs), GFP_KERNEL); + if (!cfg->l2.l2ptrs) return -ENOMEM; return 0; @@ -3672,29 +3668,28 @@ static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu) static int arm_smmu_init_strtab_linear(struct arm_smmu_device *smmu) { - void *strtab; u64 reg; u32 size; struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg; - size = (1 << smmu->sid_bits) * (STRTAB_STE_DWORDS << 3); - strtab = dmam_alloc_coherent(smmu->dev, size, &cfg->strtab_dma, - GFP_KERNEL); - if (!strtab) { + size = (1 << smmu->sid_bits) * sizeof(struct arm_smmu_ste); + cfg->linear.table = dmam_alloc_coherent(smmu->dev, size, + &cfg->linear.ste_dma, + GFP_KERNEL); + if (!cfg->linear.table) { dev_err(smmu->dev, "failed to allocate linear stream table (%u bytes)\n", size); return -ENOMEM; } - cfg->strtab = strtab; - cfg->num_l1_ents = 1 << smmu->sid_bits; + cfg->linear.num_ents = 1 << smmu->sid_bits; /* Configure strtab_base_cfg for a linear table covering all SIDs */ reg = FIELD_PREP(STRTAB_BASE_CFG_FMT, STRTAB_BASE_CFG_FMT_LINEAR); reg |= FIELD_PREP(STRTAB_BASE_CFG_LOG2SIZE, smmu->sid_bits); cfg->strtab_base_cfg = reg; - arm_smmu_init_initial_stes(strtab, cfg->num_l1_ents); + arm_smmu_init_initial_stes(cfg->linear.table, cfg->linear.num_ents); return 0; } @@ -3703,16 +3698,17 @@ static int arm_smmu_init_strtab(struct arm_smmu_device *smmu) u64 reg; int ret; - if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) + if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) { ret = arm_smmu_init_strtab_2lvl(smmu); - else + reg = smmu->strtab_cfg.l2.l1_dma & STRTAB_BASE_ADDR_MASK; + } else { ret = arm_smmu_init_strtab_linear(smmu); - + reg = smmu->strtab_cfg.linear.ste_dma & STRTAB_BASE_ADDR_MASK; + } if (ret) return ret; /* Set the strtab base address */ - reg = smmu->strtab_cfg.strtab_dma & STRTAB_BASE_ADDR_MASK; reg |= STRTAB_BASE_RA; smmu->strtab_cfg.strtab_base = reg; diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index b25dd7217941..23918a547cbb 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -613,10 +613,6 @@ struct arm_smmu_priq { }; /* High-level stream table and context descriptor structures */ -struct arm_smmu_strtab_l1_desc { - struct arm_smmu_strtab_l2 *l2ptr; -}; - struct arm_smmu_ctx_desc { u16 asid; }; @@ -649,11 +645,19 @@ struct arm_smmu_s2_cfg { }; struct arm_smmu_strtab_cfg { - __le64 *strtab; - dma_addr_t strtab_dma; - struct arm_smmu_strtab_l1_desc *l1_desc; - unsigned int num_l1_ents; - + union { + struct { + struct arm_smmu_ste *table; + dma_addr_t ste_dma; + unsigned int num_ents; + } linear; + struct { + struct arm_smmu_strtab_l1 *l1tab; + struct arm_smmu_strtab_l2 **l2ptrs; + dma_addr_t l1_dma; + unsigned int num_l1_ents; + } l2; + }; u64 strtab_base; u32 strtab_base_cfg; }; From 8c153ef95697242b72646d2c4cf6c4b23ccf35a3 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 6 Sep 2024 12:47:51 -0300 Subject: [PATCH 462/573] iommu/arm-smmu-v3: Remove strtab_base/cfg These values can be computed from the other values already stored in the config. Move the calculation to arm_smmu_write_strtab() and do it directly before writing the registers. This moves all the logic to calculate the two registers into one function from three and saves an unimportant 16 bytes from the arm_smmu_device. Suggested-by: Nicolin Chen Reviewed-by: Nicolin Chen Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/4-v4-6416877274e1+1af-smmuv3_tidy_jgg@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 55 ++++++++++----------- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 2 - 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 336803fb1f89..bb6c556995a7 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -3627,7 +3627,6 @@ static int arm_smmu_init_queues(struct arm_smmu_device *smmu) static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu) { - u64 reg; u32 l1size; struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg; unsigned int last_sid_idx = @@ -3651,13 +3650,6 @@ static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu) return -ENOMEM; } - /* Configure strtab_base_cfg for 2 levels */ - reg = FIELD_PREP(STRTAB_BASE_CFG_FMT, STRTAB_BASE_CFG_FMT_2LVL); - reg |= FIELD_PREP(STRTAB_BASE_CFG_LOG2SIZE, - ilog2(cfg->l2.num_l1_ents) + STRTAB_SPLIT); - reg |= FIELD_PREP(STRTAB_BASE_CFG_SPLIT, STRTAB_SPLIT); - cfg->strtab_base_cfg = reg; - cfg->l2.l2ptrs = devm_kcalloc(smmu->dev, cfg->l2.num_l1_ents, sizeof(*cfg->l2.l2ptrs), GFP_KERNEL); if (!cfg->l2.l2ptrs) @@ -3668,7 +3660,6 @@ static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu) static int arm_smmu_init_strtab_linear(struct arm_smmu_device *smmu) { - u64 reg; u32 size; struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg; @@ -3684,34 +3675,21 @@ static int arm_smmu_init_strtab_linear(struct arm_smmu_device *smmu) } cfg->linear.num_ents = 1 << smmu->sid_bits; - /* Configure strtab_base_cfg for a linear table covering all SIDs */ - reg = FIELD_PREP(STRTAB_BASE_CFG_FMT, STRTAB_BASE_CFG_FMT_LINEAR); - reg |= FIELD_PREP(STRTAB_BASE_CFG_LOG2SIZE, smmu->sid_bits); - cfg->strtab_base_cfg = reg; - arm_smmu_init_initial_stes(cfg->linear.table, cfg->linear.num_ents); return 0; } static int arm_smmu_init_strtab(struct arm_smmu_device *smmu) { - u64 reg; int ret; - if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) { + if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) ret = arm_smmu_init_strtab_2lvl(smmu); - reg = smmu->strtab_cfg.l2.l1_dma & STRTAB_BASE_ADDR_MASK; - } else { + else ret = arm_smmu_init_strtab_linear(smmu); - reg = smmu->strtab_cfg.linear.ste_dma & STRTAB_BASE_ADDR_MASK; - } if (ret) return ret; - /* Set the strtab base address */ - reg |= STRTAB_BASE_RA; - smmu->strtab_cfg.strtab_base = reg; - ida_init(&smmu->vmid_map); return 0; @@ -3927,6 +3905,30 @@ static int arm_smmu_device_disable(struct arm_smmu_device *smmu) return ret; } +static void arm_smmu_write_strtab(struct arm_smmu_device *smmu) +{ + struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg; + dma_addr_t dma; + u32 reg; + + if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) { + reg = FIELD_PREP(STRTAB_BASE_CFG_FMT, + STRTAB_BASE_CFG_FMT_2LVL) | + FIELD_PREP(STRTAB_BASE_CFG_LOG2SIZE, + ilog2(cfg->l2.num_l1_ents) + STRTAB_SPLIT) | + FIELD_PREP(STRTAB_BASE_CFG_SPLIT, STRTAB_SPLIT); + dma = cfg->l2.l1_dma; + } else { + reg = FIELD_PREP(STRTAB_BASE_CFG_FMT, + STRTAB_BASE_CFG_FMT_LINEAR) | + FIELD_PREP(STRTAB_BASE_CFG_LOG2SIZE, smmu->sid_bits); + dma = cfg->linear.ste_dma; + } + writeq_relaxed((dma & STRTAB_BASE_ADDR_MASK) | STRTAB_BASE_RA, + smmu->base + ARM_SMMU_STRTAB_BASE); + writel_relaxed(reg, smmu->base + ARM_SMMU_STRTAB_BASE_CFG); +} + static int arm_smmu_device_reset(struct arm_smmu_device *smmu) { int ret; @@ -3962,10 +3964,7 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu) writel_relaxed(reg, smmu->base + ARM_SMMU_CR2); /* Stream table */ - writeq_relaxed(smmu->strtab_cfg.strtab_base, - smmu->base + ARM_SMMU_STRTAB_BASE); - writel_relaxed(smmu->strtab_cfg.strtab_base_cfg, - smmu->base + ARM_SMMU_STRTAB_BASE_CFG); + arm_smmu_write_strtab(smmu); /* Command queue */ writeq_relaxed(smmu->cmdq.q.q_base, smmu->base + ARM_SMMU_CMDQ_BASE); diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 23918a547cbb..33a8bee1b82e 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -658,8 +658,6 @@ struct arm_smmu_strtab_cfg { unsigned int num_l1_ents; } l2; }; - u64 strtab_base; - u32 strtab_base_cfg; }; struct arm_smmu_impl_ops { From 47b2de35cab2b683f69d03515c2658c2d8515323 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 6 Sep 2024 12:47:52 -0300 Subject: [PATCH 463/573] iommu/arm-smmu-v3: Do not use devm for the cd table allocations The master->cd_table is entirely contained within the struct arm_smmu_master which is guaranteed to be freed by the core code under arm_smmu_release_device(). There is no reason to use devm here, arm_smmu_free_cd_tables() is reliably called to free the CD related memory. Remove it and save some memory. Tested-by: Nicolin Chen Reviewed-by: Nicolin Chen Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/5-v4-6416877274e1+1af-smmuv3_tidy_jgg@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 29 +++++++++------------ 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index bb6c556995a7..c69d50e39b21 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -1222,8 +1222,8 @@ static int arm_smmu_alloc_cd_leaf_table(struct arm_smmu_device *smmu, { size_t size = CTXDESC_L2_ENTRIES * (CTXDESC_CD_DWORDS << 3); - l1_desc->l2ptr = dmam_alloc_coherent(smmu->dev, size, - &l1_desc->l2ptr_dma, GFP_KERNEL); + l1_desc->l2ptr = dma_alloc_coherent(smmu->dev, size, + &l1_desc->l2ptr_dma, GFP_KERNEL); if (!l1_desc->l2ptr) { dev_warn(smmu->dev, "failed to allocate context descriptor table\n"); @@ -1437,17 +1437,17 @@ static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master) cd_table->num_l1_ents = DIV_ROUND_UP(max_contexts, CTXDESC_L2_ENTRIES); - cd_table->l1_desc = devm_kcalloc(smmu->dev, cd_table->num_l1_ents, - sizeof(*cd_table->l1_desc), - GFP_KERNEL); + cd_table->l1_desc = kcalloc(cd_table->num_l1_ents, + sizeof(*cd_table->l1_desc), + GFP_KERNEL); if (!cd_table->l1_desc) return -ENOMEM; l1size = cd_table->num_l1_ents * (CTXDESC_L1_DESC_DWORDS << 3); } - cd_table->cdtab = dmam_alloc_coherent(smmu->dev, l1size, &cd_table->cdtab_dma, - GFP_KERNEL); + cd_table->cdtab = dma_alloc_coherent(smmu->dev, l1size, + &cd_table->cdtab_dma, GFP_KERNEL); if (!cd_table->cdtab) { dev_warn(smmu->dev, "failed to allocate context descriptor\n"); ret = -ENOMEM; @@ -1458,7 +1458,7 @@ static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master) err_free_l1: if (cd_table->l1_desc) { - devm_kfree(smmu->dev, cd_table->l1_desc); + kfree(cd_table->l1_desc); cd_table->l1_desc = NULL; } return ret; @@ -1478,21 +1478,18 @@ static void arm_smmu_free_cd_tables(struct arm_smmu_master *master) if (!cd_table->l1_desc[i].l2ptr) continue; - dmam_free_coherent(smmu->dev, size, - cd_table->l1_desc[i].l2ptr, - cd_table->l1_desc[i].l2ptr_dma); + dma_free_coherent(smmu->dev, size, + cd_table->l1_desc[i].l2ptr, + cd_table->l1_desc[i].l2ptr_dma); } - devm_kfree(smmu->dev, cd_table->l1_desc); - cd_table->l1_desc = NULL; + kfree(cd_table->l1_desc); l1size = cd_table->num_l1_ents * (CTXDESC_L1_DESC_DWORDS << 3); } else { l1size = cd_table->num_l1_ents * (CTXDESC_CD_DWORDS << 3); } - dmam_free_coherent(smmu->dev, l1size, cd_table->cdtab, cd_table->cdtab_dma); - cd_table->cdtab_dma = 0; - cd_table->cdtab = NULL; + dma_free_coherent(smmu->dev, l1size, cd_table->cdtab, cd_table->cdtab_dma); } /* Stream table manipulation functions */ From c0a25a96dee9c3af01fbcad227871fc0f222900b Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 6 Sep 2024 12:47:53 -0300 Subject: [PATCH 464/573] iommu/arm-smmu-v3: Shrink the cdtab l1_desc array The top of the 2 level CD table is (at most) 1024 entries big, and two high order allocations are required. One of __le64 which is programmed into the HW (8k) and one of struct arm_smmu_l1_ctx_desc which holds the CPU pointer (16k). There are two copies of the l2ptr_dma, one is stored in the struct arm_smmu_l1_ctx_desc, and another is encoded in the __le64 for the HW to use. Instead of storing two copies just decode the value from the __le64. Tested-by: Nicolin Chen Reviewed-by: Nicolin Chen Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/6-v4-6416877274e1+1af-smmuv3_tidy_jgg@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 42 +++++++++------------ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 - 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index c69d50e39b21..3c33ebffbe62 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -1217,31 +1217,19 @@ static void arm_smmu_sync_cd(struct arm_smmu_master *master, arm_smmu_cmdq_batch_submit(smmu, &cmds); } -static int arm_smmu_alloc_cd_leaf_table(struct arm_smmu_device *smmu, - struct arm_smmu_l1_ctx_desc *l1_desc) +static void arm_smmu_write_cd_l1_desc(__le64 *dst, dma_addr_t l2ptr_dma) { - size_t size = CTXDESC_L2_ENTRIES * (CTXDESC_CD_DWORDS << 3); - - l1_desc->l2ptr = dma_alloc_coherent(smmu->dev, size, - &l1_desc->l2ptr_dma, GFP_KERNEL); - if (!l1_desc->l2ptr) { - dev_warn(smmu->dev, - "failed to allocate context descriptor table\n"); - return -ENOMEM; - } - return 0; -} - -static void arm_smmu_write_cd_l1_desc(__le64 *dst, - struct arm_smmu_l1_ctx_desc *l1_desc) -{ - u64 val = (l1_desc->l2ptr_dma & CTXDESC_L1_DESC_L2PTR_MASK) | - CTXDESC_L1_DESC_V; + u64 val = (l2ptr_dma & CTXDESC_L1_DESC_L2PTR_MASK) | CTXDESC_L1_DESC_V; /* The HW has 64 bit atomicity with stores to the L2 CD table */ WRITE_ONCE(*dst, cpu_to_le64(val)); } +static dma_addr_t arm_smmu_cd_l1_get_desc(const __le64 *src) +{ + return le64_to_cpu(*src) & CTXDESC_L1_DESC_L2PTR_MASK; +} + struct arm_smmu_cd *arm_smmu_get_cd_ptr(struct arm_smmu_master *master, u32 ssid) { @@ -1281,13 +1269,18 @@ static struct arm_smmu_cd *arm_smmu_alloc_cd_ptr(struct arm_smmu_master *master, l1_desc = &cd_table->l1_desc[idx]; if (!l1_desc->l2ptr) { - __le64 *l1ptr; + dma_addr_t l2ptr_dma; + size_t size; - if (arm_smmu_alloc_cd_leaf_table(smmu, l1_desc)) + size = CTXDESC_L2_ENTRIES * sizeof(struct arm_smmu_cd); + l1_desc->l2ptr = dma_alloc_coherent(smmu->dev, size, + &l2ptr_dma, + GFP_KERNEL); + if (!l1_desc->l2ptr) return NULL; - l1ptr = cd_table->cdtab + idx * CTXDESC_L1_DESC_DWORDS; - arm_smmu_write_cd_l1_desc(l1ptr, l1_desc); + arm_smmu_write_cd_l1_desc(&cd_table->cdtab[idx], + l2ptr_dma); /* An invalid L1CD can be cached */ arm_smmu_sync_cd(master, ssid, false); } @@ -1480,7 +1473,8 @@ static void arm_smmu_free_cd_tables(struct arm_smmu_master *master) dma_free_coherent(smmu->dev, size, cd_table->l1_desc[i].l2ptr, - cd_table->l1_desc[i].l2ptr_dma); + arm_smmu_cd_l1_get_desc( + &cd_table->cdtab[i])); } kfree(cd_table->l1_desc); diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 33a8bee1b82e..8b5628771b96 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -619,7 +619,6 @@ struct arm_smmu_ctx_desc { struct arm_smmu_l1_ctx_desc { struct arm_smmu_cd *l2ptr; - dma_addr_t l2ptr_dma; }; struct arm_smmu_ctx_desc_cfg { From 7c567eb1e1d2a835140091ff8d4b73ac5454ba7b Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 6 Sep 2024 12:47:54 -0300 Subject: [PATCH 465/573] iommu/arm-smmu-v3: Add types for each level of the CD table As well as indexing helpers arm_smmu_cdtab_l1/2_idx(). Remove CTXDESC_L1_DESC_DWORDS and CTXDESC_CD_DWORDS replacing them all with type specific calculations. Tested-by: Nicolin Chen Reviewed-by: Nicolin Chen Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/7-v4-6416877274e1+1af-smmuv3_tidy_jgg@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 45 +++++++++++---------- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 23 +++++++++-- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 3c33ebffbe62..534a21414456 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -1217,17 +1217,18 @@ static void arm_smmu_sync_cd(struct arm_smmu_master *master, arm_smmu_cmdq_batch_submit(smmu, &cmds); } -static void arm_smmu_write_cd_l1_desc(__le64 *dst, dma_addr_t l2ptr_dma) +static void arm_smmu_write_cd_l1_desc(struct arm_smmu_cdtab_l1 *dst, + dma_addr_t l2ptr_dma) { u64 val = (l2ptr_dma & CTXDESC_L1_DESC_L2PTR_MASK) | CTXDESC_L1_DESC_V; /* The HW has 64 bit atomicity with stores to the L2 CD table */ - WRITE_ONCE(*dst, cpu_to_le64(val)); + WRITE_ONCE(dst->l2ptr, cpu_to_le64(val)); } -static dma_addr_t arm_smmu_cd_l1_get_desc(const __le64 *src) +static dma_addr_t arm_smmu_cd_l1_get_desc(const struct arm_smmu_cdtab_l1 *src) { - return le64_to_cpu(*src) & CTXDESC_L1_DESC_L2PTR_MASK; + return le64_to_cpu(src->l2ptr) & CTXDESC_L1_DESC_L2PTR_MASK; } struct arm_smmu_cd *arm_smmu_get_cd_ptr(struct arm_smmu_master *master, @@ -1240,13 +1241,12 @@ struct arm_smmu_cd *arm_smmu_get_cd_ptr(struct arm_smmu_master *master, return NULL; if (cd_table->s1fmt == STRTAB_STE_0_S1FMT_LINEAR) - return (struct arm_smmu_cd *)(cd_table->cdtab + - ssid * CTXDESC_CD_DWORDS); + return &((struct arm_smmu_cd *)cd_table->cdtab)[ssid]; - l1_desc = &cd_table->l1_desc[ssid / CTXDESC_L2_ENTRIES]; + l1_desc = &cd_table->l1_desc[arm_smmu_cdtab_l1_idx(ssid)]; if (!l1_desc->l2ptr) return NULL; - return &l1_desc->l2ptr[ssid % CTXDESC_L2_ENTRIES]; + return &l1_desc->l2ptr->cds[arm_smmu_cdtab_l2_idx(ssid)]; } static struct arm_smmu_cd *arm_smmu_alloc_cd_ptr(struct arm_smmu_master *master, @@ -1264,11 +1264,12 @@ static struct arm_smmu_cd *arm_smmu_alloc_cd_ptr(struct arm_smmu_master *master, } if (cd_table->s1fmt == STRTAB_STE_0_S1FMT_64K_L2) { - unsigned int idx = ssid / CTXDESC_L2_ENTRIES; + unsigned int idx = arm_smmu_cdtab_l1_idx(ssid); struct arm_smmu_l1_ctx_desc *l1_desc; l1_desc = &cd_table->l1_desc[idx]; if (!l1_desc->l2ptr) { + struct arm_smmu_cdtab_l1 *dst; dma_addr_t l2ptr_dma; size_t size; @@ -1279,8 +1280,8 @@ static struct arm_smmu_cd *arm_smmu_alloc_cd_ptr(struct arm_smmu_master *master, if (!l1_desc->l2ptr) return NULL; - arm_smmu_write_cd_l1_desc(&cd_table->cdtab[idx], - l2ptr_dma); + dst = &((struct arm_smmu_cdtab_l1 *)cd_table->cdtab)[idx]; + arm_smmu_write_cd_l1_desc(dst, l2ptr_dma); /* An invalid L1CD can be cached */ arm_smmu_sync_cd(master, ssid, false); } @@ -1424,7 +1425,7 @@ static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master) cd_table->s1fmt = STRTAB_STE_0_S1FMT_LINEAR; cd_table->num_l1_ents = max_contexts; - l1size = max_contexts * (CTXDESC_CD_DWORDS << 3); + l1size = max_contexts * sizeof(struct arm_smmu_cd); } else { cd_table->s1fmt = STRTAB_STE_0_S1FMT_64K_L2; cd_table->num_l1_ents = DIV_ROUND_UP(max_contexts, @@ -1436,7 +1437,7 @@ static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master) if (!cd_table->l1_desc) return -ENOMEM; - l1size = cd_table->num_l1_ents * (CTXDESC_L1_DESC_DWORDS << 3); + l1size = cd_table->num_l1_ents * sizeof(struct arm_smmu_cdtab_l1); } cd_table->cdtab = dma_alloc_coherent(smmu->dev, l1size, @@ -1460,27 +1461,29 @@ err_free_l1: static void arm_smmu_free_cd_tables(struct arm_smmu_master *master) { int i; - size_t size, l1size; + size_t l1size; struct arm_smmu_device *smmu = master->smmu; struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table; if (cd_table->l1_desc) { - size = CTXDESC_L2_ENTRIES * (CTXDESC_CD_DWORDS << 3); - for (i = 0; i < cd_table->num_l1_ents; i++) { + dma_addr_t dma_handle; + if (!cd_table->l1_desc[i].l2ptr) continue; - dma_free_coherent(smmu->dev, size, + dma_handle = arm_smmu_cd_l1_get_desc(&( + (struct arm_smmu_cdtab_l1 *)cd_table->cdtab)[i]); + dma_free_coherent(smmu->dev, + sizeof(*cd_table->l1_desc[i].l2ptr), cd_table->l1_desc[i].l2ptr, - arm_smmu_cd_l1_get_desc( - &cd_table->cdtab[i])); + dma_handle); } kfree(cd_table->l1_desc); - l1size = cd_table->num_l1_ents * (CTXDESC_L1_DESC_DWORDS << 3); + l1size = cd_table->num_l1_ents * sizeof(struct arm_smmu_cdtab_l1); } else { - l1size = cd_table->num_l1_ents * (CTXDESC_CD_DWORDS << 3); + l1size = cd_table->num_l1_ents * sizeof(struct arm_smmu_cd); } dma_free_coherent(smmu->dev, l1size, cd_table->cdtab, cd_table->cdtab_dma); diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 8b5628771b96..6fe68f8b3840 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -301,7 +301,6 @@ static inline u32 arm_smmu_strtab_l2_idx(u32 sid) */ #define CTXDESC_L2_ENTRIES 1024 -#define CTXDESC_L1_DESC_DWORDS 1 #define CTXDESC_L1_DESC_V (1UL << 0) #define CTXDESC_L1_DESC_L2PTR_MASK GENMASK_ULL(51, 12) @@ -311,6 +310,24 @@ struct arm_smmu_cd { __le64 data[CTXDESC_CD_DWORDS]; }; +struct arm_smmu_cdtab_l2 { + struct arm_smmu_cd cds[CTXDESC_L2_ENTRIES]; +}; + +struct arm_smmu_cdtab_l1 { + __le64 l2ptr; +}; + +static inline unsigned int arm_smmu_cdtab_l1_idx(unsigned int ssid) +{ + return ssid / CTXDESC_L2_ENTRIES; +} + +static inline unsigned int arm_smmu_cdtab_l2_idx(unsigned int ssid) +{ + return ssid % CTXDESC_L2_ENTRIES; +} + #define CTXDESC_CD_0_TCR_T0SZ GENMASK_ULL(5, 0) #define CTXDESC_CD_0_TCR_TG0 GENMASK_ULL(7, 6) #define CTXDESC_CD_0_TCR_IRGN0 GENMASK_ULL(9, 8) @@ -341,7 +358,7 @@ struct arm_smmu_cd { * When the SMMU only supports linear context descriptor tables, pick a * reasonable size limit (64kB). */ -#define CTXDESC_LINEAR_CDMAX ilog2(SZ_64K / (CTXDESC_CD_DWORDS << 3)) +#define CTXDESC_LINEAR_CDMAX ilog2(SZ_64K / sizeof(struct arm_smmu_cd)) /* Command queue */ #define CMDQ_ENT_SZ_SHIFT 4 @@ -618,7 +635,7 @@ struct arm_smmu_ctx_desc { }; struct arm_smmu_l1_ctx_desc { - struct arm_smmu_cd *l2ptr; + struct arm_smmu_cdtab_l2 *l2ptr; }; struct arm_smmu_ctx_desc_cfg { From e3b1be2e73dbe599f8b8886e120d206aa87e90f9 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Fri, 6 Sep 2024 12:47:55 -0300 Subject: [PATCH 466/573] iommu/arm-smmu-v3: Reorganize struct arm_smmu_ctx_desc_cfg The members here are being used for both the linear and the 2 level case, with the meaning of each item slightly different in the two cases. Split it into a clean union where both cases have their own struct with their own logical names and correct types. Adjust all the users to detect linear/2lvl and use the right sub structure and types consistently. Remove CTXDESC_CD_DWORDS by changing the last places to use sizeof(struct arm_smmu_cd). Tested-by: Nicolin Chen Reviewed-by: Nicolin Chen Signed-off-by: Jason Gunthorpe Link: https://lore.kernel.org/r/8-v4-6416877274e1+1af-smmuv3_tidy_jgg@nvidia.com Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 115 ++++++++++---------- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 24 ++-- 2 files changed, 72 insertions(+), 67 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 534a21414456..edc625ec261d 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -1234,19 +1234,19 @@ static dma_addr_t arm_smmu_cd_l1_get_desc(const struct arm_smmu_cdtab_l1 *src) struct arm_smmu_cd *arm_smmu_get_cd_ptr(struct arm_smmu_master *master, u32 ssid) { - struct arm_smmu_l1_ctx_desc *l1_desc; + struct arm_smmu_cdtab_l2 *l2; struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table; - if (!cd_table->cdtab) + if (!arm_smmu_cdtab_allocated(cd_table)) return NULL; if (cd_table->s1fmt == STRTAB_STE_0_S1FMT_LINEAR) - return &((struct arm_smmu_cd *)cd_table->cdtab)[ssid]; + return &cd_table->linear.table[ssid]; - l1_desc = &cd_table->l1_desc[arm_smmu_cdtab_l1_idx(ssid)]; - if (!l1_desc->l2ptr) + l2 = cd_table->l2.l2ptrs[arm_smmu_cdtab_l1_idx(ssid)]; + if (!l2) return NULL; - return &l1_desc->l2ptr->cds[arm_smmu_cdtab_l2_idx(ssid)]; + return &l2->cds[arm_smmu_cdtab_l2_idx(ssid)]; } static struct arm_smmu_cd *arm_smmu_alloc_cd_ptr(struct arm_smmu_master *master, @@ -1258,30 +1258,25 @@ static struct arm_smmu_cd *arm_smmu_alloc_cd_ptr(struct arm_smmu_master *master, might_sleep(); iommu_group_mutex_assert(master->dev); - if (!cd_table->cdtab) { + if (!arm_smmu_cdtab_allocated(cd_table)) { if (arm_smmu_alloc_cd_tables(master)) return NULL; } if (cd_table->s1fmt == STRTAB_STE_0_S1FMT_64K_L2) { unsigned int idx = arm_smmu_cdtab_l1_idx(ssid); - struct arm_smmu_l1_ctx_desc *l1_desc; + struct arm_smmu_cdtab_l2 **l2ptr = &cd_table->l2.l2ptrs[idx]; - l1_desc = &cd_table->l1_desc[idx]; - if (!l1_desc->l2ptr) { - struct arm_smmu_cdtab_l1 *dst; + if (!*l2ptr) { dma_addr_t l2ptr_dma; - size_t size; - size = CTXDESC_L2_ENTRIES * sizeof(struct arm_smmu_cd); - l1_desc->l2ptr = dma_alloc_coherent(smmu->dev, size, - &l2ptr_dma, - GFP_KERNEL); - if (!l1_desc->l2ptr) + *l2ptr = dma_alloc_coherent(smmu->dev, sizeof(**l2ptr), + &l2ptr_dma, GFP_KERNEL); + if (!*l2ptr) return NULL; - dst = &((struct arm_smmu_cdtab_l1 *)cd_table->cdtab)[idx]; - arm_smmu_write_cd_l1_desc(dst, l2ptr_dma); + arm_smmu_write_cd_l1_desc(&cd_table->l2.l1tab[idx], + l2ptr_dma); /* An invalid L1CD can be cached */ arm_smmu_sync_cd(master, ssid, false); } @@ -1401,7 +1396,7 @@ void arm_smmu_clear_cd(struct arm_smmu_master *master, ioasid_t ssid) struct arm_smmu_cd target = {}; struct arm_smmu_cd *cdptr; - if (!master->cd_table.cdtab) + if (!arm_smmu_cdtab_allocated(&master->cd_table)) return; cdptr = arm_smmu_get_cd_ptr(master, ssid); if (WARN_ON(!cdptr)) @@ -1423,70 +1418,70 @@ static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master) if (!(smmu->features & ARM_SMMU_FEAT_2_LVL_CDTAB) || max_contexts <= CTXDESC_L2_ENTRIES) { cd_table->s1fmt = STRTAB_STE_0_S1FMT_LINEAR; - cd_table->num_l1_ents = max_contexts; + cd_table->linear.num_ents = max_contexts; - l1size = max_contexts * sizeof(struct arm_smmu_cd); + l1size = max_contexts * sizeof(struct arm_smmu_cd), + cd_table->linear.table = dma_alloc_coherent(smmu->dev, l1size, + &cd_table->cdtab_dma, + GFP_KERNEL); + if (!cd_table->linear.table) + return -ENOMEM; } else { cd_table->s1fmt = STRTAB_STE_0_S1FMT_64K_L2; - cd_table->num_l1_ents = DIV_ROUND_UP(max_contexts, - CTXDESC_L2_ENTRIES); + cd_table->l2.num_l1_ents = + DIV_ROUND_UP(max_contexts, CTXDESC_L2_ENTRIES); - cd_table->l1_desc = kcalloc(cd_table->num_l1_ents, - sizeof(*cd_table->l1_desc), - GFP_KERNEL); - if (!cd_table->l1_desc) + cd_table->l2.l2ptrs = kcalloc(cd_table->l2.num_l1_ents, + sizeof(*cd_table->l2.l2ptrs), + GFP_KERNEL); + if (!cd_table->l2.l2ptrs) return -ENOMEM; - l1size = cd_table->num_l1_ents * sizeof(struct arm_smmu_cdtab_l1); + l1size = cd_table->l2.num_l1_ents * sizeof(struct arm_smmu_cdtab_l1); + cd_table->l2.l1tab = dma_alloc_coherent(smmu->dev, l1size, + &cd_table->cdtab_dma, + GFP_KERNEL); + if (!cd_table->l2.l2ptrs) { + ret = -ENOMEM; + goto err_free_l2ptrs; + } } - - cd_table->cdtab = dma_alloc_coherent(smmu->dev, l1size, - &cd_table->cdtab_dma, GFP_KERNEL); - if (!cd_table->cdtab) { - dev_warn(smmu->dev, "failed to allocate context descriptor\n"); - ret = -ENOMEM; - goto err_free_l1; - } - return 0; -err_free_l1: - if (cd_table->l1_desc) { - kfree(cd_table->l1_desc); - cd_table->l1_desc = NULL; - } +err_free_l2ptrs: + kfree(cd_table->l2.l2ptrs); + cd_table->l2.l2ptrs = NULL; return ret; } static void arm_smmu_free_cd_tables(struct arm_smmu_master *master) { int i; - size_t l1size; struct arm_smmu_device *smmu = master->smmu; struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table; - if (cd_table->l1_desc) { - for (i = 0; i < cd_table->num_l1_ents; i++) { - dma_addr_t dma_handle; - - if (!cd_table->l1_desc[i].l2ptr) + if (cd_table->s1fmt != STRTAB_STE_0_S1FMT_LINEAR) { + for (i = 0; i < cd_table->l2.num_l1_ents; i++) { + if (!cd_table->l2.l2ptrs[i]) continue; - dma_handle = arm_smmu_cd_l1_get_desc(&( - (struct arm_smmu_cdtab_l1 *)cd_table->cdtab)[i]); dma_free_coherent(smmu->dev, - sizeof(*cd_table->l1_desc[i].l2ptr), - cd_table->l1_desc[i].l2ptr, - dma_handle); + sizeof(*cd_table->l2.l2ptrs[i]), + cd_table->l2.l2ptrs[i], + arm_smmu_cd_l1_get_desc(&cd_table->l2.l1tab[i])); } - kfree(cd_table->l1_desc); + kfree(cd_table->l2.l2ptrs); - l1size = cd_table->num_l1_ents * sizeof(struct arm_smmu_cdtab_l1); + dma_free_coherent(smmu->dev, + cd_table->l2.num_l1_ents * + sizeof(struct arm_smmu_cdtab_l1), + cd_table->l2.l1tab, cd_table->cdtab_dma); } else { - l1size = cd_table->num_l1_ents * sizeof(struct arm_smmu_cd); + dma_free_coherent(smmu->dev, + cd_table->linear.num_ents * + sizeof(struct arm_smmu_cd), + cd_table->linear.table, cd_table->cdtab_dma); } - - dma_free_coherent(smmu->dev, l1size, cd_table->cdtab, cd_table->cdtab_dma); } /* Stream table manipulation functions */ @@ -3334,7 +3329,7 @@ static void arm_smmu_release_device(struct device *dev) arm_smmu_disable_pasid(master); arm_smmu_remove_master(master); - if (master->cd_table.cdtab) + if (arm_smmu_cdtab_allocated(&master->cd_table)) arm_smmu_free_cd_tables(master); kfree(master); } diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 6fe68f8b3840..1e9952ca989f 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -634,15 +634,19 @@ struct arm_smmu_ctx_desc { u16 asid; }; -struct arm_smmu_l1_ctx_desc { - struct arm_smmu_cdtab_l2 *l2ptr; -}; - struct arm_smmu_ctx_desc_cfg { - __le64 *cdtab; + union { + struct { + struct arm_smmu_cd *table; + unsigned int num_ents; + } linear; + struct { + struct arm_smmu_cdtab_l1 *l1tab; + struct arm_smmu_cdtab_l2 **l2ptrs; + unsigned int num_l1_ents; + } l2; + }; dma_addr_t cdtab_dma; - struct arm_smmu_l1_ctx_desc *l1_desc; - unsigned int num_l1_ents; unsigned int used_ssids; u8 in_ste; u8 s1fmt; @@ -650,6 +654,12 @@ struct arm_smmu_ctx_desc_cfg { u8 s1cdmax; }; +static inline bool +arm_smmu_cdtab_allocated(struct arm_smmu_ctx_desc_cfg *cfg) +{ + return cfg->linear.table || cfg->l2.l1tab; +} + /* True if the cd table has SSIDS > 0 in use. */ static inline bool arm_smmu_ssids_in_use(struct arm_smmu_ctx_desc_cfg *cd_table) { From 9028cdeb38e1f37d63cb3154799dd259b67e879e Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Thu, 5 Sep 2024 10:34:22 -0700 Subject: [PATCH 467/573] memcg: add charging of already allocated slab objects At the moment, the slab objects are charged to the memcg at the allocation time. However there are cases where slab objects are allocated at the time where the right target memcg to charge it to is not known. One such case is the network sockets for the incoming connection which are allocated in the softirq context. Couple hundred thousand connections are very normal on large loaded server and almost all of those sockets underlying those connections get allocated in the softirq context and thus not charged to any memcg. However later at the accept() time we know the right target memcg to charge. Let's add new API to charge already allocated objects, so we can have better accounting of the memory usage. To measure the performance impact of this change, tcp_crr is used from the neper [1] performance suite. Basically it is a network ping pong test with new connection for each ping pong. The server and the client are run inside 3 level of cgroup hierarchy using the following commands: Server: $ tcp_crr -6 Client: $ tcp_crr -6 -c -H ${server_ip} If the client and server run on different machines with 50 GBPS NIC, there is no visible impact of the change. For the same machine experiment with v6.11-rc5 as base. base (throughput) with-patch tcp_crr 14545 (+- 80) 14463 (+- 56) It seems like the performance impact is within the noise. Link: https://github.com/google/neper [1] Signed-off-by: Shakeel Butt Reviewed-by: Roman Gushchin Reviewed-by: Yosry Ahmed Acked-by: Paolo Abeni # net Signed-off-by: Vlastimil Babka --- include/linux/slab.h | 29 ++++++++++++++++++ mm/slab.h | 7 +++++ mm/slub.c | 53 +++++++++++++++++++++++++++++++++ net/ipv4/inet_connection_sock.c | 5 ++-- 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index eb2bf4629157..3be2a5ed4936 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -547,6 +547,35 @@ void *kmem_cache_alloc_lru_noprof(struct kmem_cache *s, struct list_lru *lru, gfp_t gfpflags) __assume_slab_alignment __malloc; #define kmem_cache_alloc_lru(...) alloc_hooks(kmem_cache_alloc_lru_noprof(__VA_ARGS__)) +/** + * kmem_cache_charge - memcg charge an already allocated slab memory + * @objp: address of the slab object to memcg charge + * @gfpflags: describe the allocation context + * + * kmem_cache_charge allows charging a slab object to the current memcg, + * primarily in cases where charging at allocation time might not be possible + * because the target memcg is not known (i.e. softirq context) + * + * The objp should be pointer returned by the slab allocator functions like + * kmalloc (with __GFP_ACCOUNT in flags) or kmem_cache_alloc. The memcg charge + * behavior can be controlled through gfpflags parameter, which affects how the + * necessary internal metadata can be allocated. Including __GFP_NOFAIL denotes + * that overcharging is requested instead of failure, but is not applied for the + * internal metadata allocation. + * + * There are several cases where it will return true even if the charging was + * not done: + * More specifically: + * + * 1. For !CONFIG_MEMCG or cgroup_disable=memory systems. + * 2. Already charged slab objects. + * 3. For slab objects from KMALLOC_NORMAL caches - allocated by kmalloc() + * without __GFP_ACCOUNT + * 4. Allocating internal metadata has failed + * + * Return: true if charge was successful otherwise false. + */ +bool kmem_cache_charge(void *objp, gfp_t gfpflags); void kmem_cache_free(struct kmem_cache *s, void *objp); kmem_buckets *kmem_buckets_create(const char *name, slab_flags_t flags, diff --git a/mm/slab.h b/mm/slab.h index dcdb56b8e7f5..9f907e930609 100644 --- a/mm/slab.h +++ b/mm/slab.h @@ -443,6 +443,13 @@ static inline bool is_kmalloc_cache(struct kmem_cache *s) return (s->flags & SLAB_KMALLOC); } +static inline bool is_kmalloc_normal(struct kmem_cache *s) +{ + if (!is_kmalloc_cache(s)) + return false; + return !(s->flags & (SLAB_CACHE_DMA|SLAB_ACCOUNT|SLAB_RECLAIM_ACCOUNT)); +} + /* Legal flag mask for kmem_cache_create(), for various configurations */ #define SLAB_CORE_FLAGS (SLAB_HWCACHE_ALIGN | SLAB_CACHE_DMA | \ SLAB_CACHE_DMA32 | SLAB_PANIC | \ diff --git a/mm/slub.c b/mm/slub.c index 95977f25a760..aa512de974e7 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -2185,6 +2185,45 @@ void memcg_slab_free_hook(struct kmem_cache *s, struct slab *slab, void **p, __memcg_slab_free_hook(s, slab, p, objects, obj_exts); } + +static __fastpath_inline +bool memcg_slab_post_charge(void *p, gfp_t flags) +{ + struct slabobj_ext *slab_exts; + struct kmem_cache *s; + struct folio *folio; + struct slab *slab; + unsigned long off; + + folio = virt_to_folio(p); + if (!folio_test_slab(folio)) { + return folio_memcg_kmem(folio) || + (__memcg_kmem_charge_page(folio_page(folio, 0), flags, + folio_order(folio)) == 0); + } + + slab = folio_slab(folio); + s = slab->slab_cache; + + /* + * Ignore KMALLOC_NORMAL cache to avoid possible circular dependency + * of slab_obj_exts being allocated from the same slab and thus the slab + * becoming effectively unfreeable. + */ + if (is_kmalloc_normal(s)) + return true; + + /* Ignore already charged objects. */ + slab_exts = slab_obj_exts(slab); + if (slab_exts) { + off = obj_to_index(s, slab, p); + if (unlikely(slab_exts[off].objcg)) + return true; + } + + return __memcg_slab_post_alloc_hook(s, NULL, flags, 1, &p); +} + #else /* CONFIG_MEMCG */ static inline bool memcg_slab_post_alloc_hook(struct kmem_cache *s, struct list_lru *lru, @@ -2198,6 +2237,11 @@ static inline void memcg_slab_free_hook(struct kmem_cache *s, struct slab *slab, void **p, int objects) { } + +static inline bool memcg_slab_post_charge(void *p, gfp_t flags) +{ + return true; +} #endif /* CONFIG_MEMCG */ #ifdef CONFIG_SLUB_RCU_DEBUG @@ -4105,6 +4149,15 @@ void *kmem_cache_alloc_lru_noprof(struct kmem_cache *s, struct list_lru *lru, } EXPORT_SYMBOL(kmem_cache_alloc_lru_noprof); +bool kmem_cache_charge(void *objp, gfp_t gfpflags) +{ + if (!memcg_kmem_online()) + return true; + + return memcg_slab_post_charge(objp, gfpflags); +} +EXPORT_SYMBOL(kmem_cache_charge); + /** * kmem_cache_alloc_node - Allocate an object on the specified node * @s: The cache to allocate from. diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index 64d07b842e73..e25381bf32d0 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -714,6 +714,7 @@ struct sock *inet_csk_accept(struct sock *sk, struct proto_accept_arg *arg) out: release_sock(sk); if (newsk && mem_cgroup_sockets_enabled) { + gfp_t gfp = GFP_KERNEL | __GFP_NOFAIL; int amt = 0; /* atomically get the memory usage, set and charge the @@ -731,8 +732,8 @@ out: } if (amt) - mem_cgroup_charge_skmem(newsk->sk_memcg, amt, - GFP_KERNEL | __GFP_NOFAIL); + mem_cgroup_charge_skmem(newsk->sk_memcg, amt, gfp); + kmem_cache_charge(newsk, gfp); release_sock(newsk); } From 53d3d210864e532ee5d8ae48f66168c12dd8f530 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:44 +0200 Subject: [PATCH 468/573] slab: s/__kmem_cache_create/do_kmem_cache_create/g Free up reusing the double-underscore variant for follow-up patches. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- mm/slab.h | 2 +- mm/slab_common.c | 4 ++-- mm/slub.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mm/slab.h b/mm/slab.h index a6051385186e..684bb48c4f39 100644 --- a/mm/slab.h +++ b/mm/slab.h @@ -424,7 +424,7 @@ kmalloc_slab(size_t size, kmem_buckets *b, gfp_t flags, unsigned long caller) gfp_t kmalloc_fix_flags(gfp_t flags); /* Functions provided by the slab allocators */ -int __kmem_cache_create(struct kmem_cache *, slab_flags_t flags); +int do_kmem_cache_create(struct kmem_cache *, slab_flags_t flags); void __init kmem_cache_init(void); extern void create_boot_cache(struct kmem_cache *, const char *name, diff --git a/mm/slab_common.c b/mm/slab_common.c index 95db3702f8d6..91e0e36e4379 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -234,7 +234,7 @@ static struct kmem_cache *create_cache(const char *name, s->useroffset = useroffset; s->usersize = usersize; #endif - err = __kmem_cache_create(s, flags); + err = do_kmem_cache_create(s, flags); if (err) goto out_free_cache; @@ -778,7 +778,7 @@ void __init create_boot_cache(struct kmem_cache *s, const char *name, s->usersize = usersize; #endif - err = __kmem_cache_create(s, flags); + err = do_kmem_cache_create(s, flags); if (err) panic("Creation of kmalloc slab %s size=%u failed. Reason %d\n", diff --git a/mm/slub.c b/mm/slub.c index 9aa5da1e8e27..23d9d783ff26 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -5902,7 +5902,7 @@ __kmem_cache_alias(const char *name, unsigned int size, unsigned int align, return s; } -int __kmem_cache_create(struct kmem_cache *s, slab_flags_t flags) +int do_kmem_cache_create(struct kmem_cache *s, slab_flags_t flags) { int err; From 879fb3c274c12cb0892718e1e54f85fc406e3c7b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:45 +0200 Subject: [PATCH 469/573] slab: add struct kmem_cache_args Currently we have multiple kmem_cache_create*() variants that take up to seven separate parameters with one of the functions having to grow an eigth parameter in the future to handle both usercopy and a custom freelist pointer. Add a struct kmem_cache_args structure and move less common parameters into it. Core parameters such as name, object size, and flags continue to be passed separately. Add a new function __kmem_cache_create_args() that takes a struct kmem_cache_args pointer and port do_kmem_cache_create_usercopy() over to it. In follow-up patches we will port the other kmem_cache_create*() variants over to it as well. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- include/linux/slab.h | 22 +++++++++++++++ mm/slab_common.c | 65 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index 5b2da2cf31a8..2b8eeca7fd2c 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -240,6 +240,28 @@ struct mem_cgroup; */ bool slab_is_available(void); +/** + * struct kmem_cache_args - Less common arguments for kmem_cache_create() + * @align: The required alignment for the objects. + * @useroffset: Usercopy region offset + * @usersize: Usercopy region size + * @freeptr_offset: Custom offset for the free pointer in RCU caches + * @use_freeptr_offset: Whether a @freeptr_offset is used + * @ctor: A constructor for the objects. + */ +struct kmem_cache_args { + unsigned int align; + unsigned int useroffset; + unsigned int usersize; + unsigned int freeptr_offset; + bool use_freeptr_offset; + void (*ctor)(void *); +}; + +struct kmem_cache *__kmem_cache_create_args(const char *name, + unsigned int object_size, + struct kmem_cache_args *args, + slab_flags_t flags); struct kmem_cache *kmem_cache_create(const char *name, unsigned int size, unsigned int align, slab_flags_t flags, void (*ctor)(void *)); diff --git a/mm/slab_common.c b/mm/slab_common.c index 91e0e36e4379..0f13c045b8d1 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -248,14 +248,24 @@ out: return ERR_PTR(err); } -static struct kmem_cache * -do_kmem_cache_create_usercopy(const char *name, - unsigned int size, unsigned int freeptr_offset, - unsigned int align, slab_flags_t flags, - unsigned int useroffset, unsigned int usersize, - void (*ctor)(void *)) +/** + * __kmem_cache_create_args - Create a kmem cache + * @name: A string which is used in /proc/slabinfo to identify this cache. + * @object_size: The size of objects to be created in this cache. + * @args: Arguments for the cache creation (see struct kmem_cache_args). + * @flags: See %SLAB_* flags for an explanation of individual @flags. + * + * Cannot be called within a interrupt, but can be interrupted. + * + * Return: a pointer to the cache on success, NULL on failure. + */ +struct kmem_cache *__kmem_cache_create_args(const char *name, + unsigned int object_size, + struct kmem_cache_args *args, + slab_flags_t flags) { struct kmem_cache *s = NULL; + unsigned int freeptr_offset = UINT_MAX; const char *cache_name; int err; @@ -275,7 +285,7 @@ do_kmem_cache_create_usercopy(const char *name, mutex_lock(&slab_mutex); - err = kmem_cache_sanity_check(name, size); + err = kmem_cache_sanity_check(name, object_size); if (err) { goto out_unlock; } @@ -296,12 +306,14 @@ do_kmem_cache_create_usercopy(const char *name, /* Fail closed on bad usersize of useroffset values. */ if (!IS_ENABLED(CONFIG_HARDENED_USERCOPY) || - WARN_ON(!usersize && useroffset) || - WARN_ON(size < usersize || size - usersize < useroffset)) - usersize = useroffset = 0; + WARN_ON(!args->usersize && args->useroffset) || + WARN_ON(object_size < args->usersize || + object_size - args->usersize < args->useroffset)) + args->usersize = args->useroffset = 0; - if (!usersize) - s = __kmem_cache_alias(name, size, align, flags, ctor); + if (!args->usersize) + s = __kmem_cache_alias(name, object_size, args->align, flags, + args->ctor); if (s) goto out_unlock; @@ -311,9 +323,11 @@ do_kmem_cache_create_usercopy(const char *name, goto out_unlock; } - s = create_cache(cache_name, size, freeptr_offset, - calculate_alignment(flags, align, size), - flags, useroffset, usersize, ctor); + if (args->use_freeptr_offset) + freeptr_offset = args->freeptr_offset; + s = create_cache(cache_name, object_size, freeptr_offset, + calculate_alignment(flags, args->align, object_size), + flags, args->useroffset, args->usersize, args->ctor); if (IS_ERR(s)) { err = PTR_ERR(s); kfree_const(cache_name); @@ -335,6 +349,27 @@ out_unlock: } return s; } +EXPORT_SYMBOL(__kmem_cache_create_args); + +static struct kmem_cache * +do_kmem_cache_create_usercopy(const char *name, + unsigned int size, unsigned int freeptr_offset, + unsigned int align, slab_flags_t flags, + unsigned int useroffset, unsigned int usersize, + void (*ctor)(void *)) +{ + struct kmem_cache_args kmem_args = { + .align = align, + .use_freeptr_offset = freeptr_offset != UINT_MAX, + .freeptr_offset = freeptr_offset, + .useroffset = useroffset, + .usersize = usersize, + .ctor = ctor, + }; + + return __kmem_cache_create_args(name, size, &kmem_args, flags); +} + /** * kmem_cache_create_usercopy - Create a cache with a region suitable From f6cd98c9407f8b8118464c633b27765e751e2750 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:46 +0200 Subject: [PATCH 470/573] slab: port kmem_cache_create() to struct kmem_cache_args Port kmem_cache_create() to struct kmem_cache_args. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- mm/slab_common.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index 0f13c045b8d1..ac0832dac01e 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -439,8 +439,12 @@ struct kmem_cache * kmem_cache_create(const char *name, unsigned int size, unsigned int align, slab_flags_t flags, void (*ctor)(void *)) { - return do_kmem_cache_create_usercopy(name, size, UINT_MAX, align, flags, - 0, 0, ctor); + struct kmem_cache_args kmem_args = { + .align = align, + .ctor = ctor, + }; + + return __kmem_cache_create_args(name, size, &kmem_args, flags); } EXPORT_SYMBOL(kmem_cache_create); From 9816c3c4e778b2477fda1966bf7047a2d9772d14 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:47 +0200 Subject: [PATCH 471/573] slab: port kmem_cache_create_rcu() to struct kmem_cache_args Port kmem_cache_create_rcu() to struct kmem_cache_args. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- mm/slab_common.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index ac0832dac01e..da62ed30f95d 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -481,9 +481,13 @@ struct kmem_cache *kmem_cache_create_rcu(const char *name, unsigned int size, unsigned int freeptr_offset, slab_flags_t flags) { - return do_kmem_cache_create_usercopy(name, size, freeptr_offset, 0, - flags | SLAB_TYPESAFE_BY_RCU, 0, 0, - NULL); + struct kmem_cache_args kmem_args = { + .freeptr_offset = freeptr_offset, + .use_freeptr_offset = true, + }; + + return __kmem_cache_create_args(name, size, &kmem_args, + flags | SLAB_TYPESAFE_BY_RCU); } EXPORT_SYMBOL(kmem_cache_create_rcu); From 1d3d7645d789c9ce8fec48b16c0040b4a3dc6604 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:48 +0200 Subject: [PATCH 472/573] slab: port kmem_cache_create_usercopy() to struct kmem_cache_args Port kmem_cache_create_usercopy() to struct kmem_cache_args and remove the now unused do_kmem_cache_create_usercopy() helper. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- mm/slab_common.c | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index da62ed30f95d..16c36a946135 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -351,26 +351,6 @@ out_unlock: } EXPORT_SYMBOL(__kmem_cache_create_args); -static struct kmem_cache * -do_kmem_cache_create_usercopy(const char *name, - unsigned int size, unsigned int freeptr_offset, - unsigned int align, slab_flags_t flags, - unsigned int useroffset, unsigned int usersize, - void (*ctor)(void *)) -{ - struct kmem_cache_args kmem_args = { - .align = align, - .use_freeptr_offset = freeptr_offset != UINT_MAX, - .freeptr_offset = freeptr_offset, - .useroffset = useroffset, - .usersize = usersize, - .ctor = ctor, - }; - - return __kmem_cache_create_args(name, size, &kmem_args, flags); -} - - /** * kmem_cache_create_usercopy - Create a cache with a region suitable * for copying to userspace @@ -405,8 +385,14 @@ kmem_cache_create_usercopy(const char *name, unsigned int size, unsigned int useroffset, unsigned int usersize, void (*ctor)(void *)) { - return do_kmem_cache_create_usercopy(name, size, UINT_MAX, align, flags, - useroffset, usersize, ctor); + struct kmem_cache_args kmem_args = { + .align = align, + .ctor = ctor, + .useroffset = useroffset, + .usersize = usersize, + }; + + return __kmem_cache_create_args(name, size, &kmem_args, flags); } EXPORT_SYMBOL(kmem_cache_create_usercopy); From 34410a906080e7f669330c45c8e38215fe09f481 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:49 +0200 Subject: [PATCH 473/573] slab: pass struct kmem_cache_args to create_cache() Pass struct kmem_cache_args to create_cache() so that we can later simplify further helpers. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- mm/slab_common.c | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index 16c36a946135..9baa61c9c670 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -202,22 +202,22 @@ struct kmem_cache *find_mergeable(unsigned int size, unsigned int align, } static struct kmem_cache *create_cache(const char *name, - unsigned int object_size, unsigned int freeptr_offset, - unsigned int align, slab_flags_t flags, - unsigned int useroffset, unsigned int usersize, - void (*ctor)(void *)) + unsigned int object_size, + struct kmem_cache_args *args, + slab_flags_t flags) { struct kmem_cache *s; int err; - if (WARN_ON(useroffset + usersize > object_size)) - useroffset = usersize = 0; + if (WARN_ON(args->useroffset + args->usersize > object_size)) + args->useroffset = args->usersize = 0; /* If a custom freelist pointer is requested make sure it's sane. */ err = -EINVAL; - if (freeptr_offset != UINT_MAX && - (freeptr_offset >= object_size || !(flags & SLAB_TYPESAFE_BY_RCU) || - !IS_ALIGNED(freeptr_offset, sizeof(freeptr_t)))) + if (args->use_freeptr_offset && + (args->freeptr_offset >= object_size || + !(flags & SLAB_TYPESAFE_BY_RCU) || + !IS_ALIGNED(args->freeptr_offset, sizeof(freeptr_t)))) goto out; err = -ENOMEM; @@ -227,12 +227,15 @@ static struct kmem_cache *create_cache(const char *name, s->name = name; s->size = s->object_size = object_size; - s->rcu_freeptr_offset = freeptr_offset; - s->align = align; - s->ctor = ctor; + if (args->use_freeptr_offset) + s->rcu_freeptr_offset = args->freeptr_offset; + else + s->rcu_freeptr_offset = UINT_MAX; + s->align = args->align; + s->ctor = args->ctor; #ifdef CONFIG_HARDENED_USERCOPY - s->useroffset = useroffset; - s->usersize = usersize; + s->useroffset = args->useroffset; + s->usersize = args->usersize; #endif err = do_kmem_cache_create(s, flags); if (err) @@ -265,7 +268,6 @@ struct kmem_cache *__kmem_cache_create_args(const char *name, slab_flags_t flags) { struct kmem_cache *s = NULL; - unsigned int freeptr_offset = UINT_MAX; const char *cache_name; int err; @@ -323,11 +325,8 @@ struct kmem_cache *__kmem_cache_create_args(const char *name, goto out_unlock; } - if (args->use_freeptr_offset) - freeptr_offset = args->freeptr_offset; - s = create_cache(cache_name, object_size, freeptr_offset, - calculate_alignment(flags, args->align, object_size), - flags, args->useroffset, args->usersize, args->ctor); + args->align = calculate_alignment(flags, args->align, object_size); + s = create_cache(cache_name, object_size, args, flags); if (IS_ERR(s)) { err = PTR_ERR(s); kfree_const(cache_name); From fc0eac57d08cad87b478a3be944899c81c950d43 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:50 +0200 Subject: [PATCH 474/573] slab: pull kmem_cache_open() into do_kmem_cache_create() do_kmem_cache_create() is the only caller and we're going to pass down struct kmem_cache_args in a follow-up patch. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- mm/slub.c | 132 +++++++++++++++++++++++++----------------------------- 1 file changed, 62 insertions(+), 70 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 23d9d783ff26..30f4ca6335c7 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -5290,65 +5290,6 @@ static int calculate_sizes(struct kmem_cache *s) return !!oo_objects(s->oo); } -static int kmem_cache_open(struct kmem_cache *s, slab_flags_t flags) -{ - s->flags = kmem_cache_flags(flags, s->name); -#ifdef CONFIG_SLAB_FREELIST_HARDENED - s->random = get_random_long(); -#endif - - if (!calculate_sizes(s)) - goto error; - if (disable_higher_order_debug) { - /* - * Disable debugging flags that store metadata if the min slab - * order increased. - */ - if (get_order(s->size) > get_order(s->object_size)) { - s->flags &= ~DEBUG_METADATA_FLAGS; - s->offset = 0; - if (!calculate_sizes(s)) - goto error; - } - } - -#ifdef system_has_freelist_aba - if (system_has_freelist_aba() && !(s->flags & SLAB_NO_CMPXCHG)) { - /* Enable fast mode */ - s->flags |= __CMPXCHG_DOUBLE; - } -#endif - - /* - * The larger the object size is, the more slabs we want on the partial - * list to avoid pounding the page allocator excessively. - */ - s->min_partial = min_t(unsigned long, MAX_PARTIAL, ilog2(s->size) / 2); - s->min_partial = max_t(unsigned long, MIN_PARTIAL, s->min_partial); - - set_cpu_partial(s); - -#ifdef CONFIG_NUMA - s->remote_node_defrag_ratio = 1000; -#endif - - /* Initialize the pre-computed randomized freelist if slab is up */ - if (slab_state >= UP) { - if (init_cache_random_seq(s)) - goto error; - } - - if (!init_kmem_cache_nodes(s)) - goto error; - - if (alloc_kmem_cache_cpus(s)) - return 0; - -error: - __kmem_cache_release(s); - return -EINVAL; -} - static void list_slab_objects(struct kmem_cache *s, struct slab *slab, const char *text) { @@ -5904,26 +5845,77 @@ __kmem_cache_alias(const char *name, unsigned int size, unsigned int align, int do_kmem_cache_create(struct kmem_cache *s, slab_flags_t flags) { - int err; + int err = -EINVAL; - err = kmem_cache_open(s, flags); - if (err) - return err; + s->flags = kmem_cache_flags(flags, s->name); +#ifdef CONFIG_SLAB_FREELIST_HARDENED + s->random = get_random_long(); +#endif + + if (!calculate_sizes(s)) + goto out; + if (disable_higher_order_debug) { + /* + * Disable debugging flags that store metadata if the min slab + * order increased. + */ + if (get_order(s->size) > get_order(s->object_size)) { + s->flags &= ~DEBUG_METADATA_FLAGS; + s->offset = 0; + if (!calculate_sizes(s)) + goto out; + } + } + +#ifdef system_has_freelist_aba + if (system_has_freelist_aba() && !(s->flags & SLAB_NO_CMPXCHG)) { + /* Enable fast mode */ + s->flags |= __CMPXCHG_DOUBLE; + } +#endif + + /* + * The larger the object size is, the more slabs we want on the partial + * list to avoid pounding the page allocator excessively. + */ + s->min_partial = min_t(unsigned long, MAX_PARTIAL, ilog2(s->size) / 2); + s->min_partial = max_t(unsigned long, MIN_PARTIAL, s->min_partial); + + set_cpu_partial(s); + +#ifdef CONFIG_NUMA + s->remote_node_defrag_ratio = 1000; +#endif + + /* Initialize the pre-computed randomized freelist if slab is up */ + if (slab_state >= UP) { + if (init_cache_random_seq(s)) + goto out; + } + + if (!init_kmem_cache_nodes(s)) + goto out; + + if (!alloc_kmem_cache_cpus(s)) + goto out; /* Mutex is not taken during early boot */ - if (slab_state <= UP) - return 0; + if (slab_state <= UP) { + err = 0; + goto out; + } err = sysfs_slab_add(s); - if (err) { - __kmem_cache_release(s); - return err; - } + if (err) + goto out; if (s->flags & SLAB_STORE_USER) debugfs_slab_add(s); - return 0; +out: + if (err) + __kmem_cache_release(s); + return err; } #ifdef SLAB_SUPPORTS_SYSFS From 3dbe2bad5785e4a9f62d99186e7d31410a8f1e2d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:51 +0200 Subject: [PATCH 475/573] slab: pass struct kmem_cache_args to do_kmem_cache_create() and initialize most things in do_kmem_cache_create(). In a follow-up patch we'll remove rcu_freeptr_offset from struct kmem_cache. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- mm/slab.h | 4 +++- mm/slab_common.c | 27 ++++++--------------------- mm/slub.c | 17 ++++++++++++++++- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/mm/slab.h b/mm/slab.h index 684bb48c4f39..c7a4e0fc3cf1 100644 --- a/mm/slab.h +++ b/mm/slab.h @@ -424,7 +424,9 @@ kmalloc_slab(size_t size, kmem_buckets *b, gfp_t flags, unsigned long caller) gfp_t kmalloc_fix_flags(gfp_t flags); /* Functions provided by the slab allocators */ -int do_kmem_cache_create(struct kmem_cache *, slab_flags_t flags); +int do_kmem_cache_create(struct kmem_cache *s, const char *name, + unsigned int size, struct kmem_cache_args *args, + slab_flags_t flags); void __init kmem_cache_init(void); extern void create_boot_cache(struct kmem_cache *, const char *name, diff --git a/mm/slab_common.c b/mm/slab_common.c index 9baa61c9c670..19ae3dd6e36f 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -224,20 +224,7 @@ static struct kmem_cache *create_cache(const char *name, s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL); if (!s) goto out; - - s->name = name; - s->size = s->object_size = object_size; - if (args->use_freeptr_offset) - s->rcu_freeptr_offset = args->freeptr_offset; - else - s->rcu_freeptr_offset = UINT_MAX; - s->align = args->align; - s->ctor = args->ctor; -#ifdef CONFIG_HARDENED_USERCOPY - s->useroffset = args->useroffset; - s->usersize = args->usersize; -#endif - err = do_kmem_cache_create(s, flags); + err = do_kmem_cache_create(s, name, object_size, args, flags); if (err) goto out_free_cache; @@ -788,9 +775,7 @@ void __init create_boot_cache(struct kmem_cache *s, const char *name, { int err; unsigned int align = ARCH_KMALLOC_MINALIGN; - - s->name = name; - s->size = s->object_size = size; + struct kmem_cache_args kmem_args = {}; /* * kmalloc caches guarantee alignment of at least the largest @@ -799,14 +784,14 @@ void __init create_boot_cache(struct kmem_cache *s, const char *name, */ if (flags & SLAB_KMALLOC) align = max(align, 1U << (ffs(size) - 1)); - s->align = calculate_alignment(flags, align, size); + kmem_args.align = calculate_alignment(flags, align, size); #ifdef CONFIG_HARDENED_USERCOPY - s->useroffset = useroffset; - s->usersize = usersize; + kmem_args.useroffset = useroffset; + kmem_args.usersize = usersize; #endif - err = do_kmem_cache_create(s, flags); + err = do_kmem_cache_create(s, name, size, &kmem_args, flags); if (err) panic("Creation of kmalloc slab %s size=%u failed. Reason %d\n", diff --git a/mm/slub.c b/mm/slub.c index 30f4ca6335c7..4719b60215b8 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -5843,14 +5843,29 @@ __kmem_cache_alias(const char *name, unsigned int size, unsigned int align, return s; } -int do_kmem_cache_create(struct kmem_cache *s, slab_flags_t flags) +int do_kmem_cache_create(struct kmem_cache *s, const char *name, + unsigned int size, struct kmem_cache_args *args, + slab_flags_t flags) { int err = -EINVAL; + s->name = name; + s->size = s->object_size = size; + s->flags = kmem_cache_flags(flags, s->name); #ifdef CONFIG_SLAB_FREELIST_HARDENED s->random = get_random_long(); #endif + if (args->use_freeptr_offset) + s->rcu_freeptr_offset = args->freeptr_offset; + else + s->rcu_freeptr_offset = UINT_MAX; + s->align = args->align; + s->ctor = args->ctor; +#ifdef CONFIG_HARDENED_USERCOPY + s->useroffset = args->useroffset; + s->usersize = args->usersize; +#endif if (!calculate_sizes(s)) goto out; From dacf472bcdfa4f3b08cd2c1beef034e3e5ab4bd0 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:52 +0200 Subject: [PATCH 476/573] slab: remove rcu_freeptr_offset from struct kmem_cache Pass down struct kmem_cache_args to calculate_sizes() so we can use args->{use}_freeptr_offset directly. This allows us to remove ->rcu_freeptr_offset from struct kmem_cache. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- mm/slab.h | 2 -- mm/slub.c | 25 +++++++------------------ 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/mm/slab.h b/mm/slab.h index c7a4e0fc3cf1..36ac38e21fcb 100644 --- a/mm/slab.h +++ b/mm/slab.h @@ -261,8 +261,6 @@ struct kmem_cache { unsigned int object_size; /* Object size without metadata */ struct reciprocal_value reciprocal_size; unsigned int offset; /* Free pointer offset */ - /* Specific free pointer requested (if not UINT_MAX) */ - unsigned int rcu_freeptr_offset; #ifdef CONFIG_SLUB_CPU_PARTIAL /* Number of per cpu partial objects to keep around */ unsigned int cpu_partial; diff --git a/mm/slub.c b/mm/slub.c index 4719b60215b8..a23c7036cd61 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -3916,8 +3916,7 @@ static void *__slab_alloc_node(struct kmem_cache *s, * If the object has been wiped upon free, make sure it's fully initialized by * zeroing out freelist pointer. * - * Note that we also wipe custom freelist pointers specified via - * s->rcu_freeptr_offset. + * Note that we also wipe custom freelist pointers. */ static __always_inline void maybe_wipe_obj_freeptr(struct kmem_cache *s, void *obj) @@ -5141,17 +5140,11 @@ static void set_cpu_partial(struct kmem_cache *s) #endif } -/* Was a valid freeptr offset requested? */ -static inline bool has_freeptr_offset(const struct kmem_cache *s) -{ - return s->rcu_freeptr_offset != UINT_MAX; -} - /* * calculate_sizes() determines the order and the distribution of data within * a slab object. */ -static int calculate_sizes(struct kmem_cache *s) +static int calculate_sizes(struct kmem_cache_args *args, struct kmem_cache *s) { slab_flags_t flags = s->flags; unsigned int size = s->object_size; @@ -5192,7 +5185,7 @@ static int calculate_sizes(struct kmem_cache *s) */ s->inuse = size; - if (((flags & SLAB_TYPESAFE_BY_RCU) && !has_freeptr_offset(s)) || + if (((flags & SLAB_TYPESAFE_BY_RCU) && !args->use_freeptr_offset) || (flags & SLAB_POISON) || s->ctor || ((flags & SLAB_RED_ZONE) && (s->object_size < sizeof(void *) || slub_debug_orig_size(s)))) { @@ -5214,8 +5207,8 @@ static int calculate_sizes(struct kmem_cache *s) */ s->offset = size; size += sizeof(void *); - } else if ((flags & SLAB_TYPESAFE_BY_RCU) && has_freeptr_offset(s)) { - s->offset = s->rcu_freeptr_offset; + } else if ((flags & SLAB_TYPESAFE_BY_RCU) && args->use_freeptr_offset) { + s->offset = args->freeptr_offset; } else { /* * Store freelist pointer near middle of object to keep @@ -5856,10 +5849,6 @@ int do_kmem_cache_create(struct kmem_cache *s, const char *name, #ifdef CONFIG_SLAB_FREELIST_HARDENED s->random = get_random_long(); #endif - if (args->use_freeptr_offset) - s->rcu_freeptr_offset = args->freeptr_offset; - else - s->rcu_freeptr_offset = UINT_MAX; s->align = args->align; s->ctor = args->ctor; #ifdef CONFIG_HARDENED_USERCOPY @@ -5867,7 +5856,7 @@ int do_kmem_cache_create(struct kmem_cache *s, const char *name, s->usersize = args->usersize; #endif - if (!calculate_sizes(s)) + if (!calculate_sizes(args, s)) goto out; if (disable_higher_order_debug) { /* @@ -5877,7 +5866,7 @@ int do_kmem_cache_create(struct kmem_cache *s, const char *name, if (get_order(s->size) > get_order(s->object_size)) { s->flags &= ~DEBUG_METADATA_FLAGS; s->offset = 0; - if (!calculate_sizes(s)) + if (!calculate_sizes(args, s)) goto out; } } From 052d67b46bcd91b6785e8e6e047241814f6142a3 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:53 +0200 Subject: [PATCH 477/573] slab: port KMEM_CACHE() to struct kmem_cache_args Make KMEM_CACHE() use struct kmem_cache_args. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- include/linux/slab.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index 2b8eeca7fd2c..1f38d94387cc 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -284,9 +284,11 @@ int kmem_cache_shrink(struct kmem_cache *s); * f.e. add ____cacheline_aligned_in_smp to the struct declaration * then the objects will be properly aligned in SMP configurations. */ -#define KMEM_CACHE(__struct, __flags) \ - kmem_cache_create(#__struct, sizeof(struct __struct), \ - __alignof__(struct __struct), (__flags), NULL) +#define KMEM_CACHE(__struct, __flags) \ + __kmem_cache_create_args(#__struct, sizeof(struct __struct), \ + &(struct kmem_cache_args) { \ + .align = __alignof__(struct __struct), \ + }, (__flags)) /* * To whitelist a single field for copying to/from usercopy, use this From 199cd13a745eb44fb4828bca373155293cdcfa5c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:54 +0200 Subject: [PATCH 478/573] slab: port KMEM_CACHE_USERCOPY() to struct kmem_cache_args Make KMEM_CACHE_USERCOPY() use struct kmem_cache_args. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- include/linux/slab.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index 1f38d94387cc..7e15a3a3edb1 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -294,12 +294,13 @@ int kmem_cache_shrink(struct kmem_cache *s); * To whitelist a single field for copying to/from usercopy, use this * macro instead for KMEM_CACHE() above. */ -#define KMEM_CACHE_USERCOPY(__struct, __flags, __field) \ - kmem_cache_create_usercopy(#__struct, \ - sizeof(struct __struct), \ - __alignof__(struct __struct), (__flags), \ - offsetof(struct __struct, __field), \ - sizeof_field(struct __struct, __field), NULL) +#define KMEM_CACHE_USERCOPY(__struct, __flags, __field) \ + __kmem_cache_create_args(#__struct, sizeof(struct __struct), \ + &(struct kmem_cache_args) { \ + .align = __alignof__(struct __struct), \ + .useroffset = offsetof(struct __struct, __field), \ + .usersize = sizeof_field(struct __struct, __field), \ + }, (__flags)) /* * Common kmalloc functions provided by all allocators From b2e7456b5c25c41eda7a8a15f7ccaa4e7579949f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:55 +0200 Subject: [PATCH 479/573] slab: create kmem_cache_create() compatibility layer Use _Generic() to create a compatibility layer that type switches on the third argument to either call __kmem_cache_create() or __kmem_cache_create_args(). If NULL is passed for the struct kmem_cache_args argument use default args making porting for callers that don't care about additional arguments easy. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Signed-off-by: Christian Brauner Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- include/linux/slab.h | 29 ++++++++++++++++++++++++++--- mm/slab_common.c | 10 +++++----- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index 7e15a3a3edb1..15eb0a0defd6 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -262,9 +262,10 @@ struct kmem_cache *__kmem_cache_create_args(const char *name, unsigned int object_size, struct kmem_cache_args *args, slab_flags_t flags); -struct kmem_cache *kmem_cache_create(const char *name, unsigned int size, - unsigned int align, slab_flags_t flags, - void (*ctor)(void *)); + +struct kmem_cache *__kmem_cache_create(const char *name, unsigned int size, + unsigned int align, slab_flags_t flags, + void (*ctor)(void *)); struct kmem_cache *kmem_cache_create_usercopy(const char *name, unsigned int size, unsigned int align, slab_flags_t flags, @@ -273,6 +274,28 @@ struct kmem_cache *kmem_cache_create_usercopy(const char *name, struct kmem_cache *kmem_cache_create_rcu(const char *name, unsigned int size, unsigned int freeptr_offset, slab_flags_t flags); + +/* If NULL is passed for @args, use this variant with default arguments. */ +static inline struct kmem_cache * +__kmem_cache_default_args(const char *name, unsigned int size, + struct kmem_cache_args *args, + slab_flags_t flags) +{ + struct kmem_cache_args kmem_default_args = {}; + + /* Make sure we don't get passed garbage. */ + if (WARN_ON_ONCE(args)) + return ERR_PTR(-EINVAL); + + return __kmem_cache_create_args(name, size, &kmem_default_args, flags); +} + +#define kmem_cache_create(__name, __object_size, __args, ...) \ + _Generic((__args), \ + struct kmem_cache_args *: __kmem_cache_create_args, \ + void *: __kmem_cache_default_args, \ + default: __kmem_cache_create)(__name, __object_size, __args, __VA_ARGS__) + void kmem_cache_destroy(struct kmem_cache *s); int kmem_cache_shrink(struct kmem_cache *s); diff --git a/mm/slab_common.c b/mm/slab_common.c index 19ae3dd6e36f..418459927670 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -383,7 +383,7 @@ kmem_cache_create_usercopy(const char *name, unsigned int size, EXPORT_SYMBOL(kmem_cache_create_usercopy); /** - * kmem_cache_create - Create a cache. + * __kmem_cache_create - Create a cache. * @name: A string which is used in /proc/slabinfo to identify this cache. * @size: The size of objects to be created in this cache. * @align: The required alignment for the objects. @@ -407,9 +407,9 @@ EXPORT_SYMBOL(kmem_cache_create_usercopy); * * Return: a pointer to the cache on success, NULL on failure. */ -struct kmem_cache * -kmem_cache_create(const char *name, unsigned int size, unsigned int align, - slab_flags_t flags, void (*ctor)(void *)) +struct kmem_cache *__kmem_cache_create(const char *name, unsigned int size, + unsigned int align, slab_flags_t flags, + void (*ctor)(void *)) { struct kmem_cache_args kmem_args = { .align = align, @@ -418,7 +418,7 @@ kmem_cache_create(const char *name, unsigned int size, unsigned int align, return __kmem_cache_create_args(name, size, &kmem_args, flags); } -EXPORT_SYMBOL(kmem_cache_create); +EXPORT_SYMBOL(__kmem_cache_create); /** * kmem_cache_create_rcu - Create a SLAB_TYPESAFE_BY_RCU cache. From 5f7d25668217bb7841bc5784d2185618a01e336b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:56 +0200 Subject: [PATCH 480/573] file: port to struct kmem_cache_args Port filp_cache to struct kmem_cache_args. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- fs/file_table.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/file_table.c b/fs/file_table.c index 3ef558f27a1c..861c03608e83 100644 --- a/fs/file_table.c +++ b/fs/file_table.c @@ -511,9 +511,14 @@ EXPORT_SYMBOL(__fput_sync); void __init files_init(void) { - filp_cachep = kmem_cache_create_rcu("filp", sizeof(struct file), - offsetof(struct file, f_freeptr), - SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT); + struct kmem_cache_args args = { + .use_freeptr_offset = true, + .freeptr_offset = offsetof(struct file, f_freeptr), + }; + + filp_cachep = kmem_cache_create("filp", sizeof(struct file), &args, + SLAB_HWCACHE_ALIGN | SLAB_PANIC | + SLAB_ACCOUNT | SLAB_TYPESAFE_BY_RCU); percpu_counter_init(&nr_files, 0, GFP_KERNEL); } From 3d453e60f1a9c377d670d5fe306d3b9eed477bc0 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:57 +0200 Subject: [PATCH 481/573] slab: remove kmem_cache_create_rcu() Now that we have ported all users of kmem_cache_create_rcu() to struct kmem_cache_args the function is unused and can be removed. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- include/linux/slab.h | 3 --- mm/slab_common.c | 43 ------------------------------------------- 2 files changed, 46 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index 15eb0a0defd6..cb82a6414a28 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -271,9 +271,6 @@ struct kmem_cache *kmem_cache_create_usercopy(const char *name, slab_flags_t flags, unsigned int useroffset, unsigned int usersize, void (*ctor)(void *)); -struct kmem_cache *kmem_cache_create_rcu(const char *name, unsigned int size, - unsigned int freeptr_offset, - slab_flags_t flags); /* If NULL is passed for @args, use this variant with default arguments. */ static inline struct kmem_cache * diff --git a/mm/slab_common.c b/mm/slab_common.c index 418459927670..9133b9fafcb1 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -420,49 +420,6 @@ struct kmem_cache *__kmem_cache_create(const char *name, unsigned int size, } EXPORT_SYMBOL(__kmem_cache_create); -/** - * kmem_cache_create_rcu - Create a SLAB_TYPESAFE_BY_RCU cache. - * @name: A string which is used in /proc/slabinfo to identify this cache. - * @size: The size of objects to be created in this cache. - * @freeptr_offset: The offset into the memory to the free pointer - * @flags: SLAB flags - * - * Cannot be called within an interrupt, but can be interrupted. - * - * See kmem_cache_create() for an explanation of possible @flags. - * - * By default SLAB_TYPESAFE_BY_RCU caches place the free pointer outside - * of the object. This might cause the object to grow in size. Callers - * that have a reason to avoid this can specify a custom free pointer - * offset in their struct where the free pointer will be placed. - * - * Note that placing the free pointer inside the object requires the - * caller to ensure that no fields are invalidated that are required to - * guard against object recycling (See SLAB_TYPESAFE_BY_RCU for - * details.). - * - * Using zero as a value for @freeptr_offset is valid. To request no - * offset UINT_MAX must be specified. - * - * Note that @ctor isn't supported with custom free pointers as a @ctor - * requires an external free pointer. - * - * Return: a pointer to the cache on success, NULL on failure. - */ -struct kmem_cache *kmem_cache_create_rcu(const char *name, unsigned int size, - unsigned int freeptr_offset, - slab_flags_t flags) -{ - struct kmem_cache_args kmem_args = { - .freeptr_offset = freeptr_offset, - .use_freeptr_offset = true, - }; - - return __kmem_cache_create_args(name, size, &kmem_args, - flags | SLAB_TYPESAFE_BY_RCU); -} -EXPORT_SYMBOL(kmem_cache_create_rcu); - static struct kmem_cache *kmem_buckets_cache __ro_after_init; /** From 0c9050b09cfb1ddb3fe4b2d401dca1fb674c825a Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:58 +0200 Subject: [PATCH 482/573] slab: make kmem_cache_create_usercopy() static inline Make kmem_cache_create_usercopy() a static inline function. Signed-off-by: Christian Brauner Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- include/linux/slab.h | 49 +++++++++++++++++++++++++++++++++++++++----- mm/slab_common.c | 45 ---------------------------------------- 2 files changed, 44 insertions(+), 50 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index cb82a6414a28..634717c9f73b 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -266,11 +266,50 @@ struct kmem_cache *__kmem_cache_create_args(const char *name, struct kmem_cache *__kmem_cache_create(const char *name, unsigned int size, unsigned int align, slab_flags_t flags, void (*ctor)(void *)); -struct kmem_cache *kmem_cache_create_usercopy(const char *name, - unsigned int size, unsigned int align, - slab_flags_t flags, - unsigned int useroffset, unsigned int usersize, - void (*ctor)(void *)); + +/** + * kmem_cache_create_usercopy - Create a cache with a region suitable + * for copying to userspace + * @name: A string which is used in /proc/slabinfo to identify this cache. + * @size: The size of objects to be created in this cache. + * @align: The required alignment for the objects. + * @flags: SLAB flags + * @useroffset: Usercopy region offset + * @usersize: Usercopy region size + * @ctor: A constructor for the objects. + * + * Cannot be called within a interrupt, but can be interrupted. + * The @ctor is run when new pages are allocated by the cache. + * + * The flags are + * + * %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5) + * to catch references to uninitialised memory. + * + * %SLAB_RED_ZONE - Insert `Red` zones around the allocated memory to check + * for buffer overruns. + * + * %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware + * cacheline. This can be beneficial if you're counting cycles as closely + * as davem. + * + * Return: a pointer to the cache on success, NULL on failure. + */ +static inline struct kmem_cache * +kmem_cache_create_usercopy(const char *name, unsigned int size, + unsigned int align, slab_flags_t flags, + unsigned int useroffset, unsigned int usersize, + void (*ctor)(void *)) +{ + struct kmem_cache_args kmem_args = { + .align = align, + .ctor = ctor, + .useroffset = useroffset, + .usersize = usersize, + }; + + return __kmem_cache_create_args(name, size, &kmem_args, flags); +} /* If NULL is passed for @args, use this variant with default arguments. */ static inline struct kmem_cache * diff --git a/mm/slab_common.c b/mm/slab_common.c index 9133b9fafcb1..3477a3918afd 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -337,51 +337,6 @@ out_unlock: } EXPORT_SYMBOL(__kmem_cache_create_args); -/** - * kmem_cache_create_usercopy - Create a cache with a region suitable - * for copying to userspace - * @name: A string which is used in /proc/slabinfo to identify this cache. - * @size: The size of objects to be created in this cache. - * @align: The required alignment for the objects. - * @flags: SLAB flags - * @useroffset: Usercopy region offset - * @usersize: Usercopy region size - * @ctor: A constructor for the objects. - * - * Cannot be called within a interrupt, but can be interrupted. - * The @ctor is run when new pages are allocated by the cache. - * - * The flags are - * - * %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5) - * to catch references to uninitialised memory. - * - * %SLAB_RED_ZONE - Insert `Red` zones around the allocated memory to check - * for buffer overruns. - * - * %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware - * cacheline. This can be beneficial if you're counting cycles as closely - * as davem. - * - * Return: a pointer to the cache on success, NULL on failure. - */ -struct kmem_cache * -kmem_cache_create_usercopy(const char *name, unsigned int size, - unsigned int align, slab_flags_t flags, - unsigned int useroffset, unsigned int usersize, - void (*ctor)(void *)) -{ - struct kmem_cache_args kmem_args = { - .align = align, - .ctor = ctor, - .useroffset = useroffset, - .usersize = usersize, - }; - - return __kmem_cache_create_args(name, size, &kmem_args, flags); -} -EXPORT_SYMBOL(kmem_cache_create_usercopy); - /** * __kmem_cache_create - Create a cache. * @name: A string which is used in /proc/slabinfo to identify this cache. From 781aee755638dbb6dbfe022bdd2f732621ec9534 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:56:59 +0200 Subject: [PATCH 483/573] slab: make __kmem_cache_create() static inline Make __kmem_cache_create() a static inline function. Signed-off-by: Christian Brauner Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka --- include/linux/slab.h | 13 ++++++++++--- mm/slab_common.c | 38 -------------------------------------- 2 files changed, 10 insertions(+), 41 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index 634717c9f73b..331412a9f4f2 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -262,10 +262,17 @@ struct kmem_cache *__kmem_cache_create_args(const char *name, unsigned int object_size, struct kmem_cache_args *args, slab_flags_t flags); +static inline struct kmem_cache * +__kmem_cache_create(const char *name, unsigned int size, unsigned int align, + slab_flags_t flags, void (*ctor)(void *)) +{ + struct kmem_cache_args kmem_args = { + .align = align, + .ctor = ctor, + }; -struct kmem_cache *__kmem_cache_create(const char *name, unsigned int size, - unsigned int align, slab_flags_t flags, - void (*ctor)(void *)); + return __kmem_cache_create_args(name, size, &kmem_args, flags); +} /** * kmem_cache_create_usercopy - Create a cache with a region suitable diff --git a/mm/slab_common.c b/mm/slab_common.c index 3477a3918afd..30000dcf0736 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -337,44 +337,6 @@ out_unlock: } EXPORT_SYMBOL(__kmem_cache_create_args); -/** - * __kmem_cache_create - Create a cache. - * @name: A string which is used in /proc/slabinfo to identify this cache. - * @size: The size of objects to be created in this cache. - * @align: The required alignment for the objects. - * @flags: SLAB flags - * @ctor: A constructor for the objects. - * - * Cannot be called within a interrupt, but can be interrupted. - * The @ctor is run when new pages are allocated by the cache. - * - * The flags are - * - * %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5) - * to catch references to uninitialised memory. - * - * %SLAB_RED_ZONE - Insert `Red` zones around the allocated memory to check - * for buffer overruns. - * - * %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware - * cacheline. This can be beneficial if you're counting cycles as closely - * as davem. - * - * Return: a pointer to the cache on success, NULL on failure. - */ -struct kmem_cache *__kmem_cache_create(const char *name, unsigned int size, - unsigned int align, slab_flags_t flags, - void (*ctor)(void *)) -{ - struct kmem_cache_args kmem_args = { - .align = align, - .ctor = ctor, - }; - - return __kmem_cache_create_args(name, size, &kmem_args, flags); -} -EXPORT_SYMBOL(__kmem_cache_create); - static struct kmem_cache *kmem_buckets_cache __ro_after_init; /** From a6711d1cd4e291f334ae900065e18f585732acfa Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 5 Sep 2024 09:57:00 +0200 Subject: [PATCH 484/573] io_uring: port to struct kmem_cache_args Port req_cachep to struct kmem_cache_args. Reviewed-by: Kees Cook Reviewed-by: Jens Axboe Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Vlastimil Babka Signed-off-by: Christian Brauner Signed-off-by: Vlastimil Babka --- io_uring/io_uring.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 3942db160f18..d9d721d1424e 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -3638,6 +3638,11 @@ SYSCALL_DEFINE2(io_uring_setup, u32, entries, static int __init io_uring_init(void) { + struct kmem_cache_args kmem_args = { + .useroffset = offsetof(struct io_kiocb, cmd.data), + .usersize = sizeof_field(struct io_kiocb, cmd.data), + }; + #define __BUILD_BUG_VERIFY_OFFSET_SIZE(stype, eoffset, esize, ename) do { \ BUILD_BUG_ON(offsetof(stype, ename) != eoffset); \ BUILD_BUG_ON(sizeof_field(stype, ename) != esize); \ @@ -3722,12 +3727,9 @@ static int __init io_uring_init(void) * range, and HARDENED_USERCOPY will complain if we haven't * correctly annotated this range. */ - req_cachep = kmem_cache_create_usercopy("io_kiocb", - sizeof(struct io_kiocb), 0, - SLAB_HWCACHE_ALIGN | SLAB_PANIC | - SLAB_ACCOUNT | SLAB_TYPESAFE_BY_RCU, - offsetof(struct io_kiocb, cmd.data), - sizeof_field(struct io_kiocb, cmd.data), NULL); + req_cachep = kmem_cache_create("io_kiocb", sizeof(struct io_kiocb), &kmem_args, + SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT | + SLAB_TYPESAFE_BY_RCU); io_buf_cachep = KMEM_CACHE(io_buffer, SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT); From 4ba4f1afb6a9fed8ef896c2363076e36572f71da Mon Sep 17 00:00:00 2001 From: Kan Liang Date: Fri, 2 Aug 2024 08:16:37 -0700 Subject: [PATCH 485/573] perf: Generic hotplug support for a PMU with a scope The perf subsystem assumes that the counters of a PMU are per-CPU. So the user space tool reads a counter from each CPU in the system wide mode. However, many PMUs don't have a per-CPU counter. The counter is effective for a scope, e.g., a die or a socket. To address this, a cpumask is exposed by the kernel driver to restrict to one CPU to stand for a specific scope. In case the given CPU is removed, the hotplug support has to be implemented for each such driver. The codes to support the cpumask and hotplug are very similar. - Expose a cpumask into sysfs - Pickup another CPU in the same scope if the given CPU is removed. - Invoke the perf_pmu_migrate_context() to migrate to a new CPU. - In event init, always set the CPU in the cpumask to event->cpu Similar duplicated codes are implemented for each such PMU driver. It would be good to introduce a generic infrastructure to avoid such duplication. 5 popular scopes are implemented here, core, die, cluster, pkg, and the system-wide. The scope can be set when a PMU is registered. If so, a "cpumask" is automatically exposed for the PMU. The "cpumask" is from the perf_online__mask, which is to track the active CPU for each scope. They are set when the first CPU of the scope is online via the generic perf hotplug support. When a corresponding CPU is removed, the perf_online__mask is updated accordingly and the PMU will be moved to a new CPU from the same scope if possible. Signed-off-by: Kan Liang Signed-off-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/r/20240802151643.1691631-2-kan.liang@linux.intel.com --- include/linux/perf_event.h | 18 ++++ kernel/events/core.c | 164 ++++++++++++++++++++++++++++++++++++- 2 files changed, 180 insertions(+), 2 deletions(-) diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 701549967c18..a3cbcd727366 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -295,6 +295,19 @@ struct perf_event_pmu_context; #define PERF_PMU_CAP_AUX_OUTPUT 0x0080 #define PERF_PMU_CAP_EXTENDED_HW_TYPE 0x0100 +/** + * pmu::scope + */ +enum perf_pmu_scope { + PERF_PMU_SCOPE_NONE = 0, + PERF_PMU_SCOPE_CORE, + PERF_PMU_SCOPE_DIE, + PERF_PMU_SCOPE_CLUSTER, + PERF_PMU_SCOPE_PKG, + PERF_PMU_SCOPE_SYS_WIDE, + PERF_PMU_MAX_SCOPE, +}; + struct perf_output_handle; #define PMU_NULL_DEV ((void *)(~0UL)) @@ -318,6 +331,11 @@ struct pmu { */ int capabilities; + /* + * PMU scope + */ + unsigned int scope; + int __percpu *pmu_disable_count; struct perf_cpu_pmu_context __percpu *cpu_pmu_context; atomic_t exclusive_cnt; /* < 0: cpu; > 0: tsk */ diff --git a/kernel/events/core.c b/kernel/events/core.c index 67e115d4ef96..5ff973513fac 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -436,6 +436,11 @@ static LIST_HEAD(pmus); static DEFINE_MUTEX(pmus_lock); static struct srcu_struct pmus_srcu; static cpumask_var_t perf_online_mask; +static cpumask_var_t perf_online_core_mask; +static cpumask_var_t perf_online_die_mask; +static cpumask_var_t perf_online_cluster_mask; +static cpumask_var_t perf_online_pkg_mask; +static cpumask_var_t perf_online_sys_mask; static struct kmem_cache *perf_event_cache; /* @@ -11578,10 +11583,60 @@ perf_event_mux_interval_ms_store(struct device *dev, } static DEVICE_ATTR_RW(perf_event_mux_interval_ms); +static inline const struct cpumask *perf_scope_cpu_topology_cpumask(unsigned int scope, int cpu) +{ + switch (scope) { + case PERF_PMU_SCOPE_CORE: + return topology_sibling_cpumask(cpu); + case PERF_PMU_SCOPE_DIE: + return topology_die_cpumask(cpu); + case PERF_PMU_SCOPE_CLUSTER: + return topology_cluster_cpumask(cpu); + case PERF_PMU_SCOPE_PKG: + return topology_core_cpumask(cpu); + case PERF_PMU_SCOPE_SYS_WIDE: + return cpu_online_mask; + } + + return NULL; +} + +static inline struct cpumask *perf_scope_cpumask(unsigned int scope) +{ + switch (scope) { + case PERF_PMU_SCOPE_CORE: + return perf_online_core_mask; + case PERF_PMU_SCOPE_DIE: + return perf_online_die_mask; + case PERF_PMU_SCOPE_CLUSTER: + return perf_online_cluster_mask; + case PERF_PMU_SCOPE_PKG: + return perf_online_pkg_mask; + case PERF_PMU_SCOPE_SYS_WIDE: + return perf_online_sys_mask; + } + + return NULL; +} + +static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pmu *pmu = dev_get_drvdata(dev); + struct cpumask *mask = perf_scope_cpumask(pmu->scope); + + if (mask) + return cpumap_print_to_pagebuf(true, buf, mask); + return 0; +} + +static DEVICE_ATTR_RO(cpumask); + static struct attribute *pmu_dev_attrs[] = { &dev_attr_type.attr, &dev_attr_perf_event_mux_interval_ms.attr, &dev_attr_nr_addr_filters.attr, + &dev_attr_cpumask.attr, NULL, }; @@ -11593,6 +11648,10 @@ static umode_t pmu_dev_is_visible(struct kobject *kobj, struct attribute *a, int if (n == 2 && !pmu->nr_addr_filters) return 0; + /* cpumask */ + if (n == 3 && pmu->scope == PERF_PMU_SCOPE_NONE) + return 0; + return a->mode; } @@ -11677,6 +11736,11 @@ int perf_pmu_register(struct pmu *pmu, const char *name, int type) goto free_pdc; } + if (WARN_ONCE(pmu->scope >= PERF_PMU_MAX_SCOPE, "Can not register a pmu with an invalid scope.\n")) { + ret = -EINVAL; + goto free_pdc; + } + pmu->name = name; if (type >= 0) @@ -11831,6 +11895,22 @@ static int perf_try_init_event(struct pmu *pmu, struct perf_event *event) event_has_any_exclude_flag(event)) ret = -EINVAL; + if (pmu->scope != PERF_PMU_SCOPE_NONE && event->cpu >= 0) { + const struct cpumask *cpumask = perf_scope_cpu_topology_cpumask(pmu->scope, event->cpu); + struct cpumask *pmu_cpumask = perf_scope_cpumask(pmu->scope); + int cpu; + + if (pmu_cpumask && cpumask) { + cpu = cpumask_any_and(pmu_cpumask, cpumask); + if (cpu >= nr_cpu_ids) + ret = -ENODEV; + else + event->cpu = cpu; + } else { + ret = -ENODEV; + } + } + if (ret && event->destroy) event->destroy(event); } @@ -13784,6 +13864,12 @@ static void __init perf_event_init_all_cpus(void) int cpu; zalloc_cpumask_var(&perf_online_mask, GFP_KERNEL); + zalloc_cpumask_var(&perf_online_core_mask, GFP_KERNEL); + zalloc_cpumask_var(&perf_online_die_mask, GFP_KERNEL); + zalloc_cpumask_var(&perf_online_cluster_mask, GFP_KERNEL); + zalloc_cpumask_var(&perf_online_pkg_mask, GFP_KERNEL); + zalloc_cpumask_var(&perf_online_sys_mask, GFP_KERNEL); + for_each_possible_cpu(cpu) { swhash = &per_cpu(swevent_htable, cpu); @@ -13833,6 +13919,40 @@ static void __perf_event_exit_context(void *__info) raw_spin_unlock(&ctx->lock); } +static void perf_event_clear_cpumask(unsigned int cpu) +{ + int target[PERF_PMU_MAX_SCOPE]; + unsigned int scope; + struct pmu *pmu; + + cpumask_clear_cpu(cpu, perf_online_mask); + + for (scope = PERF_PMU_SCOPE_NONE + 1; scope < PERF_PMU_MAX_SCOPE; scope++) { + const struct cpumask *cpumask = perf_scope_cpu_topology_cpumask(scope, cpu); + struct cpumask *pmu_cpumask = perf_scope_cpumask(scope); + + target[scope] = -1; + if (WARN_ON_ONCE(!pmu_cpumask || !cpumask)) + continue; + + if (!cpumask_test_and_clear_cpu(cpu, pmu_cpumask)) + continue; + target[scope] = cpumask_any_but(cpumask, cpu); + if (target[scope] < nr_cpu_ids) + cpumask_set_cpu(target[scope], pmu_cpumask); + } + + /* migrate */ + list_for_each_entry_rcu(pmu, &pmus, entry, lockdep_is_held(&pmus_srcu)) { + if (pmu->scope == PERF_PMU_SCOPE_NONE || + WARN_ON_ONCE(pmu->scope >= PERF_PMU_MAX_SCOPE)) + continue; + + if (target[pmu->scope] >= 0 && target[pmu->scope] < nr_cpu_ids) + perf_pmu_migrate_context(pmu, cpu, target[pmu->scope]); + } +} + static void perf_event_exit_cpu_context(int cpu) { struct perf_cpu_context *cpuctx; @@ -13840,6 +13960,11 @@ static void perf_event_exit_cpu_context(int cpu) // XXX simplify cpuctx->online mutex_lock(&pmus_lock); + /* + * Clear the cpumasks, and migrate to other CPUs if possible. + * Must be invoked before the __perf_event_exit_context. + */ + perf_event_clear_cpumask(cpu); cpuctx = per_cpu_ptr(&perf_cpu_context, cpu); ctx = &cpuctx->ctx; @@ -13847,7 +13972,6 @@ static void perf_event_exit_cpu_context(int cpu) smp_call_function_single(cpu, __perf_event_exit_context, ctx, 1); cpuctx->online = 0; mutex_unlock(&ctx->mutex); - cpumask_clear_cpu(cpu, perf_online_mask); mutex_unlock(&pmus_lock); } #else @@ -13856,6 +13980,42 @@ static void perf_event_exit_cpu_context(int cpu) { } #endif +static void perf_event_setup_cpumask(unsigned int cpu) +{ + struct cpumask *pmu_cpumask; + unsigned int scope; + + cpumask_set_cpu(cpu, perf_online_mask); + + /* + * Early boot stage, the cpumask hasn't been set yet. + * The perf_online__masks includes the first CPU of each domain. + * Always uncondifionally set the boot CPU for the perf_online__masks. + */ + if (!topology_sibling_cpumask(cpu)) { + for (scope = PERF_PMU_SCOPE_NONE + 1; scope < PERF_PMU_MAX_SCOPE; scope++) { + pmu_cpumask = perf_scope_cpumask(scope); + if (WARN_ON_ONCE(!pmu_cpumask)) + continue; + cpumask_set_cpu(cpu, pmu_cpumask); + } + return; + } + + for (scope = PERF_PMU_SCOPE_NONE + 1; scope < PERF_PMU_MAX_SCOPE; scope++) { + const struct cpumask *cpumask = perf_scope_cpu_topology_cpumask(scope, cpu); + + pmu_cpumask = perf_scope_cpumask(scope); + + if (WARN_ON_ONCE(!pmu_cpumask || !cpumask)) + continue; + + if (!cpumask_empty(cpumask) && + cpumask_any_and(pmu_cpumask, cpumask) >= nr_cpu_ids) + cpumask_set_cpu(cpu, pmu_cpumask); + } +} + int perf_event_init_cpu(unsigned int cpu) { struct perf_cpu_context *cpuctx; @@ -13864,7 +14024,7 @@ int perf_event_init_cpu(unsigned int cpu) perf_swevent_init_cpu(cpu); mutex_lock(&pmus_lock); - cpumask_set_cpu(cpu, perf_online_mask); + perf_event_setup_cpumask(cpu); cpuctx = per_cpu_ptr(&perf_cpu_context, cpu); ctx = &cpuctx->ctx; From a48a36b316ae5d3ab83f9b545dba15998e96d59c Mon Sep 17 00:00:00 2001 From: Kan Liang Date: Fri, 2 Aug 2024 08:16:38 -0700 Subject: [PATCH 486/573] perf: Add PERF_EV_CAP_READ_SCOPE Usually, an event can be read from any CPU of the scope. It doesn't need to be read from the advertised CPU. Add a new event cap, PERF_EV_CAP_READ_SCOPE. An event of a PMU with scope can be read from any active CPU in the scope. Signed-off-by: Kan Liang Signed-off-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/r/20240802151643.1691631-3-kan.liang@linux.intel.com --- include/linux/perf_event.h | 3 +++ kernel/events/core.c | 14 +++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index a3cbcd727366..794f66057878 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -636,10 +636,13 @@ typedef void (*perf_overflow_handler_t)(struct perf_event *, * PERF_EV_CAP_SIBLING: An event with this flag must be a group sibling and * cannot be a group leader. If an event with this flag is detached from the * group it is scheduled out and moved into an unrecoverable ERROR state. + * PERF_EV_CAP_READ_SCOPE: A CPU event that can be read from any CPU of the + * PMU scope where it is active. */ #define PERF_EV_CAP_SOFTWARE BIT(0) #define PERF_EV_CAP_READ_ACTIVE_PKG BIT(1) #define PERF_EV_CAP_SIBLING BIT(2) +#define PERF_EV_CAP_READ_SCOPE BIT(3) #define SWEVENT_HLIST_BITS 8 #define SWEVENT_HLIST_SIZE (1 << SWEVENT_HLIST_BITS) diff --git a/kernel/events/core.c b/kernel/events/core.c index 5ff973513fac..2766090de84e 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -4556,16 +4556,24 @@ struct perf_read_data { int ret; }; +static inline const struct cpumask *perf_scope_cpu_topology_cpumask(unsigned int scope, int cpu); + static int __perf_event_read_cpu(struct perf_event *event, int event_cpu) { + int local_cpu = smp_processor_id(); u16 local_pkg, event_pkg; if ((unsigned)event_cpu >= nr_cpu_ids) return event_cpu; - if (event->group_caps & PERF_EV_CAP_READ_ACTIVE_PKG) { - int local_cpu = smp_processor_id(); + if (event->group_caps & PERF_EV_CAP_READ_SCOPE) { + const struct cpumask *cpumask = perf_scope_cpu_topology_cpumask(event->pmu->scope, event_cpu); + if (cpumask && cpumask_test_cpu(local_cpu, cpumask)) + return local_cpu; + } + + if (event->group_caps & PERF_EV_CAP_READ_ACTIVE_PKG) { event_pkg = topology_physical_package_id(event_cpu); local_pkg = topology_physical_package_id(local_cpu); @@ -11905,7 +11913,7 @@ static int perf_try_init_event(struct pmu *pmu, struct perf_event *event) if (cpu >= nr_cpu_ids) ret = -ENODEV; else - event->cpu = cpu; + event->event_caps |= PERF_EV_CAP_READ_SCOPE; } else { ret = -ENODEV; } From 08155c7f2a2cc6ff99886ea5743da274be24ebe4 Mon Sep 17 00:00:00 2001 From: Kan Liang Date: Fri, 2 Aug 2024 08:16:39 -0700 Subject: [PATCH 487/573] perf/x86/intel/cstate: Clean up cpumask and hotplug There are three cstate PMUs with different scopes, core, die and module. The scopes are supported by the generic perf_event subsystem now. Set the scope for each PMU and remove all the cpumask and hotplug codes. Signed-off-by: Kan Liang Signed-off-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/r/20240802151643.1691631-4-kan.liang@linux.intel.com --- arch/x86/events/intel/cstate.c | 142 ++------------------------------- include/linux/cpuhotplug.h | 2 - 2 files changed, 5 insertions(+), 139 deletions(-) diff --git a/arch/x86/events/intel/cstate.c b/arch/x86/events/intel/cstate.c index 9f116dfc4728..ae4ec16156bb 100644 --- a/arch/x86/events/intel/cstate.c +++ b/arch/x86/events/intel/cstate.c @@ -128,10 +128,6 @@ static ssize_t __cstate_##_var##_show(struct device *dev, \ static struct device_attribute format_attr_##_var = \ __ATTR(_name, 0444, __cstate_##_var##_show, NULL) -static ssize_t cstate_get_attr_cpumask(struct device *dev, - struct device_attribute *attr, - char *buf); - /* Model -> events mapping */ struct cstate_model { unsigned long core_events; @@ -206,22 +202,9 @@ static struct attribute_group cstate_format_attr_group = { .attrs = cstate_format_attrs, }; -static cpumask_t cstate_core_cpu_mask; -static DEVICE_ATTR(cpumask, S_IRUGO, cstate_get_attr_cpumask, NULL); - -static struct attribute *cstate_cpumask_attrs[] = { - &dev_attr_cpumask.attr, - NULL, -}; - -static struct attribute_group cpumask_attr_group = { - .attrs = cstate_cpumask_attrs, -}; - static const struct attribute_group *cstate_attr_groups[] = { &cstate_events_attr_group, &cstate_format_attr_group, - &cpumask_attr_group, NULL, }; @@ -269,8 +252,6 @@ static struct perf_msr pkg_msr[] = { [PERF_CSTATE_PKG_C10_RES] = { MSR_PKG_C10_RESIDENCY, &group_cstate_pkg_c10, test_msr }, }; -static cpumask_t cstate_pkg_cpu_mask; - /* cstate_module PMU */ static struct pmu cstate_module_pmu; static bool has_cstate_module; @@ -291,28 +272,9 @@ static struct perf_msr module_msr[] = { [PERF_CSTATE_MODULE_C6_RES] = { MSR_MODULE_C6_RES_MS, &group_cstate_module_c6, test_msr }, }; -static cpumask_t cstate_module_cpu_mask; - -static ssize_t cstate_get_attr_cpumask(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct pmu *pmu = dev_get_drvdata(dev); - - if (pmu == &cstate_core_pmu) - return cpumap_print_to_pagebuf(true, buf, &cstate_core_cpu_mask); - else if (pmu == &cstate_pkg_pmu) - return cpumap_print_to_pagebuf(true, buf, &cstate_pkg_cpu_mask); - else if (pmu == &cstate_module_pmu) - return cpumap_print_to_pagebuf(true, buf, &cstate_module_cpu_mask); - else - return 0; -} - static int cstate_pmu_event_init(struct perf_event *event) { u64 cfg = event->attr.config; - int cpu; if (event->attr.type != event->pmu->type) return -ENOENT; @@ -331,20 +293,13 @@ static int cstate_pmu_event_init(struct perf_event *event) if (!(core_msr_mask & (1 << cfg))) return -EINVAL; event->hw.event_base = core_msr[cfg].msr; - cpu = cpumask_any_and(&cstate_core_cpu_mask, - topology_sibling_cpumask(event->cpu)); } else if (event->pmu == &cstate_pkg_pmu) { if (cfg >= PERF_CSTATE_PKG_EVENT_MAX) return -EINVAL; cfg = array_index_nospec((unsigned long)cfg, PERF_CSTATE_PKG_EVENT_MAX); if (!(pkg_msr_mask & (1 << cfg))) return -EINVAL; - - event->event_caps |= PERF_EV_CAP_READ_ACTIVE_PKG; - event->hw.event_base = pkg_msr[cfg].msr; - cpu = cpumask_any_and(&cstate_pkg_cpu_mask, - topology_die_cpumask(event->cpu)); } else if (event->pmu == &cstate_module_pmu) { if (cfg >= PERF_CSTATE_MODULE_EVENT_MAX) return -EINVAL; @@ -352,16 +307,10 @@ static int cstate_pmu_event_init(struct perf_event *event) if (!(module_msr_mask & (1 << cfg))) return -EINVAL; event->hw.event_base = module_msr[cfg].msr; - cpu = cpumask_any_and(&cstate_module_cpu_mask, - topology_cluster_cpumask(event->cpu)); } else { return -ENOENT; } - if (cpu >= nr_cpu_ids) - return -ENODEV; - - event->cpu = cpu; event->hw.config = cfg; event->hw.idx = -1; return 0; @@ -412,84 +361,6 @@ static int cstate_pmu_event_add(struct perf_event *event, int mode) return 0; } -/* - * Check if exiting cpu is the designated reader. If so migrate the - * events when there is a valid target available - */ -static int cstate_cpu_exit(unsigned int cpu) -{ - unsigned int target; - - if (has_cstate_core && - cpumask_test_and_clear_cpu(cpu, &cstate_core_cpu_mask)) { - - target = cpumask_any_but(topology_sibling_cpumask(cpu), cpu); - /* Migrate events if there is a valid target */ - if (target < nr_cpu_ids) { - cpumask_set_cpu(target, &cstate_core_cpu_mask); - perf_pmu_migrate_context(&cstate_core_pmu, cpu, target); - } - } - - if (has_cstate_pkg && - cpumask_test_and_clear_cpu(cpu, &cstate_pkg_cpu_mask)) { - - target = cpumask_any_but(topology_die_cpumask(cpu), cpu); - /* Migrate events if there is a valid target */ - if (target < nr_cpu_ids) { - cpumask_set_cpu(target, &cstate_pkg_cpu_mask); - perf_pmu_migrate_context(&cstate_pkg_pmu, cpu, target); - } - } - - if (has_cstate_module && - cpumask_test_and_clear_cpu(cpu, &cstate_module_cpu_mask)) { - - target = cpumask_any_but(topology_cluster_cpumask(cpu), cpu); - /* Migrate events if there is a valid target */ - if (target < nr_cpu_ids) { - cpumask_set_cpu(target, &cstate_module_cpu_mask); - perf_pmu_migrate_context(&cstate_module_pmu, cpu, target); - } - } - return 0; -} - -static int cstate_cpu_init(unsigned int cpu) -{ - unsigned int target; - - /* - * If this is the first online thread of that core, set it in - * the core cpu mask as the designated reader. - */ - target = cpumask_any_and(&cstate_core_cpu_mask, - topology_sibling_cpumask(cpu)); - - if (has_cstate_core && target >= nr_cpu_ids) - cpumask_set_cpu(cpu, &cstate_core_cpu_mask); - - /* - * If this is the first online thread of that package, set it - * in the package cpu mask as the designated reader. - */ - target = cpumask_any_and(&cstate_pkg_cpu_mask, - topology_die_cpumask(cpu)); - if (has_cstate_pkg && target >= nr_cpu_ids) - cpumask_set_cpu(cpu, &cstate_pkg_cpu_mask); - - /* - * If this is the first online thread of that cluster, set it - * in the cluster cpu mask as the designated reader. - */ - target = cpumask_any_and(&cstate_module_cpu_mask, - topology_cluster_cpumask(cpu)); - if (has_cstate_module && target >= nr_cpu_ids) - cpumask_set_cpu(cpu, &cstate_module_cpu_mask); - - return 0; -} - static const struct attribute_group *core_attr_update[] = { &group_cstate_core_c1, &group_cstate_core_c3, @@ -526,6 +397,7 @@ static struct pmu cstate_core_pmu = { .stop = cstate_pmu_event_stop, .read = cstate_pmu_event_update, .capabilities = PERF_PMU_CAP_NO_INTERRUPT | PERF_PMU_CAP_NO_EXCLUDE, + .scope = PERF_PMU_SCOPE_CORE, .module = THIS_MODULE, }; @@ -541,6 +413,7 @@ static struct pmu cstate_pkg_pmu = { .stop = cstate_pmu_event_stop, .read = cstate_pmu_event_update, .capabilities = PERF_PMU_CAP_NO_INTERRUPT | PERF_PMU_CAP_NO_EXCLUDE, + .scope = PERF_PMU_SCOPE_PKG, .module = THIS_MODULE, }; @@ -556,6 +429,7 @@ static struct pmu cstate_module_pmu = { .stop = cstate_pmu_event_stop, .read = cstate_pmu_event_update, .capabilities = PERF_PMU_CAP_NO_INTERRUPT | PERF_PMU_CAP_NO_EXCLUDE, + .scope = PERF_PMU_SCOPE_CLUSTER, .module = THIS_MODULE, }; @@ -810,9 +684,6 @@ static int __init cstate_probe(const struct cstate_model *cm) static inline void cstate_cleanup(void) { - cpuhp_remove_state_nocalls(CPUHP_AP_PERF_X86_CSTATE_ONLINE); - cpuhp_remove_state_nocalls(CPUHP_AP_PERF_X86_CSTATE_STARTING); - if (has_cstate_core) perf_pmu_unregister(&cstate_core_pmu); @@ -827,11 +698,6 @@ static int __init cstate_init(void) { int err; - cpuhp_setup_state(CPUHP_AP_PERF_X86_CSTATE_STARTING, - "perf/x86/cstate:starting", cstate_cpu_init, NULL); - cpuhp_setup_state(CPUHP_AP_PERF_X86_CSTATE_ONLINE, - "perf/x86/cstate:online", NULL, cstate_cpu_exit); - if (has_cstate_core) { err = perf_pmu_register(&cstate_core_pmu, cstate_core_pmu.name, -1); if (err) { @@ -844,6 +710,8 @@ static int __init cstate_init(void) if (has_cstate_pkg) { if (topology_max_dies_per_package() > 1) { + /* CLX-AP is multi-die and the cstate is die-scope */ + cstate_pkg_pmu.scope = PERF_PMU_SCOPE_DIE; err = perf_pmu_register(&cstate_pkg_pmu, "cstate_die", -1); } else { diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h index 9316c39260e0..2101ae2ecfca 100644 --- a/include/linux/cpuhotplug.h +++ b/include/linux/cpuhotplug.h @@ -152,7 +152,6 @@ enum cpuhp_state { CPUHP_AP_PERF_X86_AMD_UNCORE_STARTING, CPUHP_AP_PERF_X86_STARTING, CPUHP_AP_PERF_X86_AMD_IBS_STARTING, - CPUHP_AP_PERF_X86_CSTATE_STARTING, CPUHP_AP_PERF_XTENSA_STARTING, CPUHP_AP_ARM_VFP_STARTING, CPUHP_AP_ARM64_DEBUG_MONITORS_STARTING, @@ -209,7 +208,6 @@ enum cpuhp_state { CPUHP_AP_PERF_X86_AMD_UNCORE_ONLINE, CPUHP_AP_PERF_X86_AMD_POWER_ONLINE, CPUHP_AP_PERF_X86_RAPL_ONLINE, - CPUHP_AP_PERF_X86_CSTATE_ONLINE, CPUHP_AP_PERF_S390_CF_ONLINE, CPUHP_AP_PERF_S390_SF_ONLINE, CPUHP_AP_PERF_ARM_CCI_ONLINE, From a8c73b82f7792400555143773d0152d5bc3ac068 Mon Sep 17 00:00:00 2001 From: Kan Liang Date: Fri, 2 Aug 2024 08:16:40 -0700 Subject: [PATCH 488/573] iommu/vt-d: Clean up cpumask and hotplug for perfmon The iommu PMU is system-wide scope, which is supported by the generic perf_event subsystem now. Set the scope for the iommu PMU and remove all the cpumask and hotplug codes. Signed-off-by: Kan Liang Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Lu Baolu Link: https://lore.kernel.org/r/20240802151643.1691631-5-kan.liang@linux.intel.com --- drivers/iommu/intel/iommu.h | 2 - drivers/iommu/intel/perfmon.c | 111 +--------------------------------- 2 files changed, 2 insertions(+), 111 deletions(-) diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h index b67c14da1240..bd2c5a4ca11a 100644 --- a/drivers/iommu/intel/iommu.h +++ b/drivers/iommu/intel/iommu.h @@ -687,8 +687,6 @@ struct iommu_pmu { DECLARE_BITMAP(used_mask, IOMMU_PMU_IDX_MAX); struct perf_event *event_list[IOMMU_PMU_IDX_MAX]; unsigned char irq_name[16]; - struct hlist_node cpuhp_node; - int cpu; }; #define IOMMU_IRQ_ID_OFFSET_PRQ (DMAR_UNITS_SUPPORTED) diff --git a/drivers/iommu/intel/perfmon.c b/drivers/iommu/intel/perfmon.c index 44083d01852d..75f493bcb353 100644 --- a/drivers/iommu/intel/perfmon.c +++ b/drivers/iommu/intel/perfmon.c @@ -34,28 +34,9 @@ static struct attribute_group iommu_pmu_events_attr_group = { .attrs = attrs_empty, }; -static cpumask_t iommu_pmu_cpu_mask; - -static ssize_t -cpumask_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - return cpumap_print_to_pagebuf(true, buf, &iommu_pmu_cpu_mask); -} -static DEVICE_ATTR_RO(cpumask); - -static struct attribute *iommu_pmu_cpumask_attrs[] = { - &dev_attr_cpumask.attr, - NULL -}; - -static struct attribute_group iommu_pmu_cpumask_attr_group = { - .attrs = iommu_pmu_cpumask_attrs, -}; - static const struct attribute_group *iommu_pmu_attr_groups[] = { &iommu_pmu_format_attr_group, &iommu_pmu_events_attr_group, - &iommu_pmu_cpumask_attr_group, NULL }; @@ -565,6 +546,7 @@ static int __iommu_pmu_register(struct intel_iommu *iommu) iommu_pmu->pmu.attr_groups = iommu_pmu_attr_groups; iommu_pmu->pmu.attr_update = iommu_pmu_attr_update; iommu_pmu->pmu.capabilities = PERF_PMU_CAP_NO_EXCLUDE; + iommu_pmu->pmu.scope = PERF_PMU_SCOPE_SYS_WIDE; iommu_pmu->pmu.module = THIS_MODULE; return perf_pmu_register(&iommu_pmu->pmu, iommu_pmu->pmu.name, -1); @@ -773,89 +755,6 @@ static void iommu_pmu_unset_interrupt(struct intel_iommu *iommu) iommu->perf_irq = 0; } -static int iommu_pmu_cpu_online(unsigned int cpu, struct hlist_node *node) -{ - struct iommu_pmu *iommu_pmu = hlist_entry_safe(node, typeof(*iommu_pmu), cpuhp_node); - - if (cpumask_empty(&iommu_pmu_cpu_mask)) - cpumask_set_cpu(cpu, &iommu_pmu_cpu_mask); - - if (cpumask_test_cpu(cpu, &iommu_pmu_cpu_mask)) - iommu_pmu->cpu = cpu; - - return 0; -} - -static int iommu_pmu_cpu_offline(unsigned int cpu, struct hlist_node *node) -{ - struct iommu_pmu *iommu_pmu = hlist_entry_safe(node, typeof(*iommu_pmu), cpuhp_node); - int target = cpumask_first(&iommu_pmu_cpu_mask); - - /* - * The iommu_pmu_cpu_mask has been updated when offline the CPU - * for the first iommu_pmu. Migrate the other iommu_pmu to the - * new target. - */ - if (target < nr_cpu_ids && target != iommu_pmu->cpu) { - perf_pmu_migrate_context(&iommu_pmu->pmu, cpu, target); - iommu_pmu->cpu = target; - return 0; - } - - if (!cpumask_test_and_clear_cpu(cpu, &iommu_pmu_cpu_mask)) - return 0; - - target = cpumask_any_but(cpu_online_mask, cpu); - - if (target < nr_cpu_ids) - cpumask_set_cpu(target, &iommu_pmu_cpu_mask); - else - return 0; - - perf_pmu_migrate_context(&iommu_pmu->pmu, cpu, target); - iommu_pmu->cpu = target; - - return 0; -} - -static int nr_iommu_pmu; -static enum cpuhp_state iommu_cpuhp_slot; - -static int iommu_pmu_cpuhp_setup(struct iommu_pmu *iommu_pmu) -{ - int ret; - - if (!nr_iommu_pmu) { - ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, - "driver/iommu/intel/perfmon:online", - iommu_pmu_cpu_online, - iommu_pmu_cpu_offline); - if (ret < 0) - return ret; - iommu_cpuhp_slot = ret; - } - - ret = cpuhp_state_add_instance(iommu_cpuhp_slot, &iommu_pmu->cpuhp_node); - if (ret) { - if (!nr_iommu_pmu) - cpuhp_remove_multi_state(iommu_cpuhp_slot); - return ret; - } - nr_iommu_pmu++; - - return 0; -} - -static void iommu_pmu_cpuhp_free(struct iommu_pmu *iommu_pmu) -{ - cpuhp_state_remove_instance(iommu_cpuhp_slot, &iommu_pmu->cpuhp_node); - - if (--nr_iommu_pmu) - return; - - cpuhp_remove_multi_state(iommu_cpuhp_slot); -} - void iommu_pmu_register(struct intel_iommu *iommu) { struct iommu_pmu *iommu_pmu = iommu->pmu; @@ -866,17 +765,12 @@ void iommu_pmu_register(struct intel_iommu *iommu) if (__iommu_pmu_register(iommu)) goto err; - if (iommu_pmu_cpuhp_setup(iommu_pmu)) - goto unregister; - /* Set interrupt for overflow */ if (iommu_pmu_set_interrupt(iommu)) - goto cpuhp_free; + goto unregister; return; -cpuhp_free: - iommu_pmu_cpuhp_free(iommu_pmu); unregister: perf_pmu_unregister(&iommu_pmu->pmu); err: @@ -892,6 +786,5 @@ void iommu_pmu_unregister(struct intel_iommu *iommu) return; iommu_pmu_unset_interrupt(iommu); - iommu_pmu_cpuhp_free(iommu_pmu); perf_pmu_unregister(&iommu_pmu->pmu); } From bbdd4df35c31fe502c1c70e4d3a8e3ebe5a270b7 Mon Sep 17 00:00:00 2001 From: Kan Liang Date: Fri, 2 Aug 2024 08:16:41 -0700 Subject: [PATCH 489/573] dmaengine: idxd: Clean up cpumask and hotplug for perfmon The idxd PMU is system-wide scope, which is supported by the generic perf_event subsystem now. Set the scope for the idxd PMU and remove all the cpumask and hotplug codes. Signed-off-by: Kan Liang Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dave Jiang Reviewed-by: Fenghua Yu Link: https://lore.kernel.org/r/20240802151643.1691631-6-kan.liang@linux.intel.com --- drivers/dma/idxd/idxd.h | 7 --- drivers/dma/idxd/init.c | 3 -- drivers/dma/idxd/perfmon.c | 98 +------------------------------------- 3 files changed, 1 insertion(+), 107 deletions(-) diff --git a/drivers/dma/idxd/idxd.h b/drivers/dma/idxd/idxd.h index 868b724a3b75..d84e21daa991 100644 --- a/drivers/dma/idxd/idxd.h +++ b/drivers/dma/idxd/idxd.h @@ -124,7 +124,6 @@ struct idxd_pmu { struct pmu pmu; char name[IDXD_NAME_SIZE]; - int cpu; int n_counters; int counter_width; @@ -135,8 +134,6 @@ struct idxd_pmu { unsigned long supported_filters; int n_filters; - - struct hlist_node cpuhp_node; }; #define IDXD_MAX_PRIORITY 0xf @@ -803,14 +800,10 @@ void idxd_user_counter_increment(struct idxd_wq *wq, u32 pasid, int index); int perfmon_pmu_init(struct idxd_device *idxd); void perfmon_pmu_remove(struct idxd_device *idxd); void perfmon_counter_overflow(struct idxd_device *idxd); -void perfmon_init(void); -void perfmon_exit(void); #else static inline int perfmon_pmu_init(struct idxd_device *idxd) { return 0; } static inline void perfmon_pmu_remove(struct idxd_device *idxd) {} static inline void perfmon_counter_overflow(struct idxd_device *idxd) {} -static inline void perfmon_init(void) {} -static inline void perfmon_exit(void) {} #endif /* debugfs */ diff --git a/drivers/dma/idxd/init.c b/drivers/dma/idxd/init.c index 21f6905b554d..5725ea82c409 100644 --- a/drivers/dma/idxd/init.c +++ b/drivers/dma/idxd/init.c @@ -878,8 +878,6 @@ static int __init idxd_init_module(void) else support_enqcmd = true; - perfmon_init(); - err = idxd_driver_register(&idxd_drv); if (err < 0) goto err_idxd_driver_register; @@ -928,7 +926,6 @@ static void __exit idxd_exit_module(void) idxd_driver_unregister(&idxd_drv); pci_unregister_driver(&idxd_pci_driver); idxd_cdev_remove(); - perfmon_exit(); idxd_remove_debugfs(); } module_exit(idxd_exit_module); diff --git a/drivers/dma/idxd/perfmon.c b/drivers/dma/idxd/perfmon.c index 5e94247e1ea7..f511cf15845b 100644 --- a/drivers/dma/idxd/perfmon.c +++ b/drivers/dma/idxd/perfmon.c @@ -6,29 +6,6 @@ #include "idxd.h" #include "perfmon.h" -static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, - char *buf); - -static cpumask_t perfmon_dsa_cpu_mask; -static bool cpuhp_set_up; -static enum cpuhp_state cpuhp_slot; - -/* - * perf userspace reads this attribute to determine which cpus to open - * counters on. It's connected to perfmon_dsa_cpu_mask, which is - * maintained by the cpu hotplug handlers. - */ -static DEVICE_ATTR_RO(cpumask); - -static struct attribute *perfmon_cpumask_attrs[] = { - &dev_attr_cpumask.attr, - NULL, -}; - -static struct attribute_group cpumask_attr_group = { - .attrs = perfmon_cpumask_attrs, -}; - /* * These attributes specify the bits in the config word that the perf * syscall uses to pass the event ids and categories to perfmon. @@ -67,16 +44,9 @@ static struct attribute_group perfmon_format_attr_group = { static const struct attribute_group *perfmon_attr_groups[] = { &perfmon_format_attr_group, - &cpumask_attr_group, NULL, }; -static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - return cpumap_print_to_pagebuf(true, buf, &perfmon_dsa_cpu_mask); -} - static bool is_idxd_event(struct idxd_pmu *idxd_pmu, struct perf_event *event) { return &idxd_pmu->pmu == event->pmu; @@ -217,7 +187,6 @@ static int perfmon_pmu_event_init(struct perf_event *event) return -EINVAL; event->hw.event_base = ioread64(PERFMON_TABLE_OFFSET(idxd)); - event->cpu = idxd->idxd_pmu->cpu; event->hw.config = event->attr.config; if (event->group_leader != event) @@ -488,6 +457,7 @@ static void idxd_pmu_init(struct idxd_pmu *idxd_pmu) idxd_pmu->pmu.stop = perfmon_pmu_event_stop; idxd_pmu->pmu.read = perfmon_pmu_event_update; idxd_pmu->pmu.capabilities = PERF_PMU_CAP_NO_EXCLUDE; + idxd_pmu->pmu.scope = PERF_PMU_SCOPE_SYS_WIDE; idxd_pmu->pmu.module = THIS_MODULE; } @@ -496,59 +466,17 @@ void perfmon_pmu_remove(struct idxd_device *idxd) if (!idxd->idxd_pmu) return; - cpuhp_state_remove_instance(cpuhp_slot, &idxd->idxd_pmu->cpuhp_node); perf_pmu_unregister(&idxd->idxd_pmu->pmu); kfree(idxd->idxd_pmu); idxd->idxd_pmu = NULL; } -static int perf_event_cpu_online(unsigned int cpu, struct hlist_node *node) -{ - struct idxd_pmu *idxd_pmu; - - idxd_pmu = hlist_entry_safe(node, typeof(*idxd_pmu), cpuhp_node); - - /* select the first online CPU as the designated reader */ - if (cpumask_empty(&perfmon_dsa_cpu_mask)) { - cpumask_set_cpu(cpu, &perfmon_dsa_cpu_mask); - idxd_pmu->cpu = cpu; - } - - return 0; -} - -static int perf_event_cpu_offline(unsigned int cpu, struct hlist_node *node) -{ - struct idxd_pmu *idxd_pmu; - unsigned int target; - - idxd_pmu = hlist_entry_safe(node, typeof(*idxd_pmu), cpuhp_node); - - if (!cpumask_test_and_clear_cpu(cpu, &perfmon_dsa_cpu_mask)) - return 0; - - target = cpumask_any_but(cpu_online_mask, cpu); - /* migrate events if there is a valid target */ - if (target < nr_cpu_ids) { - cpumask_set_cpu(target, &perfmon_dsa_cpu_mask); - perf_pmu_migrate_context(&idxd_pmu->pmu, cpu, target); - } - - return 0; -} - int perfmon_pmu_init(struct idxd_device *idxd) { union idxd_perfcap perfcap; struct idxd_pmu *idxd_pmu; int rc = -ENODEV; - /* - * perfmon module initialization failed, nothing to do - */ - if (!cpuhp_set_up) - return -ENODEV; - /* * If perfmon_offset or num_counters is 0, it means perfmon is * not supported on this hardware. @@ -624,11 +552,6 @@ int perfmon_pmu_init(struct idxd_device *idxd) if (rc) goto free; - rc = cpuhp_state_add_instance(cpuhp_slot, &idxd_pmu->cpuhp_node); - if (rc) { - perf_pmu_unregister(&idxd->idxd_pmu->pmu); - goto free; - } out: return rc; free: @@ -637,22 +560,3 @@ free: goto out; } - -void __init perfmon_init(void) -{ - int rc = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, - "driver/dma/idxd/perf:online", - perf_event_cpu_online, - perf_event_cpu_offline); - if (WARN_ON(rc < 0)) - return; - - cpuhp_slot = rc; - cpuhp_set_up = true; -} - -void __exit perfmon_exit(void) -{ - if (cpuhp_set_up) - cpuhp_remove_multi_state(cpuhp_slot); -} From f0295913c4b4f377c454e06f50c1a04f2f80d9df Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Thu, 5 Sep 2024 09:22:40 +0200 Subject: [PATCH 490/573] iommu/amd: Add kernel parameters to limit V1 page-sizes Add two new kernel command line parameters to limit the page-sizes used for v1 page-tables: nohugepages - Limits page-sizes to 4KiB v2_pgsizes_only - Limits page-sizes to 4Kib/2Mib/1GiB; The same as the sizes used with v2 page-tables This is needed for multiple scenarios. When assigning devices to SEV-SNP guests the IOMMU page-sizes need to match the sizes in the RMP table, otherwise the device will not be able to access all shared memory. Also, some ATS devices do not work properly with arbitrary IO page-sizes as supported by AMD-Vi, so limiting the sizes used by the driver is a suitable workaround. All-in-all, these parameters are only workarounds until the IOMMU core and related APIs gather the ability to negotiate the page-sizes in a better way. Signed-off-by: Joerg Roedel Reviewed-by: Vasant Hegde Link: https://lore.kernel.org/r/20240905072240.253313-1-joro@8bytes.org --- Documentation/admin-guide/kernel-parameters.txt | 17 +++++++++++------ drivers/iommu/amd/amd_iommu.h | 1 + drivers/iommu/amd/amd_iommu_types.h | 4 ++++ drivers/iommu/amd/init.c | 8 ++++++++ drivers/iommu/amd/io_pgtable.c | 2 +- 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 09126bb8cc9f..6d6630aec46c 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -333,12 +333,17 @@ allowed anymore to lift isolation requirements as needed. This option does not override iommu=pt - force_enable - Force enable the IOMMU on platforms known - to be buggy with IOMMU enabled. Use this - option with care. - pgtbl_v1 - Use v1 page table for DMA-API (Default). - pgtbl_v2 - Use v2 page table for DMA-API. - irtcachedis - Disable Interrupt Remapping Table (IRT) caching. + force_enable - Force enable the IOMMU on platforms known + to be buggy with IOMMU enabled. Use this + option with care. + pgtbl_v1 - Use v1 page table for DMA-API (Default). + pgtbl_v2 - Use v2 page table for DMA-API. + irtcachedis - Disable Interrupt Remapping Table (IRT) caching. + nohugepages - Limit page-sizes used for v1 page-tables + to 4 KiB. + v2_pgsizes_only - Limit page-sizes used for v1 page-tables + to 4KiB/2Mib/1GiB. + amd_iommu_dump= [HW,X86-64] Enable AMD IOMMU driver option to dump the ACPI table diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h index 29e6e71f7f9a..6386fa4556d9 100644 --- a/drivers/iommu/amd/amd_iommu.h +++ b/drivers/iommu/amd/amd_iommu.h @@ -43,6 +43,7 @@ int amd_iommu_enable_faulting(unsigned int cpu); extern int amd_iommu_guest_ir; extern enum io_pgtable_fmt amd_iommu_pgtable; extern int amd_iommu_gpt_level; +extern unsigned long amd_iommu_pgsize_bitmap; /* Protection domain ops */ struct protection_domain *protection_domain_alloc(unsigned int type, int nid); diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h index 35aa4ff020f5..601fb4ee6900 100644 --- a/drivers/iommu/amd/amd_iommu_types.h +++ b/drivers/iommu/amd/amd_iommu_types.h @@ -293,6 +293,10 @@ * Page sizes >= the 52 bit max physical address of the CPU are not supported. */ #define AMD_IOMMU_PGSIZES (GENMASK_ULL(51, 12) ^ SZ_512G) + +/* Special mode where page-sizes are limited to 4 KiB */ +#define AMD_IOMMU_PGSIZES_4K (PAGE_SIZE) + /* 4K, 2MB, 1G page sizes are supported */ #define AMD_IOMMU_PGSIZES_V2 (PAGE_SIZE | (1ULL << 21) | (1ULL << 30)) diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c index 6b15ce09e78d..43131c3a2172 100644 --- a/drivers/iommu/amd/init.c +++ b/drivers/iommu/amd/init.c @@ -192,6 +192,8 @@ bool amdr_ivrs_remap_support __read_mostly; bool amd_iommu_force_isolation __read_mostly; +unsigned long amd_iommu_pgsize_bitmap __ro_after_init = AMD_IOMMU_PGSIZES; + /* * AMD IOMMU allows up to 2^16 different protection domains. This is a bitmap * to know which ones are already in use. @@ -3492,6 +3494,12 @@ static int __init parse_amd_iommu_options(char *str) amd_iommu_pgtable = AMD_IOMMU_V2; } else if (strncmp(str, "irtcachedis", 11) == 0) { amd_iommu_irtcachedis = true; + } else if (strncmp(str, "nohugepages", 11) == 0) { + pr_info("Restricting V1 page-sizes to 4KiB"); + amd_iommu_pgsize_bitmap = AMD_IOMMU_PGSIZES_4K; + } else if (strncmp(str, "v2_pgsizes_only", 15) == 0) { + pr_info("Restricting V1 page-sizes to 4KiB/2MiB/1GiB"); + amd_iommu_pgsize_bitmap = AMD_IOMMU_PGSIZES_V2; } else { pr_notice("Unknown option - '%s'\n", str); } diff --git a/drivers/iommu/amd/io_pgtable.c b/drivers/iommu/amd/io_pgtable.c index 14f62c420e4a..804b788f3f16 100644 --- a/drivers/iommu/amd/io_pgtable.c +++ b/drivers/iommu/amd/io_pgtable.c @@ -548,7 +548,7 @@ static struct io_pgtable *v1_alloc_pgtable(struct io_pgtable_cfg *cfg, void *coo return NULL; pgtable->mode = PAGE_MODE_3_LEVEL; - cfg->pgsize_bitmap = AMD_IOMMU_PGSIZES; + cfg->pgsize_bitmap = amd_iommu_pgsize_bitmap; cfg->ias = IOMMU_IN_ADDR_BIT_SIZE; cfg->oas = IOMMU_OUT_ADDR_BIT_SIZE; From ef493f4b122d6b14a6de111d1acac1eab1d673b0 Mon Sep 17 00:00:00 2001 From: Kan Liang Date: Mon, 9 Sep 2024 08:58:48 -0700 Subject: [PATCH 491/573] perf/x86/intel: Allow to setup LBR for counting event for BPF The BPF subsystem may capture LBR data on a counting event. However, the current implementation assumes that LBR can/should only be used with sampling events. For instance, retsnoop tool ([0]) makes an extensive use of this functionality and sets up perf event as follows: struct perf_event_attr attr; memset(&attr, 0, sizeof(attr)); attr.size = sizeof(attr); attr.type = PERF_TYPE_HARDWARE; attr.config = PERF_COUNT_HW_CPU_CYCLES; attr.sample_type = PERF_SAMPLE_BRANCH_STACK; attr.branch_sample_type = PERF_SAMPLE_BRANCH_KERNEL; To limit the LBR for a sampling event is to avoid unnecessary branch stack setup for a counting event in the sample read. Because LBR is only read in the sampling event's overflow. Although in most cases LBR is used in sampling, there is no HW limit to bind LBR to the sampling mode. Allow an LBR setup for a counting event unless in the sample read mode. Fixes: 85846b27072d ("perf/x86: Add PERF_X86_EVENT_NEEDS_BRANCH_STACK flag") Closes: https://lore.kernel.org/lkml/20240905180055.1221620-1-andrii@kernel.org/ Reported-by: Andrii Nakryiko Signed-off-by: Kan Liang Signed-off-by: Peter Zijlstra (Intel) Acked-by: Andrii Nakryiko Tested-by: Andrii Nakryiko Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20240909155848.326640-1-kan.liang@linux.intel.com --- arch/x86/events/intel/core.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 9e519d8a810a..d879478db3f5 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -3972,8 +3972,12 @@ static int intel_pmu_hw_config(struct perf_event *event) x86_pmu.pebs_aliases(event); } - if (needs_branch_stack(event) && is_sampling_event(event)) - event->hw.flags |= PERF_X86_EVENT_NEEDS_BRANCH_STACK; + if (needs_branch_stack(event)) { + /* Avoid branch stack setup for counting events in SAMPLE READ */ + if (is_sampling_event(event) || + !(event->attr.sample_type & PERF_SAMPLE_READ)) + event->hw.flags |= PERF_X86_EVENT_NEEDS_BRANCH_STACK; + } if (branch_sample_counters(event)) { struct perf_event *leader, *sibling; From c2af2a45560bd4046c2e109152acde029ed0acc2 Mon Sep 17 00:00:00 2001 From: Jason Andryuk Date: Mon, 9 Sep 2024 22:09:16 -0400 Subject: [PATCH 492/573] fbdev: xen-fbfront: Assign fb_info->device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Probing xen-fbfront faults in video_is_primary_device(). The passed-in struct device is NULL since xen-fbfront doesn't assign it and the memory is kzalloc()-ed. Assign fb_info->device to avoid this. This was exposed by the conversion of fb_is_primary_device() to video_is_primary_device() which dropped a NULL check for struct device. Fixes: f178e96de7f0 ("arch: Remove struct fb_info from video helpers") Reported-by: Arthur Borsboom Closes: https://lore.kernel.org/xen-devel/CALUcmUncX=LkXWeiSiTKsDY-cOe8QksWhFvcCneOKfrKd0ZajA@mail.gmail.com/ Tested-by: Arthur Borsboom CC: stable@vger.kernel.org Signed-off-by: Jason Andryuk Reviewed-by: Roger Pau Monné Reviewed-by: Thomas Zimmermann Signed-off-by: Helge Deller --- drivers/video/fbdev/xen-fbfront.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/video/fbdev/xen-fbfront.c b/drivers/video/fbdev/xen-fbfront.c index 66d4628a96ae..c90f48ebb15e 100644 --- a/drivers/video/fbdev/xen-fbfront.c +++ b/drivers/video/fbdev/xen-fbfront.c @@ -407,6 +407,7 @@ static int xenfb_probe(struct xenbus_device *dev, /* complete the abuse: */ fb_info->pseudo_palette = fb_info->par; fb_info->par = info; + fb_info->device = &dev->dev; fb_info->screen_buffer = info->fb; From 4a6921095eb04a900e0000da83d9475eb958e61e Mon Sep 17 00:00:00 2001 From: Kaixin Wang Date: Wed, 11 Sep 2024 22:29:52 +0800 Subject: [PATCH 493/573] fbdev: pxafb: Fix possible use after free in pxafb_task() In the pxafb_probe function, it calls the pxafb_init_fbinfo function, after which &fbi->task is associated with pxafb_task. Moreover, within this pxafb_init_fbinfo function, the pxafb_blank function within the &pxafb_ops struct is capable of scheduling work. If we remove the module which will call pxafb_remove to make cleanup, it will call unregister_framebuffer function which can call do_unregister_framebuffer to free fbi->fb through put_fb_info(fb_info), while the work mentioned above will be used. The sequence of operations that may lead to a UAF bug is as follows: CPU0 CPU1 | pxafb_task pxafb_remove | unregister_framebuffer(info) | do_unregister_framebuffer(fb_info) | put_fb_info(fb_info) | // free fbi->fb | set_ctrlr_state(fbi, state) | __pxafb_lcd_power(fbi, 0) | fbi->lcd_power(on, &fbi->fb.var) | //use fbi->fb Fix it by ensuring that the work is canceled before proceeding with the cleanup in pxafb_remove. Note that only root user can remove the driver at runtime. Signed-off-by: Kaixin Wang Signed-off-by: Helge Deller --- drivers/video/fbdev/pxafb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/video/fbdev/pxafb.c b/drivers/video/fbdev/pxafb.c index 2ef56fa28aff..5ce02495cda6 100644 --- a/drivers/video/fbdev/pxafb.c +++ b/drivers/video/fbdev/pxafb.c @@ -2403,6 +2403,7 @@ static void pxafb_remove(struct platform_device *dev) info = &fbi->fb; pxafb_overlay_exit(fbi); + cancel_work_sync(&fbi->task); unregister_framebuffer(info); pxafb_disable_controller(fbi); From de5e89b6654ea0b021a5737e0f55fc6bed625550 Mon Sep 17 00:00:00 2001 From: Andrew Kreimer Date: Wed, 11 Sep 2024 21:24:37 +0300 Subject: [PATCH 494/573] fbdev: omapfb: Fix typo in comment Reported-by: Matthew Wilcox Signed-off-by: Andrew Kreimer Signed-off-by: Helge Deller --- drivers/video/fbdev/omap2/omapfb/dss/hdmi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/video/fbdev/omap2/omapfb/dss/hdmi.h b/drivers/video/fbdev/omap2/omapfb/dss/hdmi.h index 9a7253355f6d..cdb1dedca492 100644 --- a/drivers/video/fbdev/omap2/omapfb/dss/hdmi.h +++ b/drivers/video/fbdev/omap2/omapfb/dss/hdmi.h @@ -351,7 +351,7 @@ struct omap_hdmi { bool audio_configured; struct omap_dss_audio audio_config; - /* This lock should be taken when booleans bellow are touched. */ + /* This lock should be taken when booleans below are touched. */ spinlock_t audio_playing_lock; bool audio_playing; bool display_enabled; From 8386207f37e98453e1de3f51e50eeeea089103f9 Mon Sep 17 00:00:00 2001 From: Eliav Bar-ilan Date: Tue, 10 Sep 2024 16:44:16 -0300 Subject: [PATCH 495/573] iommu/amd: Fix argument order in amd_iommu_dev_flush_pasid_all() An incorrect argument order calling amd_iommu_dev_flush_pasid_pages() causes improper flushing of the IOMMU, leaving the old value of GCR3 from a previous process attached to the same PASID. The function has the signature: void amd_iommu_dev_flush_pasid_pages(struct iommu_dev_data *dev_data, ioasid_t pasid, u64 address, size_t size) Correct the argument order. Cc: stable@vger.kernel.org Fixes: 474bf01ed9f0 ("iommu/amd: Add support for device based TLB invalidation") Signed-off-by: Eliav Bar-ilan Signed-off-by: Jason Gunthorpe Reviewed-by: Vasant Hegde Link: https://lore.kernel.org/r/0-v1-fc6bc37d8208+250b-amd_pasid_flush_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/iommu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index b19e8c0f48fa..6bc4030a6ba8 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -1552,8 +1552,8 @@ void amd_iommu_dev_flush_pasid_pages(struct iommu_dev_data *dev_data, void amd_iommu_dev_flush_pasid_all(struct iommu_dev_data *dev_data, ioasid_t pasid) { - amd_iommu_dev_flush_pasid_pages(dev_data, 0, - CMD_INV_IOMMU_ALL_PAGES_ADDRESS, pasid); + amd_iommu_dev_flush_pasid_pages(dev_data, pasid, 0, + CMD_INV_IOMMU_ALL_PAGES_ADDRESS); } void amd_iommu_domain_flush_complete(struct protection_domain *domain) From 3ab9d8d1b50b516507655b10e19d8e624ad1d79c Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Tue, 10 Sep 2024 17:00:34 -0300 Subject: [PATCH 496/573] iommu/amd: Test for PAGING domains before freeing a domain This domain free function can be called for IDENTITY and SVA domains too, and they don't have page tables. For now protect against this by checking the type. Eventually the different types should have their own free functions. Fixes: 485534bfccb2 ("iommu/amd: Remove conditions from domain free paths") Reported-by: Vasant Hegde Signed-off-by: Jason Gunthorpe Reviewed-by: Vasant Hegde Link: https://lore.kernel.org/r/0-v1-ad9884ee5f5b+da-amd_iopgtbl_fix_jgg@nvidia.com Signed-off-by: Joerg Roedel --- drivers/iommu/amd/iommu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index 2876ac42ae2d..f4dfabb3e14d 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -2254,7 +2254,8 @@ static void cleanup_domain(struct protection_domain *domain) void protection_domain_free(struct protection_domain *domain) { WARN_ON(!list_empty(&domain->dev_list)); - free_io_pgtable_ops(&domain->iop.pgtbl.ops); + if (domain->domain.type & __IOMMU_DOMAIN_PAGING) + free_io_pgtable_ops(&domain->iop.pgtbl.ops); domain_id_free(domain->id); kfree(domain); } From 4638a0474d342492060c03f84656ebe9b58a7636 Mon Sep 17 00:00:00 2001 From: Zhenhua Huang Date: Wed, 11 Sep 2024 15:18:54 +0800 Subject: [PATCH 497/573] dt-bindings: arm-smmu: Add compatible for QCS8300 SoC Qualcomm QCS8300 SoC includes apps smmu that implements arm,mmu-500, which is used to translate device-visible virtual addresses to physical addresses. Add compatible for it. Signed-off-by: Zhenhua Huang Signed-off-by: Jingyi Wang Link: https://lore.kernel.org/r/20240911-qcs8300_smmu_binding-v2-1-f53dd9c047ba@quicinc.com Signed-off-by: Will Deacon --- Documentation/devicetree/bindings/iommu/arm,smmu.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml index 3353c2d37841..92d350b8e01a 100644 --- a/Documentation/devicetree/bindings/iommu/arm,smmu.yaml +++ b/Documentation/devicetree/bindings/iommu/arm,smmu.yaml @@ -36,6 +36,7 @@ properties: items: - enum: - qcom,qcm2290-smmu-500 + - qcom,qcs8300-smmu-500 - qcom,qdu1000-smmu-500 - qcom,sa8255p-smmu-500 - qcom,sa8775p-smmu-500 @@ -554,6 +555,7 @@ allOf: - cavium,smmu-v2 - marvell,ap806-smmu-500 - nvidia,smmu-500 + - qcom,qcs8300-smmu-500 - qcom,qdu1000-smmu-500 - qcom,sa8255p-smmu-500 - qcom,sc7180-smmu-500 From 38f9fa39afe119f6b2771c9065e638414226c86d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 11 Sep 2024 22:46:27 +0300 Subject: [PATCH 498/573] hwmon: (sht21) Use %*ph to print small buffer Use %*ph format to print small buffer as hex string. Signed-off-by: Andy Shevchenko Message-ID: <20240911194627.2885506-1-andriy.shevchenko@linux.intel.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/sht21.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/hwmon/sht21.c b/drivers/hwmon/sht21.c index ad1b827ea782..97327313529b 100644 --- a/drivers/hwmon/sht21.c +++ b/drivers/hwmon/sht21.c @@ -199,10 +199,7 @@ static ssize_t eic_read(struct sht21 *sht21) eic[6] = rx[0]; eic[7] = rx[1]; - ret = snprintf(sht21->eic, sizeof(sht21->eic), - "%02x%02x%02x%02x%02x%02x%02x%02x\n", - eic[0], eic[1], eic[2], eic[3], - eic[4], eic[5], eic[6], eic[7]); + ret = snprintf(sht21->eic, sizeof(sht21->eic), "%8phN\n", eic); out: if (ret < 0) sht21->eic[0] = 0; From 3017d28d6c0fe995703a1f34275e1cade424dc35 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 11 Sep 2024 23:19:03 +0300 Subject: [PATCH 499/573] hwmon: (sch5636) Print unknown ID in error string via %*pE Instead of custom approach this allows to print escaped strings via %*pE extension. With this the unknown ID will be printed as a string. Nonetheless, leave hex values to be printed as well. Signed-off-by: Andy Shevchenko Message-ID: <20240911201903.2886874-1-andriy.shevchenko@linux.intel.com> Signed-off-by: Guenter Roeck --- drivers/hwmon/sch5636.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/hwmon/sch5636.c b/drivers/hwmon/sch5636.c index 6e6d54158474..a4b05ebb0546 100644 --- a/drivers/hwmon/sch5636.c +++ b/drivers/hwmon/sch5636.c @@ -416,8 +416,7 @@ static int sch5636_probe(struct platform_device *pdev) id[i] = '\0'; if (strcmp(id, "THS")) { - pr_err("Unknown Fujitsu id: %02x%02x%02x\n", - id[0], id[1], id[2]); + pr_err("Unknown Fujitsu id: %3pE (%3ph)\n", id, id); err = -ENODEV; goto error; } From 4b7ff9ab98af11a477d50f08382bcc4c2f899926 Mon Sep 17 00:00:00 2001 From: Vlastimil Babka Date: Fri, 13 Sep 2024 10:15:56 +0200 Subject: [PATCH 500/573] mm, slab: restore kerneldoc for kmem_cache_create() As kmem_cache_create() became a _Generic() wrapper macro, it currently has no kerneldoc despite being the main API to use. Add it. Also adjust kmem_cache_create_usercopy() kerneldoc to indicate it is now a legacy wrapper. Also expand the kerneldoc for struct kmem_cache_args, especially for the freeptr_offset field, where important details were removed with the removal of kmem_cache_create_rcu(). Signed-off-by: Vlastimil Babka Reviewed-by: Christian Brauner --- include/linux/slab.h | 114 ++++++++++++++++++++++++++++++++++--------- mm/slab_common.c | 10 ++-- 2 files changed, 98 insertions(+), 26 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index 331412a9f4f2..6a8ab7ef3af7 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -242,19 +242,72 @@ bool slab_is_available(void); /** * struct kmem_cache_args - Less common arguments for kmem_cache_create() - * @align: The required alignment for the objects. - * @useroffset: Usercopy region offset - * @usersize: Usercopy region size - * @freeptr_offset: Custom offset for the free pointer in RCU caches - * @use_freeptr_offset: Whether a @freeptr_offset is used - * @ctor: A constructor for the objects. + * + * Any uninitialized fields of the structure are interpreted as unused. The + * exception is @freeptr_offset where %0 is a valid value, so + * @use_freeptr_offset must be also set to %true in order to interpret the field + * as used. For @useroffset %0 is also valid, but only with non-%0 + * @usersize. + * + * When %NULL args is passed to kmem_cache_create(), it is equivalent to all + * fields unused. */ struct kmem_cache_args { + /** + * @align: The required alignment for the objects. + * + * %0 means no specific alignment is requested. + */ unsigned int align; + /** + * @useroffset: Usercopy region offset. + * + * %0 is a valid offset, when @usersize is non-%0 + */ unsigned int useroffset; + /** + * @usersize: Usercopy region size. + * + * %0 means no usercopy region is specified. + */ unsigned int usersize; + /** + * @freeptr_offset: Custom offset for the free pointer + * in &SLAB_TYPESAFE_BY_RCU caches + * + * By default &SLAB_TYPESAFE_BY_RCU caches place the free pointer + * outside of the object. This might cause the object to grow in size. + * Cache creators that have a reason to avoid this can specify a custom + * free pointer offset in their struct where the free pointer will be + * placed. + * + * Note that placing the free pointer inside the object requires the + * caller to ensure that no fields are invalidated that are required to + * guard against object recycling (See &SLAB_TYPESAFE_BY_RCU for + * details). + * + * Using %0 as a value for @freeptr_offset is valid. If @freeptr_offset + * is specified, %use_freeptr_offset must be set %true. + * + * Note that @ctor currently isn't supported with custom free pointers + * as a @ctor requires an external free pointer. + */ unsigned int freeptr_offset; + /** + * @use_freeptr_offset: Whether a @freeptr_offset is used. + */ bool use_freeptr_offset; + /** + * @ctor: A constructor for the objects. + * + * The constructor is invoked for each object in a newly allocated slab + * page. It is the cache user's responsibility to free object in the + * same state as after calling the constructor, or deal appropriately + * with any differences between a freshly constructed and a reallocated + * object. + * + * %NULL means no constructor. + */ void (*ctor)(void *); }; @@ -275,30 +328,20 @@ __kmem_cache_create(const char *name, unsigned int size, unsigned int align, } /** - * kmem_cache_create_usercopy - Create a cache with a region suitable - * for copying to userspace + * kmem_cache_create_usercopy - Create a kmem cache with a region suitable + * for copying to userspace. * @name: A string which is used in /proc/slabinfo to identify this cache. * @size: The size of objects to be created in this cache. * @align: The required alignment for the objects. * @flags: SLAB flags * @useroffset: Usercopy region offset * @usersize: Usercopy region size - * @ctor: A constructor for the objects. + * @ctor: A constructor for the objects, or %NULL. * - * Cannot be called within a interrupt, but can be interrupted. - * The @ctor is run when new pages are allocated by the cache. - * - * The flags are - * - * %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5) - * to catch references to uninitialised memory. - * - * %SLAB_RED_ZONE - Insert `Red` zones around the allocated memory to check - * for buffer overruns. - * - * %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware - * cacheline. This can be beneficial if you're counting cycles as closely - * as davem. + * This is a legacy wrapper, new code should use either KMEM_CACHE_USERCOPY() + * if whitelisting a single field is sufficient, or kmem_cache_create() with + * the necessary parameters passed via the args parameter (see + * &struct kmem_cache_args) * * Return: a pointer to the cache on success, NULL on failure. */ @@ -333,6 +376,31 @@ __kmem_cache_default_args(const char *name, unsigned int size, return __kmem_cache_create_args(name, size, &kmem_default_args, flags); } +/** + * kmem_cache_create - Create a kmem cache. + * @__name: A string which is used in /proc/slabinfo to identify this cache. + * @__object_size: The size of objects to be created in this cache. + * @__args: Optional arguments, see &struct kmem_cache_args. Passing %NULL + * means defaults will be used for all the arguments. + * + * This is currently implemented as a macro using ``_Generic()`` to call + * either the new variant of the function, or a legacy one. + * + * The new variant has 4 parameters: + * ``kmem_cache_create(name, object_size, args, flags)`` + * + * See __kmem_cache_create_args() which implements this. + * + * The legacy variant has 5 parameters: + * ``kmem_cache_create(name, object_size, align, flags, ctor)`` + * + * The align and ctor parameters map to the respective fields of + * &struct kmem_cache_args + * + * Context: Cannot be called within a interrupt, but can be interrupted. + * + * Return: a pointer to the cache on success, NULL on failure. + */ #define kmem_cache_create(__name, __object_size, __args, ...) \ _Generic((__args), \ struct kmem_cache_args *: __kmem_cache_create_args, \ diff --git a/mm/slab_common.c b/mm/slab_common.c index 30000dcf0736..86c2e6f4a1ce 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -239,13 +239,17 @@ out: } /** - * __kmem_cache_create_args - Create a kmem cache + * __kmem_cache_create_args - Create a kmem cache. * @name: A string which is used in /proc/slabinfo to identify this cache. * @object_size: The size of objects to be created in this cache. - * @args: Arguments for the cache creation (see struct kmem_cache_args). + * @args: Additional arguments for the cache creation (see + * &struct kmem_cache_args). * @flags: See %SLAB_* flags for an explanation of individual @flags. * - * Cannot be called within a interrupt, but can be interrupted. + * Not to be called directly, use the kmem_cache_create() wrapper with the same + * parameters. + * + * Context: Cannot be called within a interrupt, but can be interrupted. * * Return: a pointer to the cache on success, NULL on failure. */ From f253f6d922da29d0d7091801cbc9b4166c3959fe Mon Sep 17 00:00:00 2001 From: Zhang Zekun Date: Wed, 21 Aug 2024 11:40:21 +0800 Subject: [PATCH 501/573] pmdomain: qcom-cpr: Use helper function for_each_available_child_of_node() for_each_available_child_of_node() can help to iterate through the device_node, and we don't need to use while loop. Besides, the purpose of the while loop is to find a device_node which fits the condition "child_req_np == ref_np", we can just read the property of "child_np" directly in for_each_available_child_of_node(). No functional change with such conversion. Signed-off-by: Zhang Zekun Link: https://lore.kernel.org/r/20240821034022.27394-2-zhangzekun11@huawei.com Signed-off-by: Ulf Hansson --- drivers/pmdomain/qcom/cpr.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/pmdomain/qcom/cpr.c b/drivers/pmdomain/qcom/cpr.c index c64e84a27cc7..1834b3861232 100644 --- a/drivers/pmdomain/qcom/cpr.c +++ b/drivers/pmdomain/qcom/cpr.c @@ -1054,14 +1054,14 @@ static unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref, if (!ref_np) goto out_ref; - do { + for_each_available_child_of_node(desc_np, child_np) { of_node_put(child_req_np); - child_np = of_get_next_available_child(desc_np, child_np); child_req_np = of_parse_phandle(child_np, "required-opps", 0); - } while (child_np && child_req_np != ref_np); - - if (child_np && child_req_np == ref_np) - of_property_read_u64(child_np, "opp-hz", &rate); + if (child_req_np == ref_np) { + of_property_read_u64(child_np, "opp-hz", &rate); + break; + } + } of_node_put(child_req_np); of_node_put(child_np); From 181c8148556a2a7dd3047ea687873937b1be4f00 Mon Sep 17 00:00:00 2001 From: Zhang Zekun Date: Wed, 21 Aug 2024 11:40:22 +0800 Subject: [PATCH 502/573] pmdomain: qcom-cpr: Use scope based of_node_put() to simplify code. Use scope based of_node_put() to simplify the code logic, and we don't need to call of_node_put(). This will simplify the code a lot. Signed-off-by: Zhang Zekun Link: https://lore.kernel.org/r/20240821034022.27394-3-zhangzekun11@huawei.com Signed-off-by: Ulf Hansson --- drivers/pmdomain/qcom/cpr.c | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/drivers/pmdomain/qcom/cpr.c b/drivers/pmdomain/qcom/cpr.c index 1834b3861232..37b318bf505a 100644 --- a/drivers/pmdomain/qcom/cpr.c +++ b/drivers/pmdomain/qcom/cpr.c @@ -1040,36 +1040,30 @@ static unsigned int cpr_get_fuse_corner(struct dev_pm_opp *opp) static unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref, struct device *cpu_dev) { - u64 rate = 0; - struct device_node *ref_np; - struct device_node *desc_np; - struct device_node *child_np = NULL; - struct device_node *child_req_np = NULL; + struct device_node *ref_np __free(device_node) = NULL; + struct device_node *desc_np __free(device_node) = + dev_pm_opp_of_get_opp_desc_node(cpu_dev); - desc_np = dev_pm_opp_of_get_opp_desc_node(cpu_dev); if (!desc_np) return 0; ref_np = dev_pm_opp_get_of_node(ref); if (!ref_np) - goto out_ref; + return 0; + + for_each_available_child_of_node_scoped(desc_np, child_np) { + struct device_node *child_req_np __free(device_node) = + of_parse_phandle(child_np, "required-opps", 0); - for_each_available_child_of_node(desc_np, child_np) { - of_node_put(child_req_np); - child_req_np = of_parse_phandle(child_np, "required-opps", 0); if (child_req_np == ref_np) { + u64 rate; + of_property_read_u64(child_np, "opp-hz", &rate); - break; + return (unsigned long) rate; } } - of_node_put(child_req_np); - of_node_put(child_np); - of_node_put(ref_np); -out_ref: - of_node_put(desc_np); - - return (unsigned long) rate; + return 0; } static int cpr_corner_init(struct cpr_drv *drv) From 0d946ef4646092a23de2baf7b9d3063fe5571e82 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 23 Aug 2024 14:51:05 +0200 Subject: [PATCH 503/573] pmdomain: rockchip: Simplify with scoped for each OF child loop Use scoped for_each_available_child_of_node_scoped() and for_each_child_of_node_scoped() when iterating over device nodes to make code a bit simpler. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Jonathan Cameron Link: https://lore.kernel.org/r/20240823-cleanup-h-guard-pm-domain-v1-1-8320722eaf39@linaro.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/rockchip/pm-domains.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index 64b4d7120d83..5679ad336a11 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -804,11 +804,10 @@ static void rockchip_configure_pd_cnt(struct rockchip_pmu *pmu, static int rockchip_pm_add_subdomain(struct rockchip_pmu *pmu, struct device_node *parent) { - struct device_node *np; struct generic_pm_domain *child_domain, *parent_domain; int error; - for_each_child_of_node(parent, np) { + for_each_child_of_node_scoped(parent, np) { u32 idx; error = of_property_read_u32(parent, "reg", &idx); @@ -816,7 +815,7 @@ static int rockchip_pm_add_subdomain(struct rockchip_pmu *pmu, dev_err(pmu->dev, "%pOFn: failed to retrieve domain id (reg): %d\n", parent, error); - goto err_out; + return error; } parent_domain = pmu->genpd_data.domains[idx]; @@ -824,7 +823,7 @@ static int rockchip_pm_add_subdomain(struct rockchip_pmu *pmu, if (error) { dev_err(pmu->dev, "failed to handle node %pOFn: %d\n", np, error); - goto err_out; + return error; } error = of_property_read_u32(np, "reg", &idx); @@ -832,7 +831,7 @@ static int rockchip_pm_add_subdomain(struct rockchip_pmu *pmu, dev_err(pmu->dev, "%pOFn: failed to retrieve domain id (reg): %d\n", np, error); - goto err_out; + return error; } child_domain = pmu->genpd_data.domains[idx]; @@ -840,7 +839,7 @@ static int rockchip_pm_add_subdomain(struct rockchip_pmu *pmu, if (error) { dev_err(pmu->dev, "%s failed to add subdomain %s: %d\n", parent_domain->name, child_domain->name, error); - goto err_out; + return error; } else { dev_dbg(pmu->dev, "%s add subdomain: %s\n", parent_domain->name, child_domain->name); @@ -850,17 +849,12 @@ static int rockchip_pm_add_subdomain(struct rockchip_pmu *pmu, } return 0; - -err_out: - of_node_put(np); - return error; } static int rockchip_pm_domain_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; - struct device_node *node; struct device *parent; struct rockchip_pmu *pmu; const struct rockchip_pmu_info *pmu_info; @@ -918,12 +912,11 @@ static int rockchip_pm_domain_probe(struct platform_device *pdev) */ mutex_lock(&dmc_pmu_mutex); - for_each_available_child_of_node(np, node) { + for_each_available_child_of_node_scoped(np, node) { error = rockchip_pm_add_one_domain(pmu, node); if (error) { dev_err(dev, "failed to handle node %pOFn: %d\n", node, error); - of_node_put(node); goto err_out; } @@ -931,7 +924,6 @@ static int rockchip_pm_domain_probe(struct platform_device *pdev) if (error < 0) { dev_err(dev, "failed to handle subdomain node %pOFn: %d\n", node, error); - of_node_put(node); goto err_out; } } From da64dae42672a03eb877ebf21bde847215f5fa29 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 23 Aug 2024 14:51:06 +0200 Subject: [PATCH 504/573] pmdomain: rockchip: Simplify locking with guard() Simplify error handling (smaller error handling) over locks with guard(). Signed-off-by: Krzysztof Kozlowski Reviewed-by: Jonathan Cameron Link: https://lore.kernel.org/r/20240823-cleanup-h-guard-pm-domain-v1-2-8320722eaf39@linaro.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/rockchip/pm-domains.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index 5679ad336a11..538dde58d924 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -910,7 +910,7 @@ static int rockchip_pm_domain_probe(struct platform_device *pdev) * Prevent any rockchip_pmu_block() from racing with the remainder of * setup (clocks, register initialization). */ - mutex_lock(&dmc_pmu_mutex); + guard(mutex)(&dmc_pmu_mutex); for_each_available_child_of_node_scoped(np, node) { error = rockchip_pm_add_one_domain(pmu, node); @@ -943,13 +943,10 @@ static int rockchip_pm_domain_probe(struct platform_device *pdev) if (!WARN_ON_ONCE(dmc_pmu)) dmc_pmu = pmu; - mutex_unlock(&dmc_pmu_mutex); - return 0; err_out: rockchip_pm_domain_cleanup(pmu); - mutex_unlock(&dmc_pmu_mutex); return error; } From 3e4d109ee8fca30b59f5f0382498a9a9ee90f3ea Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 23 Aug 2024 14:51:07 +0200 Subject: [PATCH 505/573] pmdomain: imx: gpc: Simplify with scoped for each OF child loop Use scoped for_each_child_of_node_scoped() when iterating over device nodes to make code a bit simpler. Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240823-cleanup-h-guard-pm-domain-v1-3-8320722eaf39@linaro.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/imx/gpc.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/drivers/pmdomain/imx/gpc.c b/drivers/pmdomain/imx/gpc.c index 9517cce93d8a..80a4dcc77199 100644 --- a/drivers/pmdomain/imx/gpc.c +++ b/drivers/pmdomain/imx/gpc.c @@ -455,7 +455,6 @@ static int imx_gpc_probe(struct platform_device *pdev) } else { struct imx_pm_domain *domain; struct platform_device *pd_pdev; - struct device_node *np; struct clk *ipg_clk; unsigned int ipg_rate_mhz; int domain_index; @@ -465,28 +464,24 @@ static int imx_gpc_probe(struct platform_device *pdev) return PTR_ERR(ipg_clk); ipg_rate_mhz = clk_get_rate(ipg_clk) / 1000000; - for_each_child_of_node(pgc_node, np) { + for_each_child_of_node_scoped(pgc_node, np) { ret = of_property_read_u32(np, "reg", &domain_index); - if (ret) { - of_node_put(np); + if (ret) return ret; - } + if (domain_index >= of_id_data->num_domains) continue; pd_pdev = platform_device_alloc("imx-pgc-power-domain", domain_index); - if (!pd_pdev) { - of_node_put(np); + if (!pd_pdev) return -ENOMEM; - } ret = platform_device_add_data(pd_pdev, &imx_gpc_domains[domain_index], sizeof(imx_gpc_domains[domain_index])); if (ret) { platform_device_put(pd_pdev); - of_node_put(np); return ret; } domain = pd_pdev->dev.platform_data; @@ -500,7 +495,6 @@ static int imx_gpc_probe(struct platform_device *pdev) ret = platform_device_add(pd_pdev); if (ret) { platform_device_put(pd_pdev); - of_node_put(np); return ret; } } From 13bd778c900537f3fff7cfb671ff2eb0e92feee6 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 23 Aug 2024 14:51:08 +0200 Subject: [PATCH 506/573] pmdomain: imx: gpcv2: Simplify with scoped for each OF child loop Use scoped for_each_child_of_node_scoped() when iterating over device nodes to make code a bit simpler. Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240823-cleanup-h-guard-pm-domain-v1-4-8320722eaf39@linaro.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/imx/gpcv2.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/pmdomain/imx/gpcv2.c b/drivers/pmdomain/imx/gpcv2.c index 856eaac0ec14..963d61c5af6d 100644 --- a/drivers/pmdomain/imx/gpcv2.c +++ b/drivers/pmdomain/imx/gpcv2.c @@ -1458,7 +1458,7 @@ static int imx_gpcv2_probe(struct platform_device *pdev) .max_register = SZ_4K, }; struct device *dev = &pdev->dev; - struct device_node *pgc_np, *np; + struct device_node *pgc_np; struct regmap *regmap; void __iomem *base; int ret; @@ -1480,7 +1480,7 @@ static int imx_gpcv2_probe(struct platform_device *pdev) return ret; } - for_each_child_of_node(pgc_np, np) { + for_each_child_of_node_scoped(pgc_np, np) { struct platform_device *pd_pdev; struct imx_pgc_domain *domain; u32 domain_index; @@ -1491,7 +1491,6 @@ static int imx_gpcv2_probe(struct platform_device *pdev) ret = of_property_read_u32(np, "reg", &domain_index); if (ret) { dev_err(dev, "Failed to read 'reg' property\n"); - of_node_put(np); return ret; } @@ -1506,7 +1505,6 @@ static int imx_gpcv2_probe(struct platform_device *pdev) domain_index); if (!pd_pdev) { dev_err(dev, "Failed to allocate platform device\n"); - of_node_put(np); return -ENOMEM; } @@ -1515,7 +1513,6 @@ static int imx_gpcv2_probe(struct platform_device *pdev) sizeof(domain_data->domains[domain_index])); if (ret) { platform_device_put(pd_pdev); - of_node_put(np); return ret; } @@ -1532,7 +1529,6 @@ static int imx_gpcv2_probe(struct platform_device *pdev) ret = platform_device_add(pd_pdev); if (ret) { platform_device_put(pd_pdev); - of_node_put(np); return ret; } } From 584dc41b3d5750a5a57be46c96708eee1092eb30 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 23 Aug 2024 14:51:09 +0200 Subject: [PATCH 507/573] pmdomain: qcom: cpr: Simplify with dev_err_probe() Use dev_err_probe() to make defer code handling simpler. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20240823-cleanup-h-guard-pm-domain-v1-5-8320722eaf39@linaro.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/qcom/cpr.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/pmdomain/qcom/cpr.c b/drivers/pmdomain/qcom/cpr.c index 37b318bf505a..87d1db1a0019 100644 --- a/drivers/pmdomain/qcom/cpr.c +++ b/drivers/pmdomain/qcom/cpr.c @@ -1464,9 +1464,8 @@ static int cpr_pd_attach_dev(struct generic_pm_domain *domain, */ drv->cpu_clk = devm_clk_get(dev, NULL); if (IS_ERR(drv->cpu_clk)) { - ret = PTR_ERR(drv->cpu_clk); - if (ret != -EPROBE_DEFER) - dev_err(drv->dev, "could not get cpu clk: %d\n", ret); + ret = dev_err_probe(drv->dev, PTR_ERR(drv->cpu_clk), + "could not get cpu clk\n"); goto unlock; } drv->attached_cpu_dev = dev; From ba3a65c69bdbff04ea5699597bf491de5cbe82e9 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 23 Aug 2024 14:51:10 +0200 Subject: [PATCH 508/573] pmdomain: qcom: cpr: Simplify locking with guard() Simplify error handling (less gotos) over locks with guard(). Signed-off-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20240823-cleanup-h-guard-pm-domain-v1-6-8320722eaf39@linaro.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/qcom/cpr.c | 57 +++++++++++++++---------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/drivers/pmdomain/qcom/cpr.c b/drivers/pmdomain/qcom/cpr.c index 87d1db1a0019..e1fca65b80be 100644 --- a/drivers/pmdomain/qcom/cpr.c +++ b/drivers/pmdomain/qcom/cpr.c @@ -4,6 +4,7 @@ * Copyright (c) 2019, Linaro Limited */ +#include #include #include #include @@ -747,9 +748,9 @@ static int cpr_set_performance_state(struct generic_pm_domain *domain, struct cpr_drv *drv = container_of(domain, struct cpr_drv, pd); struct corner *corner, *end; enum voltage_change_dir dir; - int ret = 0, new_uV; + int ret, new_uV; - mutex_lock(&drv->lock); + guard(mutex)(&drv->lock); dev_dbg(drv->dev, "%s: setting perf state: %u (prev state: %u)\n", __func__, state, cpr_get_cur_perf_state(drv)); @@ -760,10 +761,8 @@ static int cpr_set_performance_state(struct generic_pm_domain *domain, */ corner = drv->corners + state - 1; end = &drv->corners[drv->num_corners - 1]; - if (corner > end || corner < drv->corners) { - ret = -EINVAL; - goto unlock; - } + if (corner > end || corner < drv->corners) + return -EINVAL; /* Determine direction */ if (drv->corner > corner) @@ -783,7 +782,7 @@ static int cpr_set_performance_state(struct generic_pm_domain *domain, ret = cpr_scale_voltage(drv, corner, new_uV, dir); if (ret) - goto unlock; + return ret; if (cpr_is_allowed(drv)) { cpr_irq_clr(drv); @@ -794,10 +793,7 @@ static int cpr_set_performance_state(struct generic_pm_domain *domain, drv->corner = corner; -unlock: - mutex_unlock(&drv->lock); - - return ret; + return 0; } static int @@ -1437,9 +1433,9 @@ static int cpr_pd_attach_dev(struct generic_pm_domain *domain, { struct cpr_drv *drv = container_of(domain, struct cpr_drv, pd); const struct acc_desc *acc_desc = drv->acc_desc; - int ret = 0; + int ret; - mutex_lock(&drv->lock); + guard(mutex)(&drv->lock); dev_dbg(drv->dev, "attach callback for: %s\n", dev_name(dev)); @@ -1451,7 +1447,7 @@ static int cpr_pd_attach_dev(struct generic_pm_domain *domain, * additional initialization when further CPUs get attached. */ if (drv->attached_cpu_dev) - goto unlock; + return 0; /* * cpr_scale_voltage() requires the direction (if we are changing @@ -1463,11 +1459,10 @@ static int cpr_pd_attach_dev(struct generic_pm_domain *domain, * the first time cpr_set_performance_state() is called. */ drv->cpu_clk = devm_clk_get(dev, NULL); - if (IS_ERR(drv->cpu_clk)) { - ret = dev_err_probe(drv->dev, PTR_ERR(drv->cpu_clk), - "could not get cpu clk\n"); - goto unlock; - } + if (IS_ERR(drv->cpu_clk)) + return dev_err_probe(drv->dev, PTR_ERR(drv->cpu_clk), + "could not get cpu clk\n"); + drv->attached_cpu_dev = dev; dev_dbg(drv->dev, "using cpu clk from: %s\n", @@ -1484,42 +1479,39 @@ static int cpr_pd_attach_dev(struct generic_pm_domain *domain, ret = dev_pm_opp_get_opp_count(&drv->pd.dev); if (ret < 0) { dev_err(drv->dev, "could not get OPP count\n"); - goto unlock; + return ret; } drv->num_corners = ret; if (drv->num_corners < 2) { dev_err(drv->dev, "need at least 2 OPPs to use CPR\n"); - ret = -EINVAL; - goto unlock; + return -EINVAL; } drv->corners = devm_kcalloc(drv->dev, drv->num_corners, sizeof(*drv->corners), GFP_KERNEL); - if (!drv->corners) { - ret = -ENOMEM; - goto unlock; - } + if (!drv->corners) + return -ENOMEM; ret = cpr_corner_init(drv); if (ret) - goto unlock; + return ret; cpr_set_loop_allowed(drv); ret = cpr_init_parameters(drv); if (ret) - goto unlock; + return ret; /* Configure CPR HW but keep it disabled */ ret = cpr_config(drv); if (ret) - goto unlock; + return ret; ret = cpr_find_initial_corner(drv); if (ret) - goto unlock; + return ret; if (acc_desc->config) regmap_multi_reg_write(drv->tcsr, acc_desc->config, @@ -1534,10 +1526,7 @@ static int cpr_pd_attach_dev(struct generic_pm_domain *domain, dev_info(drv->dev, "driver initialized with %u OPPs\n", drv->num_corners); -unlock: - mutex_unlock(&drv->lock); - - return ret; + return 0; } static int cpr_debug_info_show(struct seq_file *s, void *unused) From 005d29ac591a8d3cf0fb6f34c2045d0690992148 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 23 Aug 2024 14:51:11 +0200 Subject: [PATCH 509/573] pmdomain: qcom: rpmhpd: Simplify locking with guard() Simplify error handling (less gotos) over locks with guard(). Signed-off-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20240823-cleanup-h-guard-pm-domain-v1-7-8320722eaf39@linaro.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/qcom/rpmhpd.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/pmdomain/qcom/rpmhpd.c b/drivers/pmdomain/qcom/rpmhpd.c index d2cb4271a1ca..65505e1e2219 100644 --- a/drivers/pmdomain/qcom/rpmhpd.c +++ b/drivers/pmdomain/qcom/rpmhpd.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2018, The Linux Foundation. All rights reserved.*/ +#include #include #include #include @@ -775,9 +776,9 @@ static int rpmhpd_set_performance_state(struct generic_pm_domain *domain, unsigned int level) { struct rpmhpd *pd = domain_to_rpmhpd(domain); - int ret = 0, i; + int ret, i; - mutex_lock(&rpmhpd_lock); + guard(mutex)(&rpmhpd_lock); for (i = 0; i < pd->level_count; i++) if (level <= pd->level[i]) @@ -797,14 +798,12 @@ static int rpmhpd_set_performance_state(struct generic_pm_domain *domain, ret = rpmhpd_aggregate_corner(pd, i); if (ret) - goto out; + return ret; } pd->corner = i; -out: - mutex_unlock(&rpmhpd_lock); - return ret; + return 0; } static int rpmhpd_update_level_mapping(struct rpmhpd *rpmhpd) From f3185222ccce1026cf077baa7fc438c12fa4cf59 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 23 Aug 2024 14:51:12 +0200 Subject: [PATCH 510/573] pmdomain: qcom: rpmpd: Simplify locking with guard() Simplify error handling (less gotos) over locks with guard(). Signed-off-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20240823-cleanup-h-guard-pm-domain-v1-8-8320722eaf39@linaro.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/qcom/rpmpd.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/drivers/pmdomain/qcom/rpmpd.c b/drivers/pmdomain/qcom/rpmpd.c index 5e6280b4cf70..0be6b3026e3a 100644 --- a/drivers/pmdomain/qcom/rpmpd.c +++ b/drivers/pmdomain/qcom/rpmpd.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. */ +#include #include #include #include @@ -1024,20 +1025,17 @@ static int rpmpd_power_on(struct generic_pm_domain *domain) int ret; struct rpmpd *pd = domain_to_rpmpd(domain); - mutex_lock(&rpmpd_lock); + guard(mutex)(&rpmpd_lock); ret = rpmpd_send_enable(pd, true); if (ret) - goto out; + return ret; pd->enabled = true; if (pd->corner) ret = rpmpd_aggregate_corner(pd); -out: - mutex_unlock(&rpmpd_lock); - return ret; } @@ -1060,27 +1058,21 @@ static int rpmpd_power_off(struct generic_pm_domain *domain) static int rpmpd_set_performance(struct generic_pm_domain *domain, unsigned int state) { - int ret = 0; struct rpmpd *pd = domain_to_rpmpd(domain); if (state > pd->max_state) state = pd->max_state; - mutex_lock(&rpmpd_lock); + guard(mutex)(&rpmpd_lock); pd->corner = state; /* Always send updates for vfc and vfl */ if (!pd->enabled && pd->key != cpu_to_le32(KEY_FLOOR_CORNER) && pd->key != cpu_to_le32(KEY_FLOOR_LEVEL)) - goto out; + return 0; - ret = rpmpd_aggregate_corner(pd); - -out: - mutex_unlock(&rpmpd_lock); - - return ret; + return rpmpd_aggregate_corner(pd); } static int rpmpd_probe(struct platform_device *pdev) From 06cee3c6b3844b0ee46dc15ce1bf938eeba2bb28 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Sun, 25 Aug 2024 16:34:00 +0200 Subject: [PATCH 511/573] pmdomain: imx93-pd: replace dev_err() with dev_err_probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This way, the code becomes more compact, and dev_err_probe() is used in every error path of the probe() function. Signed-off-by: Dario Binacchi Acked-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20240825143428.556439-1-dario.binacchi@amarulasolutions.com Signed-off-by: Ulf Hansson --- drivers/pmdomain/imx/imx93-pd.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/pmdomain/imx/imx93-pd.c b/drivers/pmdomain/imx/imx93-pd.c index d750a7dc58d2..44daecbe5cc3 100644 --- a/drivers/pmdomain/imx/imx93-pd.c +++ b/drivers/pmdomain/imx/imx93-pd.c @@ -125,11 +125,10 @@ static int imx93_pd_probe(struct platform_device *pdev) /* Just to sync the status of hardware */ if (!domain->init_off) { ret = clk_bulk_prepare_enable(domain->num_clks, domain->clks); - if (ret) { - dev_err(domain->dev, "failed to enable clocks for domain: %s\n", - domain->genpd.name); - return ret; - } + if (ret) + return dev_err_probe(domain->dev, ret, + "failed to enable clocks for domain: %s\n", + domain->genpd.name); } ret = pm_genpd_init(&domain->genpd, NULL, domain->init_off); From 28717ec8b948eedca5855ac4f587b45bcb1d57e5 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Sun, 25 Aug 2024 16:34:01 +0200 Subject: [PATCH 512/573] pmdomain: imx93-pd: don't unprepare clocks on driver remove The removed code was added to handle the case where the power domain is already on during the driver's probing. In this use case, the "is_off" parameter is passed as false to pm_genpd_init() to inform it not to call the power_on() callback, as it's unnecessary to perform the hardware power-on procedure since the power domain is already on. Therefore, with the call to clk_bulk_prepare_enable() by probe(), the system is in the same operational state as when "is_off" is passed as true after the power_on() callback execution: probe() -> is_off == true -> clk_bulk_prepare_enable() called by power_on() probe() -> is_off == false -> clk_bulk_prepare_enable() called by probe() Reaching the same logical and operational state, it follows that upon driver removal, there is no need to perform different actions depending on the power domain's on/off state during probing. Signed-off-by: Dario Binacchi Link: https://lore.kernel.org/r/20240825143428.556439-2-dario.binacchi@amarulasolutions.com Signed-off-by: Ulf Hansson --- drivers/pmdomain/imx/imx93-pd.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/pmdomain/imx/imx93-pd.c b/drivers/pmdomain/imx/imx93-pd.c index 44daecbe5cc3..fb53a8e359bc 100644 --- a/drivers/pmdomain/imx/imx93-pd.c +++ b/drivers/pmdomain/imx/imx93-pd.c @@ -90,9 +90,6 @@ static void imx93_pd_remove(struct platform_device *pdev) struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; - if (!domain->init_off) - clk_bulk_disable_unprepare(domain->num_clks, domain->clks); - of_genpd_del_provider(np); pm_genpd_remove(&domain->genpd); } From 1a2e369aa2f7a187f0737355ec951bdb1bbc2e84 Mon Sep 17 00:00:00 2001 From: Dario Binacchi Date: Sun, 25 Aug 2024 16:34:02 +0200 Subject: [PATCH 513/573] pmdomain: imx93-pd: drop the context variable "init_off" This variable is only used within the probe() function, so let's remove it from the context and define it locally within the same function. Signed-off-by: Dario Binacchi Link: https://lore.kernel.org/r/20240825143428.556439-3-dario.binacchi@amarulasolutions.com Signed-off-by: Ulf Hansson --- drivers/pmdomain/imx/imx93-pd.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/pmdomain/imx/imx93-pd.c b/drivers/pmdomain/imx/imx93-pd.c index fb53a8e359bc..25ab592945bd 100644 --- a/drivers/pmdomain/imx/imx93-pd.c +++ b/drivers/pmdomain/imx/imx93-pd.c @@ -28,7 +28,6 @@ struct imx93_power_domain { void __iomem *addr; struct clk_bulk_data *clks; int num_clks; - bool init_off; }; #define to_imx93_pd(_genpd) container_of(_genpd, struct imx93_power_domain, genpd) @@ -99,6 +98,7 @@ static int imx93_pd_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct imx93_power_domain *domain; + bool init_off; int ret; domain = devm_kzalloc(dev, sizeof(*domain), GFP_KERNEL); @@ -118,9 +118,9 @@ static int imx93_pd_probe(struct platform_device *pdev) domain->genpd.power_on = imx93_pd_on; domain->dev = dev; - domain->init_off = readl(domain->addr + MIX_FUNC_STAT_OFF) & FUNC_STAT_ISO_STAT_MASK; + init_off = readl(domain->addr + MIX_FUNC_STAT_OFF) & FUNC_STAT_ISO_STAT_MASK; /* Just to sync the status of hardware */ - if (!domain->init_off) { + if (!init_off) { ret = clk_bulk_prepare_enable(domain->num_clks, domain->clks); if (ret) return dev_err_probe(domain->dev, ret, @@ -128,7 +128,7 @@ static int imx93_pd_probe(struct platform_device *pdev) domain->genpd.name); } - ret = pm_genpd_init(&domain->genpd, NULL, domain->init_off); + ret = pm_genpd_init(&domain->genpd, NULL, init_off); if (ret) goto err_clk_unprepare; @@ -144,7 +144,7 @@ err_genpd_remove: pm_genpd_remove(&domain->genpd); err_clk_unprepare: - if (!domain->init_off) + if (!init_off) clk_bulk_disable_unprepare(domain->num_clks, domain->clks); return ret; From 391a2e64d757a0d22a99676930b5aee29f4a4f35 Mon Sep 17 00:00:00 2001 From: Hongbo Li Date: Wed, 28 Aug 2024 20:12:30 +0800 Subject: [PATCH 514/573] pmdomain: mediatek: make use of dev_err_cast_probe() Using dev_err_cast_probe() to simplify the code. Signed-off-by: Hongbo Li Reviewed-by: Matthias Brugger Link: https://lore.kernel.org/r/20240828121230.3696315-1-lihongbo22@huawei.com Signed-off-by: Ulf Hansson --- drivers/pmdomain/mediatek/mtk-pm-domains.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.c b/drivers/pmdomain/mediatek/mtk-pm-domains.c index e274e3315fe7..88406e9ac63c 100644 --- a/drivers/pmdomain/mediatek/mtk-pm-domains.c +++ b/drivers/pmdomain/mediatek/mtk-pm-domains.c @@ -398,12 +398,10 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no scpsys->dev->of_node = node; pd->supply = devm_regulator_get(scpsys->dev, "domain"); scpsys->dev->of_node = root_node; - if (IS_ERR(pd->supply)) { - dev_err_probe(scpsys->dev, PTR_ERR(pd->supply), + if (IS_ERR(pd->supply)) + return dev_err_cast_probe(scpsys->dev, pd->supply, "%pOF: failed to get power supply.\n", node); - return ERR_CAST(pd->supply); - } } pd->infracfg = syscon_regmap_lookup_by_phandle_optional(node, "mediatek,infracfg"); From 4c621d6e667af6a41a0434fed6774abec7857801 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 25 Aug 2024 20:31:16 +0200 Subject: [PATCH 515/573] pmdomain: rockchip: Simplify dropping OF node reference Drop OF node reference immediately after using it in syscon_node_to_regmap(), which is both simpler and typical/expected code pattern. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Heiko Stuebner Link: https://lore.kernel.org/r/20240825183116.102953-1-krzysztof.kozlowski@linaro.org Signed-off-by: Ulf Hansson --- drivers/pmdomain/rockchip/pm-domains.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index 538dde58d924..66e473065b6b 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -716,12 +716,11 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu, goto err_unprepare_clocks; } pd->qos_regmap[j] = syscon_node_to_regmap(qos_node); + of_node_put(qos_node); if (IS_ERR(pd->qos_regmap[j])) { error = -ENODEV; - of_node_put(qos_node); goto err_unprepare_clocks; } - of_node_put(qos_node); } } From 8b579881de295d49a75f6312547f7813b1551a83 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Thu, 29 Aug 2024 16:20:47 -0400 Subject: [PATCH 516/573] pmdomain: rockchip: Add gating support Some rockchip SoC need to ungate power domains before their power status can be changed. Each power domain has a gate mask that is set to 1 to ungate it when manipulating power status, then set back to 0 to gate it again. Signed-off-by: Detlev Casanova Link: https://lore.kernel.org/r/20240829202732.75961-2-detlev.casanova@collabora.com Signed-off-by: Ulf Hansson --- drivers/pmdomain/rockchip/pm-domains.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index 66e473065b6b..b9e3dc45cb30 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -46,6 +46,7 @@ struct rockchip_domain_info { bool active_wakeup; int pwr_w_mask; int req_w_mask; + int clk_ungate_mask; int mem_status_mask; int repair_status_mask; u32 pwr_offset; @@ -63,6 +64,7 @@ struct rockchip_pmu_info { u32 chain_status_offset; u32 mem_status_offset; u32 repair_status_offset; + u32 clk_ungate_offset; u32 core_pwrcnt_offset; u32 gpu_pwrcnt_offset; @@ -303,6 +305,26 @@ static unsigned int rockchip_pmu_read_ack(struct rockchip_pmu *pmu) return val; } +static int rockchip_pmu_ungate_clk(struct rockchip_pm_domain *pd, bool ungate) +{ + const struct rockchip_domain_info *pd_info = pd->info; + struct rockchip_pmu *pmu = pd->pmu; + unsigned int val; + int clk_ungate_w_mask = pd_info->clk_ungate_mask << 16; + + if (!pd_info->clk_ungate_mask) + return 0; + + if (!pmu->info->clk_ungate_offset) + return 0; + + val = ungate ? (pd_info->clk_ungate_mask | clk_ungate_w_mask) : + clk_ungate_w_mask; + regmap_write(pmu->regmap, pmu->info->clk_ungate_offset, val); + + return 0; +} + static int rockchip_pmu_set_idle_request(struct rockchip_pm_domain *pd, bool idle) { @@ -543,6 +565,8 @@ static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on) return ret; } + rockchip_pmu_ungate_clk(pd, true); + if (!power_on) { rockchip_pmu_save_qos(pd); @@ -559,6 +583,7 @@ static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on) rockchip_pmu_restore_qos(pd); } + rockchip_pmu_ungate_clk(pd, false); clk_bulk_disable(pd->num_clks, pd->clks); } From d030e94d8127d79d941a94211250060431720614 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Thu, 29 Aug 2024 16:20:48 -0400 Subject: [PATCH 517/573] pmdomain: rockchip: Add gating masks for rk3576 The RK3576 SoC needs to ungate the power domains before their status can be modified. The values have been taken from the rockchip downstream driver. Signed-off-by: Detlev Casanova Link: https://lore.kernel.org/r/20240829202732.75961-3-detlev.casanova@collabora.com Signed-off-by: Ulf Hansson --- drivers/pmdomain/rockchip/pm-domains.c | 62 +++++++++++++++++--------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index b9e3dc45cb30..cb0f93800138 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -147,6 +147,25 @@ struct rockchip_pmu { .active_wakeup = wakeup, \ } +#define DOMAIN_M_O_R_G(_name, p_offset, pwr, status, m_offset, m_status, r_status, r_offset, req, idle, ack, g_mask, wakeup) \ +{ \ + .name = _name, \ + .pwr_offset = p_offset, \ + .pwr_w_mask = (pwr) << 16, \ + .pwr_mask = (pwr), \ + .status_mask = (status), \ + .mem_offset = m_offset, \ + .mem_status_mask = (m_status), \ + .repair_status_mask = (r_status), \ + .req_offset = r_offset, \ + .req_w_mask = (req) << 16, \ + .req_mask = (req), \ + .idle_mask = (idle), \ + .clk_ungate_mask = (g_mask), \ + .ack_mask = (ack), \ + .active_wakeup = wakeup, \ +} + #define DOMAIN_RK3036(_name, req, ack, idle, wakeup) \ { \ .name = _name, \ @@ -178,8 +197,8 @@ struct rockchip_pmu { #define DOMAIN_RK3568(name, pwr, req, wakeup) \ DOMAIN_M(name, pwr, pwr, req, req, req, wakeup) -#define DOMAIN_RK3576(name, p_offset, pwr, status, r_status, r_offset, req, idle, wakeup) \ - DOMAIN_M_O_R(name, p_offset, pwr, status, 0, r_status, r_status, r_offset, req, idle, idle, wakeup) +#define DOMAIN_RK3576(name, p_offset, pwr, status, r_status, r_offset, req, idle, g_mask, wakeup) \ + DOMAIN_M_O_R_G(name, p_offset, pwr, status, 0, r_status, r_status, r_offset, req, idle, idle, g_mask, wakeup) /* * Dynamic Memory Controller may need to coordinate with us -- see @@ -1124,25 +1143,25 @@ static const struct rockchip_domain_info rk3568_pm_domains[] = { }; static const struct rockchip_domain_info rk3576_pm_domains[] = { - [RK3576_PD_NPU] = DOMAIN_RK3576("npu", 0x0, BIT(0), BIT(0), 0, 0x0, 0, 0, false), - [RK3576_PD_NVM] = DOMAIN_RK3576("nvm", 0x0, BIT(6), 0, BIT(6), 0x4, BIT(2), BIT(18), false), - [RK3576_PD_SDGMAC] = DOMAIN_RK3576("sdgmac", 0x0, BIT(7), 0, BIT(7), 0x4, BIT(1), BIT(17), false), - [RK3576_PD_AUDIO] = DOMAIN_RK3576("audio", 0x0, BIT(8), 0, BIT(8), 0x4, BIT(0), BIT(16), false), - [RK3576_PD_PHP] = DOMAIN_RK3576("php", 0x0, BIT(9), 0, BIT(9), 0x0, BIT(15), BIT(15), false), - [RK3576_PD_SUBPHP] = DOMAIN_RK3576("subphp", 0x0, BIT(10), 0, BIT(10), 0x0, 0, 0, false), - [RK3576_PD_VOP] = DOMAIN_RK3576("vop", 0x0, BIT(11), 0, BIT(11), 0x0, 0x6000, 0x6000, false), - [RK3576_PD_VO1] = DOMAIN_RK3576("vo1", 0x0, BIT(14), 0, BIT(14), 0x0, BIT(12), BIT(12), false), - [RK3576_PD_VO0] = DOMAIN_RK3576("vo0", 0x0, BIT(15), 0, BIT(15), 0x0, BIT(11), BIT(11), false), - [RK3576_PD_USB] = DOMAIN_RK3576("usb", 0x4, BIT(0), 0, BIT(16), 0x0, BIT(10), BIT(10), true), - [RK3576_PD_VI] = DOMAIN_RK3576("vi", 0x4, BIT(1), 0, BIT(17), 0x0, BIT(9), BIT(9), false), - [RK3576_PD_VEPU0] = DOMAIN_RK3576("vepu0", 0x4, BIT(2), 0, BIT(18), 0x0, BIT(7), BIT(7), false), - [RK3576_PD_VEPU1] = DOMAIN_RK3576("vepu1", 0x4, BIT(3), 0, BIT(19), 0x0, BIT(8), BIT(8), false), - [RK3576_PD_VDEC] = DOMAIN_RK3576("vdec", 0x4, BIT(4), 0, BIT(20), 0x0, BIT(6), BIT(6), false), - [RK3576_PD_VPU] = DOMAIN_RK3576("vpu", 0x4, BIT(5), 0, BIT(21), 0x0, BIT(5), BIT(5), false), - [RK3576_PD_NPUTOP] = DOMAIN_RK3576("nputop", 0x4, BIT(6), 0, BIT(22), 0x0, 0x18, 0x18, false), - [RK3576_PD_NPU0] = DOMAIN_RK3576("npu0", 0x4, BIT(7), 0, BIT(23), 0x0, BIT(1), BIT(1), false), - [RK3576_PD_NPU1] = DOMAIN_RK3576("npu1", 0x4, BIT(8), 0, BIT(24), 0x0, BIT(2), BIT(2), false), - [RK3576_PD_GPU] = DOMAIN_RK3576("gpu", 0x4, BIT(9), 0, BIT(25), 0x0, BIT(0), BIT(0), false), + [RK3576_PD_NPU] = DOMAIN_RK3576("npu", 0x0, BIT(0), BIT(0), 0, 0x0, 0, 0, 0, false), + [RK3576_PD_NVM] = DOMAIN_RK3576("nvm", 0x0, BIT(6), 0, BIT(6), 0x4, BIT(2), BIT(18), BIT(2), false), + [RK3576_PD_SDGMAC] = DOMAIN_RK3576("sdgmac", 0x0, BIT(7), 0, BIT(7), 0x4, BIT(1), BIT(17), 0x6, false), + [RK3576_PD_AUDIO] = DOMAIN_RK3576("audio", 0x0, BIT(8), 0, BIT(8), 0x4, BIT(0), BIT(16), BIT(0), false), + [RK3576_PD_PHP] = DOMAIN_RK3576("php", 0x0, BIT(9), 0, BIT(9), 0x0, BIT(15), BIT(15), BIT(15), false), + [RK3576_PD_SUBPHP] = DOMAIN_RK3576("subphp", 0x0, BIT(10), 0, BIT(10), 0x0, 0, 0, 0, false), + [RK3576_PD_VOP] = DOMAIN_RK3576("vop", 0x0, BIT(11), 0, BIT(11), 0x0, 0x6000, 0x6000, 0x6000, false), + [RK3576_PD_VO1] = DOMAIN_RK3576("vo1", 0x0, BIT(14), 0, BIT(14), 0x0, BIT(12), BIT(12), 0x7000, false), + [RK3576_PD_VO0] = DOMAIN_RK3576("vo0", 0x0, BIT(15), 0, BIT(15), 0x0, BIT(11), BIT(11), 0x6800, false), + [RK3576_PD_USB] = DOMAIN_RK3576("usb", 0x4, BIT(0), 0, BIT(16), 0x0, BIT(10), BIT(10), 0x6400, true), + [RK3576_PD_VI] = DOMAIN_RK3576("vi", 0x4, BIT(1), 0, BIT(17), 0x0, BIT(9), BIT(9), BIT(9), false), + [RK3576_PD_VEPU0] = DOMAIN_RK3576("vepu0", 0x4, BIT(2), 0, BIT(18), 0x0, BIT(7), BIT(7), 0x280, false), + [RK3576_PD_VEPU1] = DOMAIN_RK3576("vepu1", 0x4, BIT(3), 0, BIT(19), 0x0, BIT(8), BIT(8), BIT(8), false), + [RK3576_PD_VDEC] = DOMAIN_RK3576("vdec", 0x4, BIT(4), 0, BIT(20), 0x0, BIT(6), BIT(6), BIT(6), false), + [RK3576_PD_VPU] = DOMAIN_RK3576("vpu", 0x4, BIT(5), 0, BIT(21), 0x0, BIT(5), BIT(5), BIT(5), false), + [RK3576_PD_NPUTOP] = DOMAIN_RK3576("nputop", 0x4, BIT(6), 0, BIT(22), 0x0, 0x18, 0x18, 0x18, false), + [RK3576_PD_NPU0] = DOMAIN_RK3576("npu0", 0x4, BIT(7), 0, BIT(23), 0x0, BIT(1), BIT(1), 0x1a, false), + [RK3576_PD_NPU1] = DOMAIN_RK3576("npu1", 0x4, BIT(8), 0, BIT(24), 0x0, BIT(2), BIT(2), 0x1c, false), + [RK3576_PD_GPU] = DOMAIN_RK3576("gpu", 0x4, BIT(9), 0, BIT(25), 0x0, BIT(0), BIT(0), BIT(0), false), }; static const struct rockchip_domain_info rk3588_pm_domains[] = { @@ -1333,6 +1352,7 @@ static const struct rockchip_pmu_info rk3576_pmu = { .idle_offset = 0x128, .ack_offset = 0x120, .repair_status_offset = 0x570, + .clk_ungate_offset = 0x140, .num_domains = ARRAY_SIZE(rk3576_pm_domains), .domain_info = rk3576_pm_domains, From 692c20c4d075bd452acfbbc68200fc226c7c9496 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 4 Sep 2024 16:30:45 +0200 Subject: [PATCH 518/573] pmdomain: core: Harden inter-column space in debug summary The inter-column space in the debug summary is two spaces. However, in one case, the extra space is handled implicitly in a field width specifier. Make inter-column space explicit to ease future maintenance. Fixes: 45fbc464b047 ("PM: domains: Add "performance" column to debug summary") Signed-off-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/ae61eb363621b981edde878e1e74d701702a579f.1725459707.git.geert+renesas@glider.be Signed-off-by: Ulf Hansson --- drivers/pmdomain/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index 2731b285e017..fa9d538045fb 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -3271,7 +3271,7 @@ static int genpd_summary_one(struct seq_file *s, else snprintf(state, sizeof(state), "%s", status_lookup[genpd->status]); - seq_printf(s, "%-30s %-50s %u", genpd->name, state, genpd->performance_state); + seq_printf(s, "%-30s %-49s %u", genpd->name, state, genpd->performance_state); /* * Modifications on the list require holding locks on both From 987a43e89ec67cc68518c0558db42ba542581597 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 4 Sep 2024 16:30:46 +0200 Subject: [PATCH 519/573] pmdomain: core: Fix "managed by" alignment in debug summary The "performance" column contains variable-width values. Hence when their printed values contain more than one digit, all values in successive columns become misaligned. Fix this by formatting it as a fixed-width field. Adjust successive spaces and field widths to retain the exiting layout. Fixes: 0155aaf95a2a ("PM: domains: Add the domain HW-managed mode to the summary") Signed-off-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/e004f9d2a75e9a49c269507bb8a4514001751e85.1725459707.git.geert+renesas@glider.be Signed-off-by: Ulf Hansson --- drivers/pmdomain/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index fa9d538045fb..5e6aa8747303 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -3236,7 +3236,7 @@ static void mode_status_str(struct seq_file *s, struct device *dev) gpd_data = to_gpd_data(dev->power.subsys_data->domain_data); - seq_printf(s, "%20s", gpd_data->hw_mode ? "HW" : "SW"); + seq_printf(s, "%9s", gpd_data->hw_mode ? "HW" : "SW"); } static void perf_status_str(struct seq_file *s, struct device *dev) @@ -3244,7 +3244,7 @@ static void perf_status_str(struct seq_file *s, struct device *dev) struct generic_pm_domain_data *gpd_data; gpd_data = to_gpd_data(dev->power.subsys_data->domain_data); - seq_put_decimal_ull(s, "", gpd_data->performance_state); + seq_printf(s, "%-10u ", gpd_data->performance_state); } static int genpd_summary_one(struct seq_file *s, From 2fc934190e7118f7c7ddd748302df44bde1015f6 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 4 Sep 2024 16:30:47 +0200 Subject: [PATCH 520/573] pmdomain: core: Move mode_status_str() Move mode_status_str() below perf_status_str(), to make declaration order match calling order of the various *_status_str() helpers. While at it, add a blank line for consistency among the three helpers. Signed-off-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/18ed6fb2bb92860f3af1bc7e5e4a01e9dacf2126.1725459707.git.geert+renesas@glider.be Signed-off-by: Ulf Hansson --- drivers/pmdomain/core.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index 5e6aa8747303..c2d2aba4c3e6 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -3230,6 +3230,15 @@ static void rtpm_status_str(struct seq_file *s, struct device *dev) seq_printf(s, "%-25s ", p); } +static void perf_status_str(struct seq_file *s, struct device *dev) +{ + struct generic_pm_domain_data *gpd_data; + + gpd_data = to_gpd_data(dev->power.subsys_data->domain_data); + + seq_printf(s, "%-10u ", gpd_data->performance_state); +} + static void mode_status_str(struct seq_file *s, struct device *dev) { struct generic_pm_domain_data *gpd_data; @@ -3239,14 +3248,6 @@ static void mode_status_str(struct seq_file *s, struct device *dev) seq_printf(s, "%9s", gpd_data->hw_mode ? "HW" : "SW"); } -static void perf_status_str(struct seq_file *s, struct device *dev) -{ - struct generic_pm_domain_data *gpd_data; - - gpd_data = to_gpd_data(dev->power.subsys_data->domain_data); - seq_printf(s, "%-10u ", gpd_data->performance_state); -} - static int genpd_summary_one(struct seq_file *s, struct generic_pm_domain *genpd) { From c6ccb691d484544636bc4a097574c5c135ccccda Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 4 Sep 2024 16:30:48 +0200 Subject: [PATCH 521/573] pmdomain: core: Reduce debug summary table width Commit 9094e53ff5c86ebe ("pmdomain: core: Use dev_name() instead of kobject_get_path() in debugfs") severely shortened the names of devices in a PM Domain. Now the most common format[1] consists of a 32-bit unit-address (8 characters), followed by a dot and a node name (20 characters for "air-pollution-sensor" and "interrupt-controller", which are the longest generic node names documented in the Devicetree Specification), for a typical maximum of 29 characters. This offers a good opportunity to reduce the table width of the debug summary: - Reduce the device name field width from 50 to 30 characters, which matches the PM Domain name width, - Reduce the large inter-column space between the "performance" and "managed by" columns. Visual impact: - The "performance" column now starts at a position that is a multiple of 16, just like the "status" and "children" columns, - All of the "/device", "runtime status", and "managed by" columns are now indented 4 characters more than the columns right above them, - Everything fits in (one less than) 80 characters again ;-) [1] Note that some device names (e.g. TI AM335x interconnect target modules) do not follow this convention, and may be much longer, but these didn't fit in the old 50-character column width either. Signed-off-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/f8e1821364b6d5d11350447c128f6d2b470f33fe.1725459707.git.geert+renesas@glider.be Signed-off-by: Ulf Hansson --- drivers/pmdomain/core.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index c2d2aba4c3e6..5ede0f7eda09 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -3227,7 +3227,7 @@ static void rtpm_status_str(struct seq_file *s, struct device *dev) else WARN_ON(1); - seq_printf(s, "%-25s ", p); + seq_printf(s, "%-26s ", p); } static void perf_status_str(struct seq_file *s, struct device *dev) @@ -3245,7 +3245,7 @@ static void mode_status_str(struct seq_file *s, struct device *dev) gpd_data = to_gpd_data(dev->power.subsys_data->domain_data); - seq_printf(s, "%9s", gpd_data->hw_mode ? "HW" : "SW"); + seq_printf(s, "%2s", gpd_data->hw_mode ? "HW" : "SW"); } static int genpd_summary_one(struct seq_file *s, @@ -3272,7 +3272,7 @@ static int genpd_summary_one(struct seq_file *s, else snprintf(state, sizeof(state), "%s", status_lookup[genpd->status]); - seq_printf(s, "%-30s %-49s %u", genpd->name, state, genpd->performance_state); + seq_printf(s, "%-30s %-30s %u", genpd->name, state, genpd->performance_state); /* * Modifications on the list require holding locks on both @@ -3288,7 +3288,7 @@ static int genpd_summary_one(struct seq_file *s, } list_for_each_entry(pm_data, &genpd->dev_list, list_node) { - seq_printf(s, "\n %-50s ", dev_name(pm_data->dev)); + seq_printf(s, "\n %-30s ", dev_name(pm_data->dev)); rtpm_status_str(s, pm_data->dev); perf_status_str(s, pm_data->dev); mode_status_str(s, pm_data->dev); @@ -3306,9 +3306,9 @@ static int summary_show(struct seq_file *s, void *data) struct generic_pm_domain *genpd; int ret = 0; - seq_puts(s, "domain status children performance\n"); - seq_puts(s, " /device runtime status managed by\n"); - seq_puts(s, "------------------------------------------------------------------------------------------------------------\n"); + seq_puts(s, "domain status children performance\n"); + seq_puts(s, " /device runtime status managed by\n"); + seq_puts(s, "------------------------------------------------------------------------------\n"); ret = mutex_lock_interruptible(&gpd_list_lock); if (ret) From cd3689b6772fbc1a4513934a5204fd2fa5b4426b Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Wed, 11 Sep 2024 11:09:09 +0200 Subject: [PATCH 522/573] mmc: core: Use dev_err_probe for deferred regulators In case vmmc or vqmmc regulator is not available yet, use dev_err_probe in order to set a deferred probe reason. This is a helpful hint in /sys/kernel/debug/devices_deferred Signed-off-by: Alexander Stein Link: https://lore.kernel.org/r/20240911090910.3060749-1-alexander.stein@ew.tq-group.com Signed-off-by: Ulf Hansson --- drivers/mmc/core/regulator.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/regulator.c b/drivers/mmc/core/regulator.c index 005247a49e51..01747ab1024e 100644 --- a/drivers/mmc/core/regulator.c +++ b/drivers/mmc/core/regulator.c @@ -255,7 +255,9 @@ int mmc_regulator_get_supply(struct mmc_host *mmc) if (IS_ERR(mmc->supply.vmmc)) { if (PTR_ERR(mmc->supply.vmmc) == -EPROBE_DEFER) - return -EPROBE_DEFER; + return dev_err_probe(dev, -EPROBE_DEFER, + "vmmc regulator not available\n"); + dev_dbg(dev, "No vmmc regulator found\n"); } else { ret = mmc_regulator_get_ocrmask(mmc->supply.vmmc); @@ -267,7 +269,9 @@ int mmc_regulator_get_supply(struct mmc_host *mmc) if (IS_ERR(mmc->supply.vqmmc)) { if (PTR_ERR(mmc->supply.vqmmc) == -EPROBE_DEFER) - return -EPROBE_DEFER; + return dev_err_probe(dev, -EPROBE_DEFER, + "vqmmc regulator not available\n"); + dev_dbg(dev, "No vqmmc regulator found\n"); } From 2cb4acf2140be8a4f299c0b887cc314845ef6ec8 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 13 Sep 2024 07:11:42 -0700 Subject: [PATCH 523/573] hwmon: Remove devm_hwmon_device_unregister() API function devm_hwmon_device_unregister() has no in-tree user, and its implementation is wrong since it does not pass the to-be-removed hardware monitoring device as parameter. I do not envision a valid use for it; drivers needing it should not have called devm_hwmon_device_register_with_info() in the first place. Remove it. Reported-by: Matthew Sanders Closes: https://lore.kernel.org/linux-hwmon/488b3bdf870ea76c4b943dbe5fd15ac8113019dc.camel@kernel.org/ Signed-off-by: Guenter Roeck --- Documentation/hwmon/hwmon-kernel-api.rst | 7 ------- drivers/hwmon/hwmon.c | 18 ------------------ include/linux/hwmon.h | 1 - 3 files changed, 26 deletions(-) diff --git a/Documentation/hwmon/hwmon-kernel-api.rst b/Documentation/hwmon/hwmon-kernel-api.rst index 6cacf7daf25c..8297acfa3a2d 100644 --- a/Documentation/hwmon/hwmon-kernel-api.rst +++ b/Documentation/hwmon/hwmon-kernel-api.rst @@ -38,8 +38,6 @@ register/unregister functions:: void hwmon_device_unregister(struct device *dev); - void devm_hwmon_device_unregister(struct device *dev); - char *hwmon_sanitize_name(const char *name); char *devm_hwmon_sanitize_name(struct device *dev, const char *name); @@ -64,11 +62,6 @@ monitoring device structure. This function must be called from the driver remove function if the hardware monitoring device was registered with hwmon_device_register_with_info. -devm_hwmon_device_unregister does not normally have to be called. It is only -needed for error handling, and only needed if the driver probe fails after -the call to devm_hwmon_device_register_with_info and if the automatic (device -managed) removal would be too late. - All supported hwmon device registration functions only accept valid device names. Device names including invalid characters (whitespace, '*', or '-') will be rejected. The 'name' parameter is mandatory. diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index a362080d41fa..9c35c4d0369d 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -1188,24 +1188,6 @@ error: } EXPORT_SYMBOL_GPL(devm_hwmon_device_register_with_info); -static int devm_hwmon_match(struct device *dev, void *res, void *data) -{ - struct device **hwdev = res; - - return *hwdev == data; -} - -/** - * devm_hwmon_device_unregister - removes a previously registered hwmon device - * - * @dev: the parent device of the device to unregister - */ -void devm_hwmon_device_unregister(struct device *dev) -{ - WARN_ON(devres_release(dev, devm_hwmon_release, devm_hwmon_match, dev)); -} -EXPORT_SYMBOL_GPL(devm_hwmon_device_unregister); - static char *__hwmon_sanitize_name(struct device *dev, const char *old_name) { char *name, *p; diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index e94314760aab..5c6a421ad580 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -481,7 +481,6 @@ devm_hwmon_device_register_with_info(struct device *dev, const struct attribute_group **extra_groups); void hwmon_device_unregister(struct device *dev); -void devm_hwmon_device_unregister(struct device *dev); int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel); From 2aec90036dcd2cb7047a6e28625fba6c64756665 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Thu, 5 Sep 2024 19:17:24 +0200 Subject: [PATCH 524/573] selftests: vDSO: ensure vgetrandom works in a time namespace After verifying that vDSO getrandom does work, which ensures that the RNG is initialized, test to see if it also works inside of a time namespace. This is important to test, because the vvar pages get swizzled around there. If the arch code isn't careful, the RNG will appear uninitialized inside of a time namespace. Because broken code makes the RNG appear uninitialized, test that everything works by issuing a call to vgetrandom from a fork in a time namespace, and use ptrace to ensure that the actual syscall getrandom doesn't get called. If it doesn't get called, then the test succeeds. Signed-off-by: Jason A. Donenfeld --- .../selftests/vDSO/vdso_test_getrandom.c | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/vDSO/vdso_test_getrandom.c b/tools/testing/selftests/vDSO/vdso_test_getrandom.c index 8866b65a4605..72a1d9b43a84 100644 --- a/tools/testing/selftests/vDSO/vdso_test_getrandom.c +++ b/tools/testing/selftests/vDSO/vdso_test_getrandom.c @@ -16,8 +16,12 @@ #include #include #include +#include +#include #include #include +#include +#include #include "../kselftest.h" #include "parse_vdso.h" @@ -239,9 +243,10 @@ static void fill(void) static void kselftest(void) { uint8_t weird_size[1263]; + pid_t child; ksft_print_header(); - ksft_set_plan(1); + ksft_set_plan(2); for (size_t i = 0; i < 1000; ++i) { ssize_t ret = vgetrandom(weird_size, sizeof(weird_size), 0); @@ -250,6 +255,42 @@ static void kselftest(void) } ksft_test_result_pass("getrandom: PASS\n"); + + unshare(CLONE_NEWUSER); + assert(unshare(CLONE_NEWTIME) == 0); + child = fork(); + assert(child >= 0); + if (!child) { + vgetrandom_init(); + child = getpid(); + assert(ptrace(PTRACE_TRACEME, 0, NULL, NULL) == 0); + assert(kill(child, SIGSTOP) == 0); + assert(vgetrandom(weird_size, sizeof(weird_size), 0) == sizeof(weird_size)); + _exit(0); + } + for (;;) { + struct ptrace_syscall_info info = { 0 }; + int status, ret; + assert(waitpid(child, &status, 0) >= 0); + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) + exit(KSFT_FAIL); + break; + } + assert(WIFSTOPPED(status)); + if (WSTOPSIG(status) == SIGSTOP) + assert(ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD) == 0); + else if (WSTOPSIG(status) == (SIGTRAP | 0x80)) { + assert(ptrace(PTRACE_GET_SYSCALL_INFO, child, sizeof(info), &info) > 0); + if (info.op == PTRACE_SYSCALL_INFO_ENTRY && info.entry.nr == __NR_getrandom && + info.entry.args[0] == (uintptr_t)weird_size && info.entry.args[1] == sizeof(weird_size)) + exit(KSFT_FAIL); + } + assert(ptrace(PTRACE_SYSCALL, child, 0, 0) == 0); + } + + ksft_test_result_pass("getrandom timens: PASS\n"); + exit(KSFT_PASS); } From 6fd13b282f62bd840f2410692deaa23a42fd91fa Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Tue, 27 Aug 2024 17:14:18 +0200 Subject: [PATCH 525/573] random: vDSO: move prototype of arch chacha function to vdso/getrandom.h Having the prototype for __arch_chacha20_blocks_nostack in arch/x86/include/asm/vdso/getrandom.h meant that the prototype and large doc comment were cloned by every architecture, which has been causing unnecessary churn. Instead move it into include/vdso/getrandom.h, where it can be shared by all archs implementing it. As a side bonus, this then lets us use that prototype in the vdso_test_chacha self test, to ensure that it matches the source, and indeed doing so turned up some inconsistencies, which are rectified here. Suggested-by: Christophe Leroy Signed-off-by: Jason A. Donenfeld --- arch/x86/include/asm/vdso/getrandom.h | 13 ------------- include/vdso/getrandom.h | 13 +++++++++++++ tools/testing/selftests/vDSO/vdso_test_chacha.c | 10 +++++++--- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/arch/x86/include/asm/vdso/getrandom.h b/arch/x86/include/asm/vdso/getrandom.h index b96e674cafde..ff5334ad32a0 100644 --- a/arch/x86/include/asm/vdso/getrandom.h +++ b/arch/x86/include/asm/vdso/getrandom.h @@ -37,19 +37,6 @@ static __always_inline const struct vdso_rng_data *__arch_get_vdso_rng_data(void return &__vdso_rng_data; } -/** - * __arch_chacha20_blocks_nostack - Generate ChaCha20 stream without using the stack. - * @dst_bytes: Destination buffer to hold @nblocks * 64 bytes of output. - * @key: 32-byte input key. - * @counter: 8-byte counter, read on input and updated on return. - * @nblocks: Number of blocks to generate. - * - * Generates a given positive number of blocks of ChaCha20 output with nonce=0, and does not write - * to any stack or memory outside of the parameters passed to it, in order to mitigate stack data - * leaking into forked child processes. - */ -extern void __arch_chacha20_blocks_nostack(u8 *dst_bytes, const u32 *key, u32 *counter, size_t nblocks); - #endif /* !__ASSEMBLY__ */ #endif /* __ASM_VDSO_GETRANDOM_H */ diff --git a/include/vdso/getrandom.h b/include/vdso/getrandom.h index a8b7c14b0ae0..4cf02e678f5e 100644 --- a/include/vdso/getrandom.h +++ b/include/vdso/getrandom.h @@ -43,4 +43,17 @@ struct vgetrandom_state { bool in_use; }; +/** + * __arch_chacha20_blocks_nostack - Generate ChaCha20 stream without using the stack. + * @dst_bytes: Destination buffer to hold @nblocks * 64 bytes of output. + * @key: 32-byte input key. + * @counter: 8-byte counter, read on input and updated on return. + * @nblocks: Number of blocks to generate. + * + * Generates a given positive number of blocks of ChaCha20 output with nonce=0, and does not write + * to any stack or memory outside of the parameters passed to it, in order to mitigate stack data + * leaking into forked child processes. + */ +extern void __arch_chacha20_blocks_nostack(u8 *dst_bytes, const u32 *key, u32 *counter, size_t nblocks); + #endif /* _VDSO_GETRANDOM_H */ diff --git a/tools/testing/selftests/vDSO/vdso_test_chacha.c b/tools/testing/selftests/vDSO/vdso_test_chacha.c index e38f44e5f803..ca5639d02969 100644 --- a/tools/testing/selftests/vDSO/vdso_test_chacha.c +++ b/tools/testing/selftests/vDSO/vdso_test_chacha.c @@ -7,16 +7,20 @@ #include #include #include +#include #include "../kselftest.h" -extern void __arch_chacha20_blocks_nostack(uint8_t *dst_bytes, const uint8_t *key, uint32_t *counter, size_t nblocks); +typedef uint8_t u8; +typedef uint32_t u32; +typedef uint64_t u64; +#include int main(int argc, char *argv[]) { enum { TRIALS = 1000, BLOCKS = 128, BLOCK_SIZE = 64 }; static const uint8_t nonce[8] = { 0 }; uint32_t counter[2]; - uint8_t key[32]; + uint32_t key[8]; uint8_t output1[BLOCK_SIZE * BLOCKS], output2[BLOCK_SIZE * BLOCKS]; ksft_print_header(); @@ -27,7 +31,7 @@ int main(int argc, char *argv[]) printf("getrandom() failed!\n"); return KSFT_SKIP; } - crypto_stream_chacha20(output1, sizeof(output1), nonce, key); + crypto_stream_chacha20(output1, sizeof(output1), nonce, (uint8_t *)key); for (unsigned int split = 0; split < BLOCKS; ++split) { memset(output2, 'X', sizeof(output2)); memset(counter, 0, sizeof(counter)); From 7fe5b3e4e7a61f3640d7645c434ac8c982f1388e Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Wed, 28 Aug 2024 15:49:32 +0200 Subject: [PATCH 526/573] selftests: vDSO: open code basic chacha instead of linking to libsodium Linking to libsodium makes building this test annoying in cross compilation environments and is just way too much. Since this is just a basic correctness test, simply open code a simple, unoptimized, dumb chacha, rather than linking to libsodium. This also fixes a correctness issue on big endian systems. The kernel's random.c doesn't bother doing a le32_to_cpu operation on the random bytes that are passed as the key, and consequently neither does vgetrandom-chacha.S. However, libsodium's chacha _does_ do this, since it takes the key as an array of bytes. This meant that the test was broken on big endian systems, which this commit rectifies. Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/Makefile | 7 +-- .../testing/selftests/vDSO/vdso_test_chacha.c | 47 +++++++++++++++++-- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index 32ea4c2a9a23..e21e78aae24d 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -1,8 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 uname_M := $(shell uname -m 2>/dev/null || echo not) ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/x86/ -e s/x86_64/x86/) -SODIUM_LIBS := $(shell pkg-config --libs libsodium 2>/dev/null) -SODIUM_CFLAGS := $(shell pkg-config --cflags libsodium 2>/dev/null) TEST_GEN_PROGS := vdso_test_gettimeofday TEST_GEN_PROGS += vdso_test_getcpu @@ -14,10 +12,8 @@ endif TEST_GEN_PROGS += vdso_test_correctness ifeq ($(uname_M),x86_64) TEST_GEN_PROGS += vdso_test_getrandom -ifneq ($(SODIUM_LIBS),) TEST_GEN_PROGS += vdso_test_chacha endif -endif CFLAGS := -std=gnu99 @@ -43,8 +39,7 @@ $(OUTPUT)/vdso_test_getrandom: CFLAGS += -isystem $(top_srcdir)/tools/include \ -isystem $(top_srcdir)/include/uapi $(OUTPUT)/vdso_test_chacha: $(top_srcdir)/tools/arch/$(ARCH)/vdso/vgetrandom-chacha.S -$(OUTPUT)/vdso_test_chacha: LDLIBS += $(SODIUM_LIBS) $(OUTPUT)/vdso_test_chacha: CFLAGS += -idirafter $(top_srcdir)/tools/include \ -idirafter $(top_srcdir)/arch/$(ARCH)/include \ -idirafter $(top_srcdir)/include \ - -D__ASSEMBLY__ -Wa,--noexecstack $(SODIUM_CFLAGS) + -D__ASSEMBLY__ -Wa,--noexecstack diff --git a/tools/testing/selftests/vDSO/vdso_test_chacha.c b/tools/testing/selftests/vDSO/vdso_test_chacha.c index ca5639d02969..f8603650d26b 100644 --- a/tools/testing/selftests/vDSO/vdso_test_chacha.c +++ b/tools/testing/selftests/vDSO/vdso_test_chacha.c @@ -3,13 +3,55 @@ * Copyright (C) 2022-2024 Jason A. Donenfeld . All Rights Reserved. */ -#include +#include #include #include #include #include #include "../kselftest.h" +static uint32_t rol32(uint32_t word, unsigned int shift) +{ + return (word << (shift & 31)) | (word >> ((-shift) & 31)); +} + +static void reference_chacha20_blocks(uint8_t *dst_bytes, const uint32_t *key, size_t nblocks) +{ + uint32_t s[16] = { + 0x61707865U, 0x3320646eU, 0x79622d32U, 0x6b206574U, + key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7] + }; + + while (nblocks--) { + uint32_t x[16]; + memcpy(x, s, sizeof(x)); + for (unsigned int r = 0; r < 20; r += 2) { + #define QR(a, b, c, d) ( \ + x[a] += x[b], \ + x[d] = rol32(x[d] ^ x[a], 16), \ + x[c] += x[d], \ + x[b] = rol32(x[b] ^ x[c], 12), \ + x[a] += x[b], \ + x[d] = rol32(x[d] ^ x[a], 8), \ + x[c] += x[d], \ + x[b] = rol32(x[b] ^ x[c], 7)) + + QR(0, 4, 8, 12); + QR(1, 5, 9, 13); + QR(2, 6, 10, 14); + QR(3, 7, 11, 15); + QR(0, 5, 10, 15); + QR(1, 6, 11, 12); + QR(2, 7, 8, 13); + QR(3, 4, 9, 14); + } + for (unsigned int i = 0; i < 16; ++i, dst_bytes += sizeof(uint32_t)) + put_unaligned_le32(x[i] + s[i], dst_bytes); + if (!++s[12]) + ++s[13]; + } +} + typedef uint8_t u8; typedef uint32_t u32; typedef uint64_t u64; @@ -18,7 +60,6 @@ typedef uint64_t u64; int main(int argc, char *argv[]) { enum { TRIALS = 1000, BLOCKS = 128, BLOCK_SIZE = 64 }; - static const uint8_t nonce[8] = { 0 }; uint32_t counter[2]; uint32_t key[8]; uint8_t output1[BLOCK_SIZE * BLOCKS], output2[BLOCK_SIZE * BLOCKS]; @@ -31,7 +72,7 @@ int main(int argc, char *argv[]) printf("getrandom() failed!\n"); return KSFT_SKIP; } - crypto_stream_chacha20(output1, sizeof(output1), nonce, (uint8_t *)key); + reference_chacha20_blocks(output1, key, BLOCKS); for (unsigned int split = 0; split < BLOCKS; ++split) { memset(output2, 'X', sizeof(output2)); memset(counter, 0, sizeof(counter)); From 81c6896049b6ca69a9e737656ac33b3fd96a277c Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Tue, 27 Aug 2024 09:31:50 +0200 Subject: [PATCH 527/573] random: vDSO: don't use 64-bit atomics on 32-bit architectures Performing SMP atomic operations on u64 fails on powerpc32: CC drivers/char/random.o In file included from : drivers/char/random.c: In function 'crng_reseed': ././include/linux/compiler_types.h:510:45: error: call to '__compiletime_assert_391' declared with attribute error: Need native word sized stores/loads for atomicity. 510 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) | ^ ././include/linux/compiler_types.h:491:25: note: in definition of macro '__compiletime_assert' 491 | prefix ## suffix(); \ | ^~~~~~ ././include/linux/compiler_types.h:510:9: note: in expansion of macro '_compiletime_assert' 510 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) | ^~~~~~~~~~~~~~~~~~~ ././include/linux/compiler_types.h:513:9: note: in expansion of macro 'compiletime_assert' 513 | compiletime_assert(__native_word(t), \ | ^~~~~~~~~~~~~~~~~~ ./arch/powerpc/include/asm/barrier.h:74:9: note: in expansion of macro 'compiletime_assert_atomic_type' 74 | compiletime_assert_atomic_type(*p); \ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ./include/asm-generic/barrier.h:172:55: note: in expansion of macro '__smp_store_release' 172 | #define smp_store_release(p, v) do { kcsan_release(); __smp_store_release(p, v); } while (0) | ^~~~~~~~~~~~~~~~~~~ drivers/char/random.c:286:9: note: in expansion of macro 'smp_store_release' 286 | smp_store_release(&__arch_get_k_vdso_rng_data()->generation, next_gen + 1); | ^~~~~~~~~~~~~~~~~ The kernel-side generation counter in the random driver is handled as an unsigned long, not as a u64, in base_crng and struct crng. But on the vDSO side, it needs to be an u64, not just an unsigned long, in order to support a 32-bit vDSO atop a 64-bit kernel. On kernel side, however, it is an unsigned long, hence a 32-bit value on 32-bit architectures, so just cast it to unsigned long for the smp_store_release(). A side effect is that on big endian architectures the store will be performed in the upper 32 bits. It is not an issue on its own because the vDSO site doesn't mind the value, as it only checks differences. Just make sure that the vDSO side checks the full 64 bits. For that, the local current_generation has to be u64 as well. Signed-off-by: Christophe Leroy Suggested-by: Thomas Gleixner Signed-off-by: Jason A. Donenfeld --- drivers/char/random.c | 9 ++++++++- lib/vdso/getrandom.c | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/char/random.c b/drivers/char/random.c index 87fe61295ea1..c12f906e05d6 100644 --- a/drivers/char/random.c +++ b/drivers/char/random.c @@ -281,8 +281,15 @@ static void crng_reseed(struct work_struct *work) * former to arrive at the latter. Use smp_store_release so that this * is ordered with the write above to base_crng.generation. Pairs with * the smp_rmb() before the syscall in the vDSO code. + * + * Cast to unsigned long for 32-bit architectures, since atomic 64-bit + * operations are not supported on those architectures. This is safe + * because base_crng.generation is a 32-bit value. On big-endian + * architectures it will be stored in the upper 32 bits, but that's okay + * because the vDSO side only checks whether the value changed, without + * actually using or interpreting the value. */ - smp_store_release(&_vdso_rng_data.generation, next_gen + 1); + smp_store_release((unsigned long *)&_vdso_rng_data.generation, next_gen + 1); #endif if (!static_branch_likely(&crng_is_ready)) crng_init = CRNG_READY; diff --git a/lib/vdso/getrandom.c b/lib/vdso/getrandom.c index e1db228bc4f0..bacf19dbb6a1 100644 --- a/lib/vdso/getrandom.c +++ b/lib/vdso/getrandom.c @@ -68,8 +68,8 @@ __cvdso_getrandom_data(const struct vdso_rng_data *rng_info, void *buffer, size_ struct vgetrandom_state *state = opaque_state; size_t batch_len, nblocks, orig_len = len; bool in_use, have_retried = false; - unsigned long current_generation; void *orig_buffer = buffer; + u64 current_generation; u32 counter[2] = { 0 }; if (unlikely(opaque_len == ~0UL && !buffer && !len && !flags)) { From b757959f192daf4d107dd5198b562032bd757a5e Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Thu, 22 Aug 2024 09:13:11 +0200 Subject: [PATCH 528/573] random: vDSO: add __arch_get_k_vdso_rng_data() helper for data page access _vdso_data is specific to x86 and __arch_get_k_vdso_data() is provided so that all architectures can provide the requested pointer. Do the same with _vdso_rng_data, provide __arch_get_k_vdso_rng_data() and don't use x86 _vdso_rng_data directly. Until now vdso/vsyscall.h was only included by time/vsyscall.c but now it will also be included in char/random.c, leading to a duplicate declaration of _vdso_data and _vdso_rng_data. To fix this issue, move the declaration in a C file. vma.c looks like the most appropriate candidate. We don't need to replace the definitions in vsyscall.h by declarations as declarations are already in asm/vvar.h. Signed-off-by: Christophe Leroy Signed-off-by: Jason A. Donenfeld --- arch/x86/entry/vdso/vma.c | 3 +++ arch/x86/include/asm/vdso/vsyscall.h | 10 +++++++--- drivers/char/random.c | 5 +++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/arch/x86/entry/vdso/vma.c b/arch/x86/entry/vdso/vma.c index 6d83ceb7f1ba..b8fed8b8b9cc 100644 --- a/arch/x86/entry/vdso/vma.c +++ b/arch/x86/entry/vdso/vma.c @@ -38,6 +38,9 @@ struct vdso_data *arch_get_vdso_data(void *vvar_page) } #undef EMIT_VVAR +DEFINE_VVAR(struct vdso_data, _vdso_data); +DEFINE_VVAR_SINGLE(struct vdso_rng_data, _vdso_rng_data); + unsigned int vclocks_used __read_mostly; #if defined(CONFIG_X86_64) diff --git a/arch/x86/include/asm/vdso/vsyscall.h b/arch/x86/include/asm/vdso/vsyscall.h index 972415a8be31..67fedf1698b5 100644 --- a/arch/x86/include/asm/vdso/vsyscall.h +++ b/arch/x86/include/asm/vdso/vsyscall.h @@ -9,9 +9,6 @@ #include #include -DEFINE_VVAR(struct vdso_data, _vdso_data); -DEFINE_VVAR_SINGLE(struct vdso_rng_data, _vdso_rng_data); - /* * Update the vDSO data page to keep in sync with kernel timekeeping. */ @@ -22,6 +19,13 @@ struct vdso_data *__x86_get_k_vdso_data(void) } #define __arch_get_k_vdso_data __x86_get_k_vdso_data +static __always_inline +struct vdso_rng_data *__x86_get_k_vdso_rng_data(void) +{ + return &_vdso_rng_data; +} +#define __arch_get_k_vdso_rng_data __x86_get_k_vdso_rng_data + /* The asm-generic header needs to be included after the definitions above */ #include diff --git a/drivers/char/random.c b/drivers/char/random.c index c12f906e05d6..23ee76bbb4aa 100644 --- a/drivers/char/random.c +++ b/drivers/char/random.c @@ -59,6 +59,7 @@ #ifdef CONFIG_VDSO_GETRANDOM #include #include +#include #endif #include #include @@ -289,7 +290,7 @@ static void crng_reseed(struct work_struct *work) * because the vDSO side only checks whether the value changed, without * actually using or interpreting the value. */ - smp_store_release((unsigned long *)&_vdso_rng_data.generation, next_gen + 1); + smp_store_release((unsigned long *)&__arch_get_k_vdso_rng_data()->generation, next_gen + 1); #endif if (!static_branch_likely(&crng_is_ready)) crng_init = CRNG_READY; @@ -742,7 +743,7 @@ static void __cold _credit_init_bits(size_t bits) queue_work(system_unbound_wq, &set_ready); atomic_notifier_call_chain(&random_ready_notifier, 0, NULL); #ifdef CONFIG_VDSO_GETRANDOM - WRITE_ONCE(_vdso_rng_data.is_ready, true); + WRITE_ONCE(__arch_get_k_vdso_rng_data()->is_ready, true); #endif wake_up_interruptible(&crng_init_wait); kill_fasync(&fasync, SIGIO, POLL_IN); From 81723e3ac388271cf1fe9d9ca8f4e9c74689ea0e Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Thu, 22 Aug 2024 09:13:12 +0200 Subject: [PATCH 529/573] random: vDSO: add missing c-getrandom-y in Makefile Same as for the gettimeofday CVDSO implementation, add c-getrandom-y to ease the inclusion of lib/vdso/getrandom.c in architectures' VDSO builds. Signed-off-by: Christophe Leroy Signed-off-by: Jason A. Donenfeld --- lib/vdso/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/vdso/Makefile b/lib/vdso/Makefile index 9f031eafc465..cedbf15f8087 100644 --- a/lib/vdso/Makefile +++ b/lib/vdso/Makefile @@ -4,6 +4,7 @@ GENERIC_VDSO_MK_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) GENERIC_VDSO_DIR := $(dir $(GENERIC_VDSO_MK_PATH)) c-gettimeofday-$(CONFIG_GENERIC_GETTIMEOFDAY) := $(addprefix $(GENERIC_VDSO_DIR), gettimeofday.c) +c-getrandom-$(CONFIG_VDSO_GETRANDOM) := $(addprefix $(GENERIC_VDSO_DIR), getrandom.c) # This cmd checks that the vdso library does not contain dynamic relocations. # It has to be called after the linking of the vdso library and requires it From b7bad082e113640fc81200ff869e5c2d7a9c29a2 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Thu, 22 Aug 2024 09:13:13 +0200 Subject: [PATCH 530/573] random: vDSO: avoid call to out of line memset() With the current implementation, __cvdso_getrandom_data() calls memset() on certain architectures, which is unexpected in the VDSO. Rather than providing a memset(), simply rewrite opaque data initialization to avoid memset(). Signed-off-by: Christophe Leroy Acked-by: Ard Biesheuvel Signed-off-by: Jason A. Donenfeld --- lib/vdso/getrandom.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/vdso/getrandom.c b/lib/vdso/getrandom.c index bacf19dbb6a1..1281fa3546c2 100644 --- a/lib/vdso/getrandom.c +++ b/lib/vdso/getrandom.c @@ -3,6 +3,7 @@ * Copyright (C) 2022-2024 Jason A. Donenfeld . All Rights Reserved. */ +#include #include #include #include @@ -73,11 +74,12 @@ __cvdso_getrandom_data(const struct vdso_rng_data *rng_info, void *buffer, size_ u32 counter[2] = { 0 }; if (unlikely(opaque_len == ~0UL && !buffer && !len && !flags)) { - *(struct vgetrandom_opaque_params *)opaque_state = (struct vgetrandom_opaque_params) { - .size_of_opaque_state = sizeof(*state), - .mmap_prot = PROT_READ | PROT_WRITE, - .mmap_flags = MAP_DROPPABLE | MAP_ANONYMOUS - }; + struct vgetrandom_opaque_params *params = opaque_state; + params->size_of_opaque_state = sizeof(*state); + params->mmap_prot = PROT_READ | PROT_WRITE; + params->mmap_flags = MAP_DROPPABLE | MAP_ANONYMOUS; + for (size_t i = 0; i < ARRAY_SIZE(params->reserved); ++i) + params->reserved[i] = 0; return 0; } From 7f053812dab3946cb704520b72c381f605ecdf95 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Tue, 27 Aug 2024 09:31:47 +0200 Subject: [PATCH 531/573] random: vDSO: minimize and simplify header includes Depending on the architecture, building a 32-bit vDSO on a 64-bit kernel is problematic when some system headers are included. Minimise the amount of headers by moving needed items, such as __{get,put}_unaligned_t, into dedicated common headers and in general use more specific headers, similar to what was done in commit 8165b57bca21 ("linux/const.h: Extract common header for vDSO") and commit 8c59ab839f52 ("lib/vdso: Enable common headers"). On some architectures this results in missing PAGE_SIZE, as was described by commit 8b3843ae3634 ("vdso/datapage: Quick fix - use asm/page-def.h for ARM64"), so define this if necessary, in the same way as done prior by commit cffaefd15a8f ("vdso: Use CONFIG_PAGE_SHIFT in vdso/datapage.h"). Removing linux/time64.h leads to missing 'struct timespec64' in x86's asm/pvclock.h. Add a forward declaration of that struct in that file. Signed-off-by: Christophe Leroy Signed-off-by: Jason A. Donenfeld --- arch/x86/include/asm/pvclock.h | 1 + include/asm-generic/unaligned.h | 11 +---------- include/vdso/helpers.h | 1 + include/vdso/unaligned.h | 15 +++++++++++++++ lib/vdso/getrandom.c | 13 ++++++++----- 5 files changed, 26 insertions(+), 15 deletions(-) create mode 100644 include/vdso/unaligned.h diff --git a/arch/x86/include/asm/pvclock.h b/arch/x86/include/asm/pvclock.h index 0c92db84469d..6e4f8fae3ce9 100644 --- a/arch/x86/include/asm/pvclock.h +++ b/arch/x86/include/asm/pvclock.h @@ -5,6 +5,7 @@ #include #include +struct timespec64; /* some helper functions for xen and kvm pv clock sources */ u64 pvclock_clocksource_read(struct pvclock_vcpu_time_info *src); u64 pvclock_clocksource_read_nowd(struct pvclock_vcpu_time_info *src); diff --git a/include/asm-generic/unaligned.h b/include/asm-generic/unaligned.h index a84c64e5f11e..95acdd70b3b2 100644 --- a/include/asm-generic/unaligned.h +++ b/include/asm-generic/unaligned.h @@ -8,16 +8,7 @@ */ #include #include - -#define __get_unaligned_t(type, ptr) ({ \ - const struct { type x; } __packed *__pptr = (typeof(__pptr))(ptr); \ - __pptr->x; \ -}) - -#define __put_unaligned_t(type, val, ptr) do { \ - struct { type x; } __packed *__pptr = (typeof(__pptr))(ptr); \ - __pptr->x = (val); \ -} while (0) +#include #define get_unaligned(ptr) __get_unaligned_t(typeof(*(ptr)), (ptr)) #define put_unaligned(val, ptr) __put_unaligned_t(typeof(*(ptr)), (val), (ptr)) diff --git a/include/vdso/helpers.h b/include/vdso/helpers.h index 73501149439d..3ddb03bb05cb 100644 --- a/include/vdso/helpers.h +++ b/include/vdso/helpers.h @@ -4,6 +4,7 @@ #ifndef __ASSEMBLY__ +#include #include static __always_inline u32 vdso_read_begin(const struct vdso_data *vd) diff --git a/include/vdso/unaligned.h b/include/vdso/unaligned.h new file mode 100644 index 000000000000..eee3d2a4dbe4 --- /dev/null +++ b/include/vdso/unaligned.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __VDSO_UNALIGNED_H +#define __VDSO_UNALIGNED_H + +#define __get_unaligned_t(type, ptr) ({ \ + const struct { type x; } __packed *__pptr = (typeof(__pptr))(ptr); \ + __pptr->x; \ +}) + +#define __put_unaligned_t(type, val, ptr) do { \ + struct { type x; } __packed *__pptr = (typeof(__pptr))(ptr); \ + __pptr->x = (val); \ +} while (0) + +#endif /* __VDSO_UNALIGNED_H */ diff --git a/lib/vdso/getrandom.c b/lib/vdso/getrandom.c index 1281fa3546c2..938ca539aaa6 100644 --- a/lib/vdso/getrandom.c +++ b/lib/vdso/getrandom.c @@ -4,15 +4,18 @@ */ #include -#include -#include -#include +#include #include #include +#include #include -#include -#include #include +#include + +#undef PAGE_SIZE +#undef PAGE_MASK +#define PAGE_SIZE (1UL << CONFIG_PAGE_SHIFT) +#define PAGE_MASK (~(PAGE_SIZE - 1)) #define MEMCPY_AND_ZERO_SRC(type, dst, src, len) do { \ while (len >= sizeof(type)) { \ From 67a121ac8fe136f17e505e6595b8cf1a1f65f40c Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Sun, 1 Sep 2024 15:05:01 +0200 Subject: [PATCH 532/573] selftests: vDSO: fix cross build for getrandom and chacha tests Unlike the check for the standalone x86 test, the check for building the vDSO getrandom and chacaha tests looks at the architecture for the host rather than the architecture for the target when deciding if they should be built. Since the chacha test includes some assembler code this means that cross building with x86 as either the target or host is broken. There's also some additional complications, where ARCH can legitimately be either x86_64 or x86, but the source code we need to compile lives in a directory path containing arch/x86. The standard SRCARCH variable handles that. And actually, all these variables and proper substitutions are already described in tools/scripts/Makefile.arch, so just include that to handle it. Similarly, ARCH=x86 can actually describe ARCH=x86_64, just with CONFIG_64BIT, so we can't rely on ARCH for selecting non-32-bit tests. For that, check against $(ARCH)$(CONFIG_X86_32). This won't help for people manually running this inside the vDSO selftest directory (which isn't really supported anyway and has problems on various archs), but it should work for builds of the kselftests, where the CONFIG_* variables are defined. On x86_64 machines, $(ARCH)$(CONFIG_X86_32) will evaluate to x86. On arm64 machines, it will evaluate to arm64. On 32-bit x86 machines, it will evaluate to x86y, which won't match the filter list. Reported-by: Mark Brown Reported-by: Christophe Leroy Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/Makefile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index e21e78aae24d..bd5005a7b37a 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -1,6 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -uname_M := $(shell uname -m 2>/dev/null || echo not) -ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/x86/ -e s/x86_64/x86/) +include ../../../scripts/Makefile.arch TEST_GEN_PROGS := vdso_test_gettimeofday TEST_GEN_PROGS += vdso_test_getcpu @@ -10,7 +9,7 @@ ifeq ($(ARCH),$(filter $(ARCH),x86 x86_64)) TEST_GEN_PROGS += vdso_standalone_test_x86 endif TEST_GEN_PROGS += vdso_test_correctness -ifeq ($(uname_M),x86_64) +ifeq ($(ARCH)$(CONFIG_X86_32),$(filter $(ARCH)$(CONFIG_X86_32),x86 x86_64)) TEST_GEN_PROGS += vdso_test_getrandom TEST_GEN_PROGS += vdso_test_chacha endif @@ -38,8 +37,8 @@ $(OUTPUT)/vdso_test_getrandom: CFLAGS += -isystem $(top_srcdir)/tools/include \ $(KHDR_INCLUDES) \ -isystem $(top_srcdir)/include/uapi -$(OUTPUT)/vdso_test_chacha: $(top_srcdir)/tools/arch/$(ARCH)/vdso/vgetrandom-chacha.S +$(OUTPUT)/vdso_test_chacha: $(top_srcdir)/tools/arch/$(SRCARCH)/vdso/vgetrandom-chacha.S $(OUTPUT)/vdso_test_chacha: CFLAGS += -idirafter $(top_srcdir)/tools/include \ - -idirafter $(top_srcdir)/arch/$(ARCH)/include \ + -idirafter $(top_srcdir)/arch/$(SRCARCH)/include \ -idirafter $(top_srcdir)/include \ -D__ASSEMBLY__ -Wa,--noexecstack From 4d456f0c87c95efb6197a30cd76b5b6ee5fb012e Mon Sep 17 00:00:00 2001 From: Xi Ruoyao Date: Sun, 1 Sep 2024 14:13:10 +0800 Subject: [PATCH 533/573] random: vDSO: add a __vdso_getrandom prototype for all architectures Without a prototype, we'll have to add a prototype for each architecture implementing vDSO getrandom. As most architectures will likely have the vDSO getrandom implemented in a near future, and we'd like to keep the declarations compatible everywhere (to ease the libc implementor work), we should really just have one copy of the prototype. This also is what's already done inside of include/vdso/gettime.h for those vDSO functions, so this continues that convention. Suggested-by: Huacai Chen Signed-off-by: Xi Ruoyao Acked-by: Huacai Chen [Jason: rewrite docbook comment for prototype.] Signed-off-by: Jason A. Donenfeld --- arch/x86/entry/vdso/vgetrandom.c | 2 -- include/vdso/getrandom.h | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/arch/x86/entry/vdso/vgetrandom.c b/arch/x86/entry/vdso/vgetrandom.c index 52d3c7faae2e..430862b8977c 100644 --- a/arch/x86/entry/vdso/vgetrandom.c +++ b/arch/x86/entry/vdso/vgetrandom.c @@ -6,8 +6,6 @@ #include "../../../../lib/vdso/getrandom.c" -ssize_t __vdso_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state, size_t opaque_len); - ssize_t __vdso_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state, size_t opaque_len) { return __cvdso_getrandom(buffer, len, flags, opaque_state, opaque_len); diff --git a/include/vdso/getrandom.h b/include/vdso/getrandom.h index 4cf02e678f5e..6ca4d6de9e46 100644 --- a/include/vdso/getrandom.h +++ b/include/vdso/getrandom.h @@ -56,4 +56,19 @@ struct vgetrandom_state { */ extern void __arch_chacha20_blocks_nostack(u8 *dst_bytes, const u32 *key, u32 *counter, size_t nblocks); +/** + * __vdso_getrandom - Architecture-specific vDSO implementation of getrandom() syscall. + * @buffer: Passed to __cvdso_getrandom(). + * @len: Passed to __cvdso_getrandom(). + * @flags: Passed to __cvdso_getrandom(). + * @opaque_state: Passed to __cvdso_getrandom(). + * @opaque_len: Passed to __cvdso_getrandom(); + * + * This function is implemented by making a single call to to __cvdso_getrandom(), whose + * documentation may be consulted for more information. + * + * Returns: The return value of __cvdso_getrandom(). + */ +extern ssize_t __vdso_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state, size_t opaque_len); + #endif /* _VDSO_GETRANDOM_H */ From 18efd0b10e0fd77fe649a375f9f17a387b5d1609 Mon Sep 17 00:00:00 2001 From: Xi Ruoyao Date: Sun, 1 Sep 2024 14:13:11 +0800 Subject: [PATCH 534/573] LoongArch: vDSO: Wire up getrandom() vDSO implementation Hook up the generic vDSO implementation to the LoongArch vDSO data page by providing the required __arch_chacha20_blocks_nostack, __arch_get_k_vdso_rng_data, and getrandom_syscall implementations. Also wire up the selftests. Signed-off-by: Xi Ruoyao Acked-by: Huacai Chen Signed-off-by: Jason A. Donenfeld --- arch/loongarch/Kconfig | 1 + arch/loongarch/include/asm/vdso/getrandom.h | 39 ++++ arch/loongarch/include/asm/vdso/vdso.h | 6 + arch/loongarch/include/asm/vdso/vsyscall.h | 8 + arch/loongarch/kernel/vdso.c | 1 + arch/loongarch/vdso/Makefile | 7 +- arch/loongarch/vdso/vdso.lds.S | 1 + arch/loongarch/vdso/vgetrandom-chacha.S | 242 ++++++++++++++++++++ arch/loongarch/vdso/vgetrandom.c | 10 + tools/arch/loongarch/vdso | 1 + tools/testing/selftests/vDSO/Makefile | 2 +- 11 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 arch/loongarch/include/asm/vdso/getrandom.h create mode 100644 arch/loongarch/vdso/vgetrandom-chacha.S create mode 100644 arch/loongarch/vdso/vgetrandom.c create mode 120000 tools/arch/loongarch/vdso diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index 70f169210b52..14821c2aba5b 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -190,6 +190,7 @@ config LOONGARCH select TRACE_IRQFLAGS_SUPPORT select USE_PERCPU_NUMA_NODE_ID select USER_STACKTRACE_SUPPORT + select VDSO_GETRANDOM select ZONE_DMA32 config 32BIT diff --git a/arch/loongarch/include/asm/vdso/getrandom.h b/arch/loongarch/include/asm/vdso/getrandom.h new file mode 100644 index 000000000000..02f36772541b --- /dev/null +++ b/arch/loongarch/include/asm/vdso/getrandom.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Xi Ruoyao . All Rights Reserved. + */ +#ifndef __ASM_VDSO_GETRANDOM_H +#define __ASM_VDSO_GETRANDOM_H + +#ifndef __ASSEMBLY__ + +#include +#include + +static __always_inline ssize_t getrandom_syscall(void *_buffer, size_t _len, unsigned int _flags) +{ + register long ret asm("a0"); + register long nr asm("a7") = __NR_getrandom; + register void *buffer asm("a0") = _buffer; + register size_t len asm("a1") = _len; + register unsigned int flags asm("a2") = _flags; + + asm volatile( + " syscall 0\n" + : "+r" (ret) + : "r" (nr), "r" (buffer), "r" (len), "r" (flags) + : "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7", "$t8", + "memory"); + + return ret; +} + +static __always_inline const struct vdso_rng_data *__arch_get_vdso_rng_data(void) +{ + return (const struct vdso_rng_data *)(get_vdso_data() + VVAR_LOONGARCH_PAGES_START * + PAGE_SIZE + offsetof(struct loongarch_vdso_data, rng_data)); +} + +#endif /* !__ASSEMBLY__ */ + +#endif /* __ASM_VDSO_GETRANDOM_H */ diff --git a/arch/loongarch/include/asm/vdso/vdso.h b/arch/loongarch/include/asm/vdso/vdso.h index 5a12309d9fb5..e31ac7474513 100644 --- a/arch/loongarch/include/asm/vdso/vdso.h +++ b/arch/loongarch/include/asm/vdso/vdso.h @@ -4,6 +4,9 @@ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited */ +#ifndef _ASM_VDSO_VDSO_H +#define _ASM_VDSO_VDSO_H + #ifndef __ASSEMBLY__ #include @@ -16,6 +19,7 @@ struct vdso_pcpu_data { struct loongarch_vdso_data { struct vdso_pcpu_data pdata[NR_CPUS]; + struct vdso_rng_data rng_data; }; /* @@ -63,3 +67,5 @@ static inline unsigned long get_vdso_data(void) } #endif /* __ASSEMBLY__ */ + +#endif diff --git a/arch/loongarch/include/asm/vdso/vsyscall.h b/arch/loongarch/include/asm/vdso/vsyscall.h index 5de615383a22..b1273ce6f140 100644 --- a/arch/loongarch/include/asm/vdso/vsyscall.h +++ b/arch/loongarch/include/asm/vdso/vsyscall.h @@ -8,6 +8,7 @@ #include extern struct vdso_data *vdso_data; +extern struct vdso_rng_data *vdso_rng_data; /* * Update the vDSO data page to keep in sync with kernel timekeeping. @@ -19,6 +20,13 @@ struct vdso_data *__loongarch_get_k_vdso_data(void) } #define __arch_get_k_vdso_data __loongarch_get_k_vdso_data +static __always_inline +struct vdso_rng_data *__loongarch_get_k_vdso_rng_data(void) +{ + return vdso_rng_data; +} +#define __arch_get_k_vdso_rng_data __loongarch_get_k_vdso_rng_data + /* The asm-generic header needs to be included after the definitions above */ #include diff --git a/arch/loongarch/kernel/vdso.c b/arch/loongarch/kernel/vdso.c index 90dfccb41c14..f6fcc52aefae 100644 --- a/arch/loongarch/kernel/vdso.c +++ b/arch/loongarch/kernel/vdso.c @@ -37,6 +37,7 @@ static union { static struct page *vdso_pages[] = { NULL }; struct vdso_data *vdso_data = generic_vdso_data.data; struct vdso_pcpu_data *vdso_pdata = loongarch_vdso_data.vdata.pdata; +struct vdso_rng_data *vdso_rng_data = &loongarch_vdso_data.vdata.rng_data; static int vdso_mremap(const struct vm_special_mapping *sm, struct vm_area_struct *new_vma) { diff --git a/arch/loongarch/vdso/Makefile b/arch/loongarch/vdso/Makefile index d724d46b07c8..40c1175823d6 100644 --- a/arch/loongarch/vdso/Makefile +++ b/arch/loongarch/vdso/Makefile @@ -4,7 +4,8 @@ # Include the generic Makefile to check the built vdso. include $(srctree)/lib/vdso/Makefile -obj-vdso-y := elf.o vgetcpu.o vgettimeofday.o sigreturn.o +obj-vdso-y := elf.o vgetcpu.o vgettimeofday.o vgetrandom.o \ + vgetrandom-chacha.o sigreturn.o # Common compiler flags between ABIs. ccflags-vdso := \ @@ -29,6 +30,10 @@ ifneq ($(c-gettimeofday-y),) CFLAGS_vgettimeofday.o += -include $(c-gettimeofday-y) endif +ifneq ($(c-getrandom-y),) + CFLAGS_vgetrandom.o += -include $(c-getrandom-y) +endif + # VDSO linker flags. ldflags-y := -Bsymbolic --no-undefined -soname=linux-vdso.so.1 \ $(filter -E%,$(KBUILD_CFLAGS)) -nostdlib -shared \ diff --git a/arch/loongarch/vdso/vdso.lds.S b/arch/loongarch/vdso/vdso.lds.S index 56ad855896de..6b441bde4026 100644 --- a/arch/loongarch/vdso/vdso.lds.S +++ b/arch/loongarch/vdso/vdso.lds.S @@ -62,6 +62,7 @@ VERSION __vdso_clock_getres; __vdso_clock_gettime; __vdso_gettimeofday; + __vdso_getrandom; __vdso_rt_sigreturn; local: *; }; diff --git a/arch/loongarch/vdso/vgetrandom-chacha.S b/arch/loongarch/vdso/vgetrandom-chacha.S new file mode 100644 index 000000000000..7e86a50f6e85 --- /dev/null +++ b/arch/loongarch/vdso/vgetrandom-chacha.S @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Xi Ruoyao . All Rights Reserved. + */ + +#include +#include +#include + +.text + +/* Salsa20 quarter-round */ +.macro QR a b c d + add.w \a, \a, \b + xor \d, \d, \a + rotri.w \d, \d, 16 + + add.w \c, \c, \d + xor \b, \b, \c + rotri.w \b, \b, 20 + + add.w \a, \a, \b + xor \d, \d, \a + rotri.w \d, \d, 24 + + add.w \c, \c, \d + xor \b, \b, \c + rotri.w \b, \b, 25 +.endm + +/* + * Very basic LoongArch implementation of ChaCha20. Produces a given positive + * number of blocks of output with a nonce of 0, taking an input key and + * 8-byte counter. Importantly does not spill to the stack. Its arguments + * are: + * + * a0: output bytes + * a1: 32-byte key input + * a2: 8-byte counter input/output + * a3: number of 64-byte blocks to write to output + */ +SYM_FUNC_START(__arch_chacha20_blocks_nostack) + +/* We don't need a frame pointer */ +#define s9 fp + +#define output a0 +#define key a1 +#define counter a2 +#define nblocks a3 +#define i a4 +#define state0 s0 +#define state1 s1 +#define state2 s2 +#define state3 s3 +#define state4 s4 +#define state5 s5 +#define state6 s6 +#define state7 s7 +#define state8 s8 +#define state9 s9 +#define state10 a5 +#define state11 a6 +#define state12 a7 +#define state13 t0 +#define state14 t1 +#define state15 t2 +#define cnt_lo t3 +#define cnt_hi t4 +#define copy0 t5 +#define copy1 t6 +#define copy2 t7 + +/* Reuse i as copy3 */ +#define copy3 i + + /* + * The ABI requires s0-s9 saved, and sp aligned to 16-byte. + * This does not violate the stack-less requirement: no sensitive data + * is spilled onto the stack. + */ + PTR_ADDI sp, sp, (-SZREG * 10) & STACK_ALIGN + REG_S s0, sp, 0 + REG_S s1, sp, SZREG + REG_S s2, sp, SZREG * 2 + REG_S s3, sp, SZREG * 3 + REG_S s4, sp, SZREG * 4 + REG_S s5, sp, SZREG * 5 + REG_S s6, sp, SZREG * 6 + REG_S s7, sp, SZREG * 7 + REG_S s8, sp, SZREG * 8 + REG_S s9, sp, SZREG * 9 + + li.w copy0, 0x61707865 + li.w copy1, 0x3320646e + li.w copy2, 0x79622d32 + + ld.w cnt_lo, counter, 0 + ld.w cnt_hi, counter, 4 + +.Lblock: + /* state[0,1,2,3] = "expand 32-byte k" */ + move state0, copy0 + move state1, copy1 + move state2, copy2 + li.w state3, 0x6b206574 + + /* state[4,5,..,11] = key */ + ld.w state4, key, 0 + ld.w state5, key, 4 + ld.w state6, key, 8 + ld.w state7, key, 12 + ld.w state8, key, 16 + ld.w state9, key, 20 + ld.w state10, key, 24 + ld.w state11, key, 28 + + /* state[12,13] = counter */ + move state12, cnt_lo + move state13, cnt_hi + + /* state[14,15] = 0 */ + move state14, zero + move state15, zero + + li.w i, 10 +.Lpermute: + /* odd round */ + QR state0, state4, state8, state12 + QR state1, state5, state9, state13 + QR state2, state6, state10, state14 + QR state3, state7, state11, state15 + + /* even round */ + QR state0, state5, state10, state15 + QR state1, state6, state11, state12 + QR state2, state7, state8, state13 + QR state3, state4, state9, state14 + + addi.w i, i, -1 + bnez i, .Lpermute + + /* + * copy[3] = "expa", materialize it here because copy[3] shares the + * same register with i which just became dead. + */ + li.w copy3, 0x6b206574 + + /* output[0,1,2,3] = copy[0,1,2,3] + state[0,1,2,3] */ + add.w state0, state0, copy0 + add.w state1, state1, copy1 + add.w state2, state2, copy2 + add.w state3, state3, copy3 + st.w state0, output, 0 + st.w state1, output, 4 + st.w state2, output, 8 + st.w state3, output, 12 + + /* from now on state[0,1,2,3] are scratch registers */ + + /* state[0,1,2,3] = lo32(key) */ + ld.w state0, key, 0 + ld.w state1, key, 4 + ld.w state2, key, 8 + ld.w state3, key, 12 + + /* output[4,5,6,7] = state[0,1,2,3] + state[4,5,6,7] */ + add.w state4, state4, state0 + add.w state5, state5, state1 + add.w state6, state6, state2 + add.w state7, state7, state3 + st.w state4, output, 16 + st.w state5, output, 20 + st.w state6, output, 24 + st.w state7, output, 28 + + /* state[0,1,2,3] = hi32(key) */ + ld.w state0, key, 16 + ld.w state1, key, 20 + ld.w state2, key, 24 + ld.w state3, key, 28 + + /* output[8,9,10,11] = state[0,1,2,3] + state[8,9,10,11] */ + add.w state8, state8, state0 + add.w state9, state9, state1 + add.w state10, state10, state2 + add.w state11, state11, state3 + st.w state8, output, 32 + st.w state9, output, 36 + st.w state10, output, 40 + st.w state11, output, 44 + + /* output[12,13,14,15] = state[12,13,14,15] + [cnt_lo, cnt_hi, 0, 0] */ + add.w state12, state12, cnt_lo + add.w state13, state13, cnt_hi + st.w state12, output, 48 + st.w state13, output, 52 + st.w state14, output, 56 + st.w state15, output, 60 + + /* ++counter */ + addi.w cnt_lo, cnt_lo, 1 + sltui state0, cnt_lo, 1 + add.w cnt_hi, cnt_hi, state0 + + /* output += 64 */ + PTR_ADDI output, output, 64 + /* --nblocks */ + PTR_ADDI nblocks, nblocks, -1 + bnez nblocks, .Lblock + + /* counter = [cnt_lo, cnt_hi] */ + st.w cnt_lo, counter, 0 + st.w cnt_hi, counter, 4 + + /* + * Zero out the potentially sensitive regs, in case nothing uses these + * again. As at now copy[0,1,2,3] just contains "expand 32-byte k" and + * state[0,...,9] are s0-s9 those we'll restore in the epilogue, so we + * only need to zero state[11,...,15]. + */ + move state10, zero + move state11, zero + move state12, zero + move state13, zero + move state14, zero + move state15, zero + + REG_L s0, sp, 0 + REG_L s1, sp, SZREG + REG_L s2, sp, SZREG * 2 + REG_L s3, sp, SZREG * 3 + REG_L s4, sp, SZREG * 4 + REG_L s5, sp, SZREG * 5 + REG_L s6, sp, SZREG * 6 + REG_L s7, sp, SZREG * 7 + REG_L s8, sp, SZREG * 8 + REG_L s9, sp, SZREG * 9 + PTR_ADDI sp, sp, -((-SZREG * 10) & STACK_ALIGN) + + jr ra +SYM_FUNC_END(__arch_chacha20_blocks_nostack) diff --git a/arch/loongarch/vdso/vgetrandom.c b/arch/loongarch/vdso/vgetrandom.c new file mode 100644 index 000000000000..d5f258ac4a36 --- /dev/null +++ b/arch/loongarch/vdso/vgetrandom.c @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Xi Ruoyao . All Rights Reserved. + */ +#include + +ssize_t __vdso_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state, size_t opaque_len) +{ + return __cvdso_getrandom(buffer, len, flags, opaque_state, opaque_len); +} diff --git a/tools/arch/loongarch/vdso b/tools/arch/loongarch/vdso new file mode 120000 index 000000000000..ebda43a82db7 --- /dev/null +++ b/tools/arch/loongarch/vdso @@ -0,0 +1 @@ +../../../arch/loongarch/vdso \ No newline at end of file diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index bd5005a7b37a..d586d3e7a7c1 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -9,7 +9,7 @@ ifeq ($(ARCH),$(filter $(ARCH),x86 x86_64)) TEST_GEN_PROGS += vdso_standalone_test_x86 endif TEST_GEN_PROGS += vdso_test_correctness -ifeq ($(ARCH)$(CONFIG_X86_32),$(filter $(ARCH)$(CONFIG_X86_32),x86 x86_64)) +ifeq ($(ARCH)$(CONFIG_X86_32),$(filter $(ARCH)$(CONFIG_X86_32),x86 x86_64 loongarch)) TEST_GEN_PROGS += vdso_test_getrandom TEST_GEN_PROGS += vdso_test_chacha endif From ecb8bd70d51ccf9009219a6097cef293deada65b Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Sun, 1 Sep 2024 19:24:03 +0200 Subject: [PATCH 535/573] selftests: vDSO: build tests with O2 optimization Without -O2, the generated code for testing chacha function is awful. GCC even implements rol32() as a function of 20 instructions instead of just using the rotlwi instruction. ~# time ./vdso_test_chacha TAP version 13 1..1 ok 1 chacha: PASS real 0m 37.16s user 0m 36.89s sys 0m 0.26s Several other selftests directory add -O2, and the kernel is also always built with optimisation active. Do the same for vDSO selftests. With this patch the time is reduced by approximately 15%. ~# time ./vdso_test_chacha TAP version 13 1..1 ok 1 chacha: PASS real 0m 32.09s user 0m 31.86s sys 0m 0.22s Signed-off-by: Christophe Leroy Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index d586d3e7a7c1..04930125035e 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -14,7 +14,7 @@ TEST_GEN_PROGS += vdso_test_getrandom TEST_GEN_PROGS += vdso_test_chacha endif -CFLAGS := -std=gnu99 +CFLAGS := -std=gnu99 -O2 ifeq ($(CONFIG_X86_32),y) LDLIBS += -lgcc_s From bb10ffe01bf2d2436190958c7eae694515e1ebb7 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Sun, 1 Sep 2024 19:40:33 +0200 Subject: [PATCH 536/573] selftests: vDSO: also test counter in vdso_test_chacha The chacha vDSO selftest doesn't check the way the counter is handled by __arch_chacha20_blocks_nostack(). It indirectly checks that the counter is writen on exit and read back on new entry, but it doesn't check that the format is correct. When implementing this function on powerpc, I missed a case where the counter was writen and read in wrong byte order. Also, the counter uses two words, but the tests with a zero counter and uses a small amount of blocks, so at the end the upper part of the counter is always 0, so it is not checked. Add a verification of counter's content in addition to the verification of the output. Also add two tests where the counter crosses the u32 upper limit. The first test verifies that the function properly writes back the upper word, the second test verifies that the function properly reads back the upper word. Signed-off-by: Christophe Leroy Signed-off-by: Jason A. Donenfeld --- .../testing/selftests/vDSO/vdso_test_chacha.c | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/vDSO/vdso_test_chacha.c b/tools/testing/selftests/vDSO/vdso_test_chacha.c index f8603650d26b..e81d72c9882e 100644 --- a/tools/testing/selftests/vDSO/vdso_test_chacha.c +++ b/tools/testing/selftests/vDSO/vdso_test_chacha.c @@ -15,11 +15,12 @@ static uint32_t rol32(uint32_t word, unsigned int shift) return (word << (shift & 31)) | (word >> ((-shift) & 31)); } -static void reference_chacha20_blocks(uint8_t *dst_bytes, const uint32_t *key, size_t nblocks) +static void reference_chacha20_blocks(uint8_t *dst_bytes, const uint32_t *key, uint32_t *counter, size_t nblocks) { uint32_t s[16] = { 0x61707865U, 0x3320646eU, 0x79622d32U, 0x6b206574U, - key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7] + key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7], + counter[0], counter[1], 0, 0 }; while (nblocks--) { @@ -50,6 +51,8 @@ static void reference_chacha20_blocks(uint8_t *dst_bytes, const uint32_t *key, s if (!++s[12]) ++s[13]; } + counter[0] = s[12]; + counter[1] = s[13]; } typedef uint8_t u8; @@ -60,8 +63,7 @@ typedef uint64_t u64; int main(int argc, char *argv[]) { enum { TRIALS = 1000, BLOCKS = 128, BLOCK_SIZE = 64 }; - uint32_t counter[2]; - uint32_t key[8]; + uint32_t key[8], counter1[2], counter2[2]; uint8_t output1[BLOCK_SIZE * BLOCKS], output2[BLOCK_SIZE * BLOCKS]; ksft_print_header(); @@ -72,17 +74,33 @@ int main(int argc, char *argv[]) printf("getrandom() failed!\n"); return KSFT_SKIP; } - reference_chacha20_blocks(output1, key, BLOCKS); + memset(counter1, 0, sizeof(counter1)); + reference_chacha20_blocks(output1, key, counter1, BLOCKS); for (unsigned int split = 0; split < BLOCKS; ++split) { memset(output2, 'X', sizeof(output2)); - memset(counter, 0, sizeof(counter)); + memset(counter2, 0, sizeof(counter2)); if (split) - __arch_chacha20_blocks_nostack(output2, key, counter, split); - __arch_chacha20_blocks_nostack(output2 + split * BLOCK_SIZE, key, counter, BLOCKS - split); - if (memcmp(output1, output2, sizeof(output1))) + __arch_chacha20_blocks_nostack(output2, key, counter2, split); + __arch_chacha20_blocks_nostack(output2 + split * BLOCK_SIZE, key, counter2, BLOCKS - split); + if (memcmp(output1, output2, sizeof(output1)) || memcmp(counter1, counter2, sizeof(counter1))) return KSFT_FAIL; } } + memset(counter1, 0, sizeof(counter1)); + counter1[0] = (uint32_t)-BLOCKS + 2; + memset(counter2, 0, sizeof(counter2)); + counter2[0] = (uint32_t)-BLOCKS + 2; + + reference_chacha20_blocks(output1, key, counter1, BLOCKS); + __arch_chacha20_blocks_nostack(output2, key, counter2, BLOCKS); + if (memcmp(output1, output2, sizeof(output1)) || memcmp(counter1, counter2, sizeof(counter1))) + return KSFT_FAIL; + + reference_chacha20_blocks(output1, key, counter1, BLOCKS); + __arch_chacha20_blocks_nostack(output2, key, counter2, BLOCKS); + if (memcmp(output1, output2, sizeof(output1)) || memcmp(counter1, counter2, sizeof(counter1))) + return KSFT_FAIL; + ksft_test_result_pass("chacha: PASS\n"); return KSFT_PASS; } From 2c2ca3416b4090aef8c646adccfc3be4b712db8a Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 3 Sep 2024 12:09:16 +0000 Subject: [PATCH 537/573] arm64: alternative: make alternative_has_cap_likely() VDSO compatible Currently alternative_has_cap_unlikely() can be used in VDSO code, but alternative_has_cap_likely() cannot as it references alt_cb_patch_nops, which is not available when linking the VDSO. This is unfortunate as it would be useful to have alternative_has_cap_likely() available in VDSO code. The use of alt_cb_patch_nops was added in commit: d926079f17bf8aa4 ("arm64: alternatives: add shared NOP callback") ... as removing duplicate NOPs within the kernel Image saved areasonable amount of space. Given the VDSO code will have nowhere near as many alternative branches as the main kernel image, this isn't much of a concern, and a few extra nops isn't a massive problem. Change alternative_has_cap_likely() to only use alt_cb_patch_nops for the main kernel image, and allow duplicate NOPs in VDSO code. Signed-off-by: Mark Rutland Signed-off-by: Adhemerval Zanella Acked-by: Will Deacon Signed-off-by: Jason A. Donenfeld --- arch/arm64/include/asm/alternative-macros.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/include/asm/alternative-macros.h b/arch/arm64/include/asm/alternative-macros.h index d328f549b1a6..c8c77f9e36d6 100644 --- a/arch/arm64/include/asm/alternative-macros.h +++ b/arch/arm64/include/asm/alternative-macros.h @@ -230,7 +230,11 @@ alternative_has_cap_likely(const unsigned long cpucap) return false; asm goto( +#ifdef BUILD_VDSO + ALTERNATIVE("b %l[l_no]", "nop", %[cpucap]) +#else ALTERNATIVE_CB("b %l[l_no]", %[cpucap], alt_cb_patch_nops) +#endif : : [cpucap] "i" (cpucap) : From 712676ea2bb3882a852bcf49862c4247317fc9b2 Mon Sep 17 00:00:00 2001 From: Adhemerval Zanella Date: Tue, 3 Sep 2024 12:09:17 +0000 Subject: [PATCH 538/573] arm64: vDSO: Wire up getrandom() vDSO implementation Hook up the generic vDSO implementation to the aarch64 vDSO data page. The _vdso_rng_data required data is placed within the _vdso_data vvar page, by using a offset larger than the vdso_data. The vDSO function requires a ChaCha20 implementation that does not write to the stack, and that can do an entire ChaCha20 permutation. The one provided uses NEON on the permute operation, with a fallback to the syscall for chips that do not support AdvSIMD. This also passes the vdso_test_chacha test along with vdso_test_getrandom. The vdso_test_getrandom bench-single result on Neoverse-N1 shows: vdso: 25000000 times in 0.783884250 seconds libc: 25000000 times in 8.780275399 seconds syscall: 25000000 times in 8.786581518 seconds A small fixup to arch/arm64/include/asm/mman.h was required to avoid pulling kernel code into the vDSO, similar to what's already done in arch/arm64/include/asm/rwonce.h. Signed-off-by: Adhemerval Zanella Reviewed-by: Ard Biesheuvel Acked-by: Will Deacon Signed-off-by: Jason A. Donenfeld --- arch/arm64/Kconfig | 1 + arch/arm64/include/asm/mman.h | 6 +- arch/arm64/include/asm/vdso/getrandom.h | 50 ++++++ arch/arm64/include/asm/vdso/vsyscall.h | 15 ++ arch/arm64/kernel/vdso.c | 6 - arch/arm64/kernel/vdso/Makefile | 25 ++- arch/arm64/kernel/vdso/vdso.lds.S | 4 + arch/arm64/kernel/vdso/vgetrandom-chacha.S | 172 +++++++++++++++++++++ arch/arm64/kernel/vdso/vgetrandom.c | 15 ++ tools/arch/arm64/vdso | 1 + tools/include/linux/compiler.h | 4 + tools/testing/selftests/vDSO/Makefile | 3 +- 12 files changed, 286 insertions(+), 16 deletions(-) create mode 100644 arch/arm64/include/asm/vdso/getrandom.h create mode 100644 arch/arm64/kernel/vdso/vgetrandom-chacha.S create mode 100644 arch/arm64/kernel/vdso/vgetrandom.c create mode 120000 tools/arch/arm64/vdso diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index a2f8ff354ca6..7f7424d1b3b8 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -262,6 +262,7 @@ config ARM64 select TRACE_IRQFLAGS_NMI_SUPPORT select HAVE_SOFTIRQ_ON_OWN_STACK select USER_STACKTRACE_SUPPORT + select VDSO_GETRANDOM help ARM 64-bit (AArch64) Linux support. diff --git a/arch/arm64/include/asm/mman.h b/arch/arm64/include/asm/mman.h index 5966ee4a6154..ceb4370a69c5 100644 --- a/arch/arm64/include/asm/mman.h +++ b/arch/arm64/include/asm/mman.h @@ -2,9 +2,11 @@ #ifndef __ASM_MMAN_H__ #define __ASM_MMAN_H__ +#include + +#ifndef BUILD_VDSO #include #include -#include static inline unsigned long arch_calc_vm_prot_bits(unsigned long prot, unsigned long pkey __always_unused) @@ -60,4 +62,6 @@ static inline bool arch_validate_flags(unsigned long vm_flags) } #define arch_validate_flags(vm_flags) arch_validate_flags(vm_flags) +#endif /* !BUILD_VDSO */ + #endif /* ! __ASM_MMAN_H__ */ diff --git a/arch/arm64/include/asm/vdso/getrandom.h b/arch/arm64/include/asm/vdso/getrandom.h new file mode 100644 index 000000000000..342f807e2044 --- /dev/null +++ b/arch/arm64/include/asm/vdso/getrandom.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __ASM_VDSO_GETRANDOM_H +#define __ASM_VDSO_GETRANDOM_H + +#ifndef __ASSEMBLY__ + +#include +#include +#include + +/** + * getrandom_syscall - Invoke the getrandom() syscall. + * @buffer: Destination buffer to fill with random bytes. + * @len: Size of @buffer in bytes. + * @flags: Zero or more GRND_* flags. + * Returns: The number of random bytes written to @buffer, or a negative value indicating an error. + */ +static __always_inline ssize_t getrandom_syscall(void *_buffer, size_t _len, unsigned int _flags) +{ + register void *buffer asm ("x0") = _buffer; + register size_t len asm ("x1") = _len; + register unsigned int flags asm ("x2") = _flags; + register long ret asm ("x0"); + register long nr asm ("x8") = __NR_getrandom; + + asm volatile( + " svc #0\n" + : "=r" (ret) + : "r" (buffer), "r" (len), "r" (flags), "r" (nr) + : "memory"); + + return ret; +} + +static __always_inline const struct vdso_rng_data *__arch_get_vdso_rng_data(void) +{ + /* + * The RNG data is in the real VVAR data page, but if a task belongs to a time namespace + * then VVAR_DATA_PAGE_OFFSET points to the namespace-specific VVAR page and VVAR_TIMENS_ + * PAGE_OFFSET points to the real VVAR page. + */ + if (IS_ENABLED(CONFIG_TIME_NS) && _vdso_data->clock_mode == VDSO_CLOCKMODE_TIMENS) + return (void *)&_vdso_rng_data + VVAR_TIMENS_PAGE_OFFSET * (1UL << CONFIG_PAGE_SHIFT); + return &_vdso_rng_data; +} + +#endif /* !__ASSEMBLY__ */ + +#endif /* __ASM_VDSO_GETRANDOM_H */ diff --git a/arch/arm64/include/asm/vdso/vsyscall.h b/arch/arm64/include/asm/vdso/vsyscall.h index f94b1457c117..5b6d0dd3cef5 100644 --- a/arch/arm64/include/asm/vdso/vsyscall.h +++ b/arch/arm64/include/asm/vdso/vsyscall.h @@ -2,11 +2,19 @@ #ifndef __ASM_VDSO_VSYSCALL_H #define __ASM_VDSO_VSYSCALL_H +#define __VDSO_RND_DATA_OFFSET 480 + #ifndef __ASSEMBLY__ #include #include +enum vvar_pages { + VVAR_DATA_PAGE_OFFSET, + VVAR_TIMENS_PAGE_OFFSET, + VVAR_NR_PAGES, +}; + #define VDSO_PRECISION_MASK ~(0xFF00ULL<<48) extern struct vdso_data *vdso_data; @@ -21,6 +29,13 @@ struct vdso_data *__arm64_get_k_vdso_data(void) } #define __arch_get_k_vdso_data __arm64_get_k_vdso_data +static __always_inline +struct vdso_rng_data *__arm64_get_k_vdso_rnd_data(void) +{ + return (void *)vdso_data + __VDSO_RND_DATA_OFFSET; +} +#define __arch_get_k_vdso_rng_data __arm64_get_k_vdso_rnd_data + static __always_inline void __arm64_update_vsyscall(struct vdso_data *vdata, struct timekeeper *tk) { diff --git a/arch/arm64/kernel/vdso.c b/arch/arm64/kernel/vdso.c index 89b6e7840002..706c9c3a7a50 100644 --- a/arch/arm64/kernel/vdso.c +++ b/arch/arm64/kernel/vdso.c @@ -34,12 +34,6 @@ enum vdso_abi { VDSO_ABI_AA32, }; -enum vvar_pages { - VVAR_DATA_PAGE_OFFSET, - VVAR_TIMENS_PAGE_OFFSET, - VVAR_NR_PAGES, -}; - struct vdso_abi_info { const char *name; const char *vdso_code_start; diff --git a/arch/arm64/kernel/vdso/Makefile b/arch/arm64/kernel/vdso/Makefile index d11da6461278..35685c036044 100644 --- a/arch/arm64/kernel/vdso/Makefile +++ b/arch/arm64/kernel/vdso/Makefile @@ -9,7 +9,7 @@ # Include the generic Makefile to check the built vdso. include $(srctree)/lib/vdso/Makefile -obj-vdso := vgettimeofday.o note.o sigreturn.o +obj-vdso := vgettimeofday.o note.o sigreturn.o vgetrandom.o vgetrandom-chacha.o # Build rules targets := $(obj-vdso) vdso.so vdso.so.dbg @@ -34,19 +34,28 @@ ccflags-y := -fno-common -fno-builtin -fno-stack-protector -ffixed-x18 ccflags-y += -DDISABLE_BRANCH_PROFILING -DBUILD_VDSO # -Wmissing-prototypes and -Wmissing-declarations are removed from -# the CFLAGS of vgettimeofday.c to make possible to build the -# kernel with CONFIG_WERROR enabled. -CFLAGS_REMOVE_vgettimeofday.o = $(CC_FLAGS_FTRACE) -Os $(CC_FLAGS_SCS) \ - $(RANDSTRUCT_CFLAGS) $(GCC_PLUGINS_CFLAGS) \ - $(CC_FLAGS_LTO) $(CC_FLAGS_CFI) \ - -Wmissing-prototypes -Wmissing-declarations +# the CFLAGS to make possible to build the kernel with CONFIG_WERROR enabled. +CC_FLAGS_REMOVE_VDSO := $(CC_FLAGS_FTRACE) -Os $(CC_FLAGS_SCS) \ + $(RANDSTRUCT_CFLAGS) $(GCC_PLUGINS_CFLAGS) \ + $(CC_FLAGS_LTO) $(CC_FLAGS_CFI) \ + -Wmissing-prototypes -Wmissing-declarations -CFLAGS_vgettimeofday.o = -O2 -mcmodel=tiny -fasynchronous-unwind-tables +CC_FLAGS_ADD_VDSO := -O2 -mcmodel=tiny -fasynchronous-unwind-tables + +CFLAGS_REMOVE_vgettimeofday.o = $(CC_FLAGS_REMOVE_VDSO) +CFLAGS_REMOVE_vgetrandom.o = $(CC_FLAGS_REMOVE_VDSO) + +CFLAGS_vgettimeofday.o = $(CC_FLAGS_ADD_VDSO) +CFLAGS_vgetrandom.o = $(CC_FLAGS_ADD_VDSO) ifneq ($(c-gettimeofday-y),) CFLAGS_vgettimeofday.o += -include $(c-gettimeofday-y) endif +ifneq ($(c-getrandom-y),) + CFLAGS_vgetrandom.o += -include $(c-getrandom-y) +endif + targets += vdso.lds CPPFLAGS_vdso.lds += -P -C -U$(ARCH) diff --git a/arch/arm64/kernel/vdso/vdso.lds.S b/arch/arm64/kernel/vdso/vdso.lds.S index 45354f2ddf70..f204a9ddc833 100644 --- a/arch/arm64/kernel/vdso/vdso.lds.S +++ b/arch/arm64/kernel/vdso/vdso.lds.S @@ -11,7 +11,9 @@ #include #include #include +#include #include +#include OUTPUT_FORMAT("elf64-littleaarch64", "elf64-bigaarch64", "elf64-littleaarch64") OUTPUT_ARCH(aarch64) @@ -19,6 +21,7 @@ OUTPUT_ARCH(aarch64) SECTIONS { PROVIDE(_vdso_data = . - __VVAR_PAGES * PAGE_SIZE); + PROVIDE(_vdso_rng_data = _vdso_data + __VDSO_RND_DATA_OFFSET); #ifdef CONFIG_TIME_NS PROVIDE(_timens_data = _vdso_data + PAGE_SIZE); #endif @@ -102,6 +105,7 @@ VERSION __kernel_gettimeofday; __kernel_clock_gettime; __kernel_clock_getres; + __kernel_getrandom; local: *; }; } diff --git a/arch/arm64/kernel/vdso/vgetrandom-chacha.S b/arch/arm64/kernel/vdso/vgetrandom-chacha.S new file mode 100644 index 000000000000..67890b445309 --- /dev/null +++ b/arch/arm64/kernel/vdso/vgetrandom-chacha.S @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include + + .text + +#define state0 v0 +#define state1 v1 +#define state2 v2 +#define state3 v3 +#define copy0 v4 +#define copy0_q q4 +#define copy1 v5 +#define copy2 v6 +#define copy3 v7 +#define copy3_d d7 +#define one_d d16 +#define one_q q16 +#define one_v v16 +#define tmp v17 +#define rot8 v18 + +/* + * ARM64 ChaCha20 implementation meant for vDSO. Produces a given positive + * number of blocks of output with nonce 0, taking an input key and 8-bytes + * counter. Importantly does not spill to the stack. + * + * This implementation avoids d8-d15 because they are callee-save in user + * space. + * + * void __arch_chacha20_blocks_nostack(uint8_t *dst_bytes, + * const uint8_t *key, + * uint32_t *counter, + * size_t nblocks) + * + * x0: output bytes + * x1: 32-byte key input + * x2: 8-byte counter input/output + * x3: number of 64-byte block to write to output + */ +SYM_FUNC_START(__arch_chacha20_blocks_nostack) + + /* copy0 = "expand 32-byte k" */ + mov_q x8, 0x3320646e61707865 + mov_q x9, 0x6b20657479622d32 + mov copy0.d[0], x8 + mov copy0.d[1], x9 + + /* copy1,copy2 = key */ + ld1 { copy1.4s, copy2.4s }, [x1] + /* copy3 = counter || zero nonce */ + ld1 { copy3.2s }, [x2] + + movi one_v.2s, #1 + uzp1 one_v.4s, one_v.4s, one_v.4s + +.Lblock: + /* copy state to auxiliary vectors for the final add after the permute. */ + mov state0.16b, copy0.16b + mov state1.16b, copy1.16b + mov state2.16b, copy2.16b + mov state3.16b, copy3.16b + + mov w4, 20 +.Lpermute: + /* + * Permute one 64-byte block where the state matrix is stored in the four NEON + * registers state0-state3. It performs matrix operations on four words in parallel, + * but requires shuffling to rearrange the words after each round. + */ + +.Ldoubleround: + /* state0 += state1, state3 = rotl32(state3 ^ state0, 16) */ + add state0.4s, state0.4s, state1.4s + eor state3.16b, state3.16b, state0.16b + rev32 state3.8h, state3.8h + + /* state2 += state3, state1 = rotl32(state1 ^ state2, 12) */ + add state2.4s, state2.4s, state3.4s + eor tmp.16b, state1.16b, state2.16b + shl state1.4s, tmp.4s, #12 + sri state1.4s, tmp.4s, #20 + + /* state0 += state1, state3 = rotl32(state3 ^ state0, 8) */ + add state0.4s, state0.4s, state1.4s + eor tmp.16b, state3.16b, state0.16b + shl state3.4s, tmp.4s, #8 + sri state3.4s, tmp.4s, #24 + + /* state2 += state3, state1 = rotl32(state1 ^ state2, 7) */ + add state2.4s, state2.4s, state3.4s + eor tmp.16b, state1.16b, state2.16b + shl state1.4s, tmp.4s, #7 + sri state1.4s, tmp.4s, #25 + + /* state1[0,1,2,3] = state1[1,2,3,0] */ + ext state1.16b, state1.16b, state1.16b, #4 + /* state2[0,1,2,3] = state2[2,3,0,1] */ + ext state2.16b, state2.16b, state2.16b, #8 + /* state3[0,1,2,3] = state3[1,2,3,0] */ + ext state3.16b, state3.16b, state3.16b, #12 + + /* state0 += state1, state3 = rotl32(state3 ^ state0, 16) */ + add state0.4s, state0.4s, state1.4s + eor state3.16b, state3.16b, state0.16b + rev32 state3.8h, state3.8h + + /* state2 += state3, state1 = rotl32(state1 ^ state2, 12) */ + add state2.4s, state2.4s, state3.4s + eor tmp.16b, state1.16b, state2.16b + shl state1.4s, tmp.4s, #12 + sri state1.4s, tmp.4s, #20 + + /* state0 += state1, state3 = rotl32(state3 ^ state0, 8) */ + add state0.4s, state0.4s, state1.4s + eor tmp.16b, state3.16b, state0.16b + shl state3.4s, tmp.4s, #8 + sri state3.4s, tmp.4s, #24 + + /* state2 += state3, state1 = rotl32(state1 ^ state2, 7) */ + add state2.4s, state2.4s, state3.4s + eor tmp.16b, state1.16b, state2.16b + shl state1.4s, tmp.4s, #7 + sri state1.4s, tmp.4s, #25 + + /* state1[0,1,2,3] = state1[3,0,1,2] */ + ext state1.16b, state1.16b, state1.16b, #12 + /* state2[0,1,2,3] = state2[2,3,0,1] */ + ext state2.16b, state2.16b, state2.16b, #8 + /* state3[0,1,2,3] = state3[1,2,3,0] */ + ext state3.16b, state3.16b, state3.16b, #4 + + subs w4, w4, #2 + b.ne .Ldoubleround + + /* output0 = state0 + state0 */ + add state0.4s, state0.4s, copy0.4s + /* output1 = state1 + state1 */ + add state1.4s, state1.4s, copy1.4s + /* output2 = state2 + state2 */ + add state2.4s, state2.4s, copy2.4s + /* output2 = state3 + state3 */ + add state3.4s, state3.4s, copy3.4s + st1 { state0.16b - state3.16b }, [x0] + + /* + * ++copy3.counter, the 'add' clears the upper half of the SIMD register + * which is the expected behaviour here. + */ + add copy3_d, copy3_d, one_d + + /* output += 64, --nblocks */ + add x0, x0, 64 + subs x3, x3, #1 + b.ne .Lblock + + /* counter = copy3.counter */ + st1 { copy3.2s }, [x2] + + /* Zero out the potentially sensitive regs, in case nothing uses these again. */ + movi state0.16b, #0 + movi state1.16b, #0 + movi state2.16b, #0 + movi state3.16b, #0 + movi copy1.16b, #0 + movi copy2.16b, #0 + ret +SYM_FUNC_END(__arch_chacha20_blocks_nostack) + +emit_aarch64_feature_1_and diff --git a/arch/arm64/kernel/vdso/vgetrandom.c b/arch/arm64/kernel/vdso/vgetrandom.c new file mode 100644 index 000000000000..832fe195292b --- /dev/null +++ b/arch/arm64/kernel/vdso/vgetrandom.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +typeof(__cvdso_getrandom) __kernel_getrandom; + +ssize_t __kernel_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state, size_t opaque_len) +{ + if (alternative_has_cap_likely(ARM64_HAS_FPSIMD)) + return __cvdso_getrandom(buffer, len, flags, opaque_state, opaque_len); + + if (unlikely(opaque_len == ~0UL && !buffer && !len && !flags)) + return -ENOSYS; + return getrandom_syscall(buffer, len, flags); +} diff --git a/tools/arch/arm64/vdso b/tools/arch/arm64/vdso new file mode 120000 index 000000000000..233c7a26f6e5 --- /dev/null +++ b/tools/arch/arm64/vdso @@ -0,0 +1 @@ +../../../arch/arm64/kernel/vdso \ No newline at end of file diff --git a/tools/include/linux/compiler.h b/tools/include/linux/compiler.h index 6f7f22ac9da5..4366da278033 100644 --- a/tools/include/linux/compiler.h +++ b/tools/include/linux/compiler.h @@ -2,6 +2,8 @@ #ifndef _TOOLS_LINUX_COMPILER_H_ #define _TOOLS_LINUX_COMPILER_H_ +#ifndef __ASSEMBLY__ + #include #ifndef __compiletime_error @@ -224,4 +226,6 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s __asm__ ("" : "=r" (var) : "0" (var)) #endif +#endif /* __ASSEMBLY__ */ + #endif /* _TOOLS_LINUX_COMPILER_H */ diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index 04930125035e..3c6fafbd83a6 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -9,7 +9,7 @@ ifeq ($(ARCH),$(filter $(ARCH),x86 x86_64)) TEST_GEN_PROGS += vdso_standalone_test_x86 endif TEST_GEN_PROGS += vdso_test_correctness -ifeq ($(ARCH)$(CONFIG_X86_32),$(filter $(ARCH)$(CONFIG_X86_32),x86 x86_64 loongarch)) +ifeq ($(ARCH)$(CONFIG_X86_32),$(filter $(ARCH)$(CONFIG_X86_32),x86 x86_64 loongarch arm64)) TEST_GEN_PROGS += vdso_test_getrandom TEST_GEN_PROGS += vdso_test_chacha endif @@ -40,5 +40,6 @@ $(OUTPUT)/vdso_test_getrandom: CFLAGS += -isystem $(top_srcdir)/tools/include \ $(OUTPUT)/vdso_test_chacha: $(top_srcdir)/tools/arch/$(SRCARCH)/vdso/vgetrandom-chacha.S $(OUTPUT)/vdso_test_chacha: CFLAGS += -idirafter $(top_srcdir)/tools/include \ -idirafter $(top_srcdir)/arch/$(SRCARCH)/include \ + -idirafter $(top_srcdir)/arch/$(SRCARCH)/include/generated \ -idirafter $(top_srcdir)/include \ -D__ASSEMBLY__ -Wa,--noexecstack From 8bc7c5e525584903ea83332e18a2118ed3b1985e Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Tue, 3 Sep 2024 14:52:45 +0200 Subject: [PATCH 539/573] selftests: vDSO: don't include generated headers for chacha test It's not correct to use $(top_srcdir) for generated header files, for builds that are done out of tree via O=, and $(objtree) isn't valid in the selftests context. Instead, just obviate the need for these generated header files by defining empty stubs in tools/include, which is the same thing that's done for rwlock.h. Reviewed-by: Adhemerval Zanella Signed-off-by: Jason A. Donenfeld --- tools/include/generated/asm-offsets.h | 0 tools/include/generated/asm/cpucap-defs.h | 0 tools/include/generated/asm/sysreg-defs.h | 0 tools/testing/selftests/vDSO/Makefile | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tools/include/generated/asm-offsets.h create mode 100644 tools/include/generated/asm/cpucap-defs.h create mode 100644 tools/include/generated/asm/sysreg-defs.h diff --git a/tools/include/generated/asm-offsets.h b/tools/include/generated/asm-offsets.h new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tools/include/generated/asm/cpucap-defs.h b/tools/include/generated/asm/cpucap-defs.h new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tools/include/generated/asm/sysreg-defs.h b/tools/include/generated/asm/sysreg-defs.h new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index 3c6fafbd83a6..610056acc1b7 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -39,7 +39,7 @@ $(OUTPUT)/vdso_test_getrandom: CFLAGS += -isystem $(top_srcdir)/tools/include \ $(OUTPUT)/vdso_test_chacha: $(top_srcdir)/tools/arch/$(SRCARCH)/vdso/vgetrandom-chacha.S $(OUTPUT)/vdso_test_chacha: CFLAGS += -idirafter $(top_srcdir)/tools/include \ + -idirafter $(top_srcdir)/tools/include/generated \ -idirafter $(top_srcdir)/arch/$(SRCARCH)/include \ - -idirafter $(top_srcdir)/arch/$(SRCARCH)/include/generated \ -idirafter $(top_srcdir)/include \ -D__ASSEMBLY__ -Wa,--noexecstack From c73049389e58c01e2e3bbfae900c8daeee177191 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Fri, 6 Sep 2024 10:33:43 +0200 Subject: [PATCH 540/573] powerpc/vdso: Fix VDSO data access when running in a non-root time namespace When running in a non-root time namespace, the global VDSO data page is replaced by a dedicated namespace data page and the global data page is mapped next to it. Detailed explanations can be found at commit 660fd04f9317 ("lib/vdso: Prepare for time namespace support"). When it happens, __kernel_get_syscall_map and __kernel_get_tbfreq and __kernel_sync_dicache don't work anymore because they read 0 instead of the data they need. To address that, clock_mode has to be read. When it is set to VDSO_CLOCKMODE_TIMENS, it means it is a dedicated namespace data page and the global data is located on the following page. Add a macro called get_realdatapage which reads clock_mode and add PAGE_SIZE to the pointer provided by get_datapage macro when clock_mode is equal to VDSO_CLOCKMODE_TIMENS. Use this new macro instead of get_datapage macro except for time functions as they handle it internally. Fixes: 74205b3fc2ef ("powerpc/vdso: Add support for time namespaces") Reported-by: Jason A. Donenfeld Closes: https://lore.kernel.org/all/ZtnYqZI-nrsNslwy@zx2c4.com/ Signed-off-by: Christophe Leroy Acked-by: Michael Ellerman Signed-off-by: Jason A. Donenfeld --- arch/powerpc/include/asm/vdso_datapage.h | 15 +++++++++++++++ arch/powerpc/kernel/asm-offsets.c | 2 ++ arch/powerpc/kernel/vdso/cacheflush.S | 2 +- arch/powerpc/kernel/vdso/datapage.S | 4 ++-- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/arch/powerpc/include/asm/vdso_datapage.h b/arch/powerpc/include/asm/vdso_datapage.h index a585c8e538ff..939daf6b695e 100644 --- a/arch/powerpc/include/asm/vdso_datapage.h +++ b/arch/powerpc/include/asm/vdso_datapage.h @@ -111,6 +111,21 @@ extern struct vdso_arch_data *vdso_data; addi \ptr, \ptr, (_vdso_datapage - 999b)@l .endm +#include +#include + +.macro get_realdatapage ptr scratch + get_datapage \ptr +#ifdef CONFIG_TIME_NS + lwz \scratch, VDSO_CLOCKMODE_OFFSET(\ptr) + xoris \scratch, \scratch, VDSO_CLOCKMODE_TIMENS@h + xori \scratch, \scratch, VDSO_CLOCKMODE_TIMENS@l + cntlzw \scratch, \scratch + rlwinm \scratch, \scratch, PAGE_SHIFT - 5, 1 << PAGE_SHIFT + add \ptr, \ptr, \scratch +#endif +.endm + #endif /* __ASSEMBLY__ */ #endif /* __KERNEL__ */ diff --git a/arch/powerpc/kernel/asm-offsets.c b/arch/powerpc/kernel/asm-offsets.c index 23733282de4d..7b3feb6bc210 100644 --- a/arch/powerpc/kernel/asm-offsets.c +++ b/arch/powerpc/kernel/asm-offsets.c @@ -346,6 +346,8 @@ int main(void) #else OFFSET(CFG_SYSCALL_MAP32, vdso_arch_data, syscall_map); #endif + OFFSET(VDSO_CLOCKMODE_OFFSET, vdso_arch_data, data[0].clock_mode); + DEFINE(VDSO_CLOCKMODE_TIMENS, VDSO_CLOCKMODE_TIMENS); #ifdef CONFIG_BUG DEFINE(BUG_ENTRY_SIZE, sizeof(struct bug_entry)); diff --git a/arch/powerpc/kernel/vdso/cacheflush.S b/arch/powerpc/kernel/vdso/cacheflush.S index 0085ae464dac..3b2479bd2f9a 100644 --- a/arch/powerpc/kernel/vdso/cacheflush.S +++ b/arch/powerpc/kernel/vdso/cacheflush.S @@ -30,7 +30,7 @@ END_FTR_SECTION_IFSET(CPU_FTR_COHERENT_ICACHE) #ifdef CONFIG_PPC64 mflr r12 .cfi_register lr,r12 - get_datapage r10 + get_realdatapage r10, r11 mtlr r12 .cfi_restore lr #endif diff --git a/arch/powerpc/kernel/vdso/datapage.S b/arch/powerpc/kernel/vdso/datapage.S index db8e167f0166..2b19b6201a33 100644 --- a/arch/powerpc/kernel/vdso/datapage.S +++ b/arch/powerpc/kernel/vdso/datapage.S @@ -28,7 +28,7 @@ V_FUNCTION_BEGIN(__kernel_get_syscall_map) mflr r12 .cfi_register lr,r12 mr. r4,r3 - get_datapage r3 + get_realdatapage r3, r11 mtlr r12 #ifdef __powerpc64__ addi r3,r3,CFG_SYSCALL_MAP64 @@ -52,7 +52,7 @@ V_FUNCTION_BEGIN(__kernel_get_tbfreq) .cfi_startproc mflr r12 .cfi_register lr,r12 - get_datapage r3 + get_realdatapage r3, r11 #ifndef __powerpc64__ lwz r4,(CFG_TB_TICKS_PER_SEC + 4)(r3) #endif From d175ee98fe545d2c56df22751314584cce228307 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Mon, 2 Sep 2024 21:17:18 +0200 Subject: [PATCH 541/573] mm: Define VM_DROPPABLE for powerpc/32 Commit 9651fcedf7b9 ("mm: add MAP_DROPPABLE for designating always lazily freeable mappings") only adds VM_DROPPABLE for 64 bits architectures. In order to also use the getrandom vDSO implementation on powerpc/32, use VM_ARCH_1 for VM_DROPPABLE on powerpc/32. This is possible because VM_ARCH_1 is used for VM_SAO on powerpc and VM_SAO is only for powerpc/64. It is used in combination with PROT_SAO in some parts of code that are restricted to CONFIG_PPC64 through #ifdefs, it is therefore possible to define VM_SAO for CONFIG_PPC64 only. Signed-off-by: Christophe Leroy Acked-by: Michael Ellerman Signed-off-by: Jason A. Donenfeld --- fs/proc/task_mmu.c | 4 +++- include/linux/mm.h | 4 +++- include/trace/events/mmflags.h | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index 5f171ad7b436..3a07e13e2f81 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -987,8 +987,10 @@ static void show_smap_vma_flags(struct seq_file *m, struct vm_area_struct *vma) #ifdef CONFIG_X86_USER_SHADOW_STACK [ilog2(VM_SHADOW_STACK)] = "ss", #endif -#ifdef CONFIG_64BIT +#if defined(CONFIG_64BIT) || defined(CONFIG_PPC32) [ilog2(VM_DROPPABLE)] = "dp", +#endif +#ifdef CONFIG_64BIT [ilog2(VM_SEALED)] = "sl", #endif }; diff --git a/include/linux/mm.h b/include/linux/mm.h index 6549d0979b28..028847f39442 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -359,7 +359,7 @@ extern unsigned int kobjsize(const void *objp); #if defined(CONFIG_X86) # define VM_PAT VM_ARCH_1 /* PAT reserves whole VMA at once (x86) */ -#elif defined(CONFIG_PPC) +#elif defined(CONFIG_PPC64) # define VM_SAO VM_ARCH_1 /* Strong Access Ordering (powerpc) */ #elif defined(CONFIG_PARISC) # define VM_GROWSUP VM_ARCH_1 @@ -409,6 +409,8 @@ extern unsigned int kobjsize(const void *objp); #ifdef CONFIG_64BIT #define VM_DROPPABLE_BIT 40 #define VM_DROPPABLE BIT(VM_DROPPABLE_BIT) +#elif defined(CONFIG_PPC32) +#define VM_DROPPABLE VM_ARCH_1 #else #define VM_DROPPABLE VM_NONE #endif diff --git a/include/trace/events/mmflags.h b/include/trace/events/mmflags.h index b63d211bd141..37265977d524 100644 --- a/include/trace/events/mmflags.h +++ b/include/trace/events/mmflags.h @@ -143,7 +143,7 @@ IF_HAVE_PG_ARCH_X(arch_3) #if defined(CONFIG_X86) #define __VM_ARCH_SPECIFIC_1 {VM_PAT, "pat" } -#elif defined(CONFIG_PPC) +#elif defined(CONFIG_PPC64) #define __VM_ARCH_SPECIFIC_1 {VM_SAO, "sao" } #elif defined(CONFIG_PARISC) #define __VM_ARCH_SPECIFIC_1 {VM_GROWSUP, "growsup" } @@ -165,7 +165,7 @@ IF_HAVE_PG_ARCH_X(arch_3) # define IF_HAVE_UFFD_MINOR(flag, name) #endif -#ifdef CONFIG_64BIT +#if defined(CONFIG_64BIT) || defined(CONFIG_PPC32) # define IF_HAVE_VM_DROPPABLE(flag, name) {flag, name}, #else # define IF_HAVE_VM_DROPPABLE(flag, name) From b163596a5b6f27aa6a9eac4198a6c6ec0ce3d5f2 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Mon, 2 Sep 2024 21:17:19 +0200 Subject: [PATCH 542/573] powerpc/vdso32: Add crtsavres Commit 08c18b63d965 ("powerpc/vdso32: Add missing _restgpr_31_x to fix build failure") added _restgpr_31_x to the vdso for gettimeofday, but the work on getrandom shows that we will need more of those functions. Remove _restgpr_31_x and link in crtsavres.o so that we get all save/restore functions when optimising the kernel for size. Signed-off-by: Christophe Leroy Acked-by: Ard Biesheuvel Acked-by: Michael Ellerman Signed-off-by: Jason A. Donenfeld --- arch/powerpc/kernel/vdso/Makefile | 5 ++++- arch/powerpc/kernel/vdso/gettimeofday.S | 13 ------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/arch/powerpc/kernel/vdso/Makefile b/arch/powerpc/kernel/vdso/Makefile index 1425b6edc66b..c07a425b8f78 100644 --- a/arch/powerpc/kernel/vdso/Makefile +++ b/arch/powerpc/kernel/vdso/Makefile @@ -43,6 +43,7 @@ else endif targets := $(obj-vdso32) vdso32.so.dbg vgettimeofday-32.o +targets += crtsavres-32.o obj-vdso32 := $(addprefix $(obj)/, $(obj-vdso32)) targets += $(obj-vdso64) vdso64.so.dbg vgettimeofday-64.o obj-vdso64 := $(addprefix $(obj)/, $(obj-vdso64)) @@ -68,7 +69,7 @@ targets += vdso64.lds CPPFLAGS_vdso64.lds += -P -C # link rule for the .so file, .lds has to be first -$(obj)/vdso32.so.dbg: $(obj)/vdso32.lds $(obj-vdso32) $(obj)/vgettimeofday-32.o FORCE +$(obj)/vdso32.so.dbg: $(obj)/vdso32.lds $(obj-vdso32) $(obj)/vgettimeofday-32.o $(obj)/crtsavres-32.o FORCE $(call if_changed,vdso32ld_and_check) $(obj)/vdso64.so.dbg: $(obj)/vdso64.lds $(obj-vdso64) $(obj)/vgettimeofday-64.o FORCE $(call if_changed,vdso64ld_and_check) @@ -76,6 +77,8 @@ $(obj)/vdso64.so.dbg: $(obj)/vdso64.lds $(obj-vdso64) $(obj)/vgettimeofday-64.o # assembly rules for the .S files $(obj-vdso32): %-32.o: %.S FORCE $(call if_changed_dep,vdso32as) +$(obj)/crtsavres-32.o: %-32.o: $(srctree)/arch/powerpc/lib/crtsavres.S FORCE + $(call if_changed_dep,vdso32as) $(obj)/vgettimeofday-32.o: %-32.o: %.c FORCE $(call if_changed_dep,vdso32cc) $(obj-vdso64): %-64.o: %.S FORCE diff --git a/arch/powerpc/kernel/vdso/gettimeofday.S b/arch/powerpc/kernel/vdso/gettimeofday.S index 48fc6658053a..67254ac9c8bb 100644 --- a/arch/powerpc/kernel/vdso/gettimeofday.S +++ b/arch/powerpc/kernel/vdso/gettimeofday.S @@ -118,16 +118,3 @@ V_FUNCTION_END(__kernel_clock_getres) V_FUNCTION_BEGIN(__kernel_time) cvdso_call __c_kernel_time call_time=1 V_FUNCTION_END(__kernel_time) - -/* Routines for restoring integer registers, called by the compiler. */ -/* Called with r11 pointing to the stack header word of the caller of the */ -/* function, just beyond the end of the integer restore area. */ -#ifndef __powerpc64__ -_GLOBAL(_restgpr_31_x) -_GLOBAL(_rest32gpr_31_x) - lwz r0,4(r11) - lwz r31,-4(r11) - mtlr r0 - mr r1,r11 - blr -#endif From a6b67eb09963af29991625862cbb4f56b85954ed Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Mon, 2 Sep 2024 21:17:20 +0200 Subject: [PATCH 543/573] powerpc/vdso: Refactor CFLAGS for CVDSO build In order to avoid two much duplication when we add new VDSO functionnalities in C like getrandom, refactor common CFLAGS. Signed-off-by: Christophe Leroy Acked-by: Michael Ellerman Signed-off-by: Jason A. Donenfeld --- arch/powerpc/kernel/vdso/Makefile | 32 +++++++++++++------------------ 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/arch/powerpc/kernel/vdso/Makefile b/arch/powerpc/kernel/vdso/Makefile index c07a425b8f78..67fe79d26fae 100644 --- a/arch/powerpc/kernel/vdso/Makefile +++ b/arch/powerpc/kernel/vdso/Makefile @@ -10,28 +10,11 @@ obj-vdso64 = sigtramp64-64.o gettimeofday-64.o datapage-64.o cacheflush-64.o not ifneq ($(c-gettimeofday-y),) CFLAGS_vgettimeofday-32.o += -include $(c-gettimeofday-y) - CFLAGS_vgettimeofday-32.o += $(DISABLE_LATENT_ENTROPY_PLUGIN) - CFLAGS_vgettimeofday-32.o += $(call cc-option, -fno-stack-protector) - CFLAGS_vgettimeofday-32.o += -DDISABLE_BRANCH_PROFILING - CFLAGS_vgettimeofday-32.o += -ffreestanding -fasynchronous-unwind-tables - CFLAGS_REMOVE_vgettimeofday-32.o = $(CC_FLAGS_FTRACE) - CFLAGS_REMOVE_vgettimeofday-32.o += -mcmodel=medium -mabi=elfv1 -mabi=elfv2 -mcall-aixdesc - # This flag is supported by clang for 64-bit but not 32-bit so it will cause - # an unused command line flag warning for this file. - ifdef CONFIG_CC_IS_CLANG - CFLAGS_REMOVE_vgettimeofday-32.o += -fno-stack-clash-protection - endif - CFLAGS_vgettimeofday-64.o += -include $(c-gettimeofday-y) - CFLAGS_vgettimeofday-64.o += $(DISABLE_LATENT_ENTROPY_PLUGIN) - CFLAGS_vgettimeofday-64.o += $(call cc-option, -fno-stack-protector) - CFLAGS_vgettimeofday-64.o += -DDISABLE_BRANCH_PROFILING - CFLAGS_vgettimeofday-64.o += -ffreestanding -fasynchronous-unwind-tables - CFLAGS_REMOVE_vgettimeofday-64.o = $(CC_FLAGS_FTRACE) # Go prior to 1.16.x assumes r30 is not clobbered by any VDSO code. That used to be true # by accident when the VDSO was hand-written asm code, but may not be now that the VDSO is # compiler generated. To avoid breaking Go tell GCC not to use r30. Impact on code # generation is minimal, it will just use r29 instead. - CFLAGS_vgettimeofday-64.o += $(call cc-option, -ffixed-r30) + CFLAGS_vgettimeofday-64.o += -include $(c-gettimeofday-y) $(call cc-option, -ffixed-r30) endif # Build rules @@ -49,6 +32,11 @@ targets += $(obj-vdso64) vdso64.so.dbg vgettimeofday-64.o obj-vdso64 := $(addprefix $(obj)/, $(obj-vdso64)) ccflags-y := -fno-common -fno-builtin +ccflags-y += $(DISABLE_LATENT_ENTROPY_PLUGIN) +ccflags-y += $(call cc-option, -fno-stack-protector) +ccflags-y += -DDISABLE_BRANCH_PROFILING +ccflags-y += -ffreestanding -fasynchronous-unwind-tables +ccflags-remove-y := $(CC_FLAGS_FTRACE) ldflags-y := -Wl,--hash-style=both -nostdlib -shared -z noexecstack $(CLANG_FLAGS) ldflags-$(CONFIG_LD_IS_LLD) += $(call cc-option,--ld-path=$(LD),-fuse-ld=lld) ldflags-$(CONFIG_LD_ORPHAN_WARN) += -Wl,--orphan-handling=$(CONFIG_LD_ORPHAN_WARN_LEVEL) @@ -57,6 +45,12 @@ ldflags-$(CONFIG_LD_ORPHAN_WARN) += -Wl,--orphan-handling=$(CONFIG_LD_ORPHAN_WAR ldflags-y += $(filter-out $(CC_AUTO_VAR_INIT_ZERO_ENABLER) $(CC_FLAGS_FTRACE) -Wa$(comma)%, $(KBUILD_CFLAGS)) CC32FLAGS := -m32 +CC32FLAGSREMOVE := -mcmodel=medium -mabi=elfv1 -mabi=elfv2 -mcall-aixdesc + # This flag is supported by clang for 64-bit but not 32-bit so it will cause + # an unused command line flag warning for this file. +ifdef CONFIG_CC_IS_CLANG +CC32FLAGSREMOVE += -fno-stack-clash-protection +endif LD32FLAGS := -Wl,-soname=linux-vdso32.so.1 AS32FLAGS := -D__VDSO32__ @@ -105,7 +99,7 @@ quiet_cmd_vdso32ld_and_check = VDSO32L $@ quiet_cmd_vdso32as = VDSO32A $@ cmd_vdso32as = $(VDSOCC) $(a_flags) $(CC32FLAGS) $(AS32FLAGS) -c -o $@ $< quiet_cmd_vdso32cc = VDSO32C $@ - cmd_vdso32cc = $(VDSOCC) $(c_flags) $(CC32FLAGS) -c -o $@ $< + cmd_vdso32cc = $(VDSOCC) $(filter-out $(CC32FLAGSREMOVE), $(c_flags)) $(CC32FLAGS) -c -o $@ $< quiet_cmd_vdso64ld_and_check = VDSO64L $@ cmd_vdso64ld_and_check = $(VDSOCC) $(ldflags-y) $(LD64FLAGS) -o $@ -Wl,-T$(filter %.lds,$^) $(filter %.o,$^); $(cmd_vdso_check) From 53cee505ae0c4d19a2c894501a363782410c4a29 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Mon, 2 Sep 2024 21:17:21 +0200 Subject: [PATCH 544/573] powerpc/vdso: Wire up getrandom() vDSO implementation on VDSO32 To be consistent with other VDSO functions, the function is called __kernel_getrandom() __arch_chacha20_blocks_nostack() fonction is implemented basically with 32 bits operations. It performs 4 QUARTERROUND operations in parallele. There are enough registers to avoid using the stack: On input: r3: output bytes r4: 32-byte key input r5: 8-byte counter input/output r6: number of 64-byte blocks to write to output During operation: stack: pointer to counter (r5) and non-volatile registers (r14-131) r0: counter of blocks (initialised with r6) r4: Value '4' after key has been read, used for indexing r5-r12: key r14-r15: block counter r16-r31: chacha state At the end: r0, r6-r12: Zeroised r5, r14-r31: Restored Performance on powerpc 885 (using kernel selftest): ~# ./vdso_test_getrandom bench-single vdso: 25000000 times in 62.938002291 seconds libc: 25000000 times in 535.581916866 seconds syscall: 25000000 times in 531.525042806 seconds Performance on powerpc 8321 (using kernel selftest): ~# ./vdso_test_getrandom bench-single vdso: 25000000 times in 16.899318858 seconds libc: 25000000 times in 131.050596522 seconds syscall: 25000000 times in 129.794790389 seconds This first patch adds support for VDSO32. As selftests cannot easily be generated only for VDSO32, and because the following patch brings support for VDSO64 anyway, this patch opts out all code in __arch_chacha20_blocks_nostack() so that vdso_test_chacha will not fail to compile and will not crash on PPC64/PPC64LE, allthough the selftest itself will fail. Signed-off-by: Christophe Leroy Acked-by: Michael Ellerman Signed-off-by: Jason A. Donenfeld --- arch/powerpc/Kconfig | 1 + arch/powerpc/include/asm/mman.h | 2 +- arch/powerpc/include/asm/vdso/getrandom.h | 54 ++++ arch/powerpc/include/asm/vdso/vsyscall.h | 6 + arch/powerpc/include/asm/vdso_datapage.h | 2 + arch/powerpc/kernel/asm-offsets.c | 1 + arch/powerpc/kernel/vdso/Makefile | 14 +- arch/powerpc/kernel/vdso/getrandom.S | 50 +++ arch/powerpc/kernel/vdso/vdso32.lds.S | 1 + arch/powerpc/kernel/vdso/vgetrandom-chacha.S | 312 +++++++++++++++++++ arch/powerpc/kernel/vdso/vgetrandom.c | 14 + tools/arch/powerpc/vdso | 1 + tools/testing/selftests/vDSO/Makefile | 2 +- 13 files changed, 455 insertions(+), 5 deletions(-) create mode 100644 arch/powerpc/include/asm/vdso/getrandom.h create mode 100644 arch/powerpc/kernel/vdso/getrandom.S create mode 100644 arch/powerpc/kernel/vdso/vgetrandom-chacha.S create mode 100644 arch/powerpc/kernel/vdso/vgetrandom.c create mode 120000 tools/arch/powerpc/vdso diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig index d7b09b064a8a..e500a59ddecc 100644 --- a/arch/powerpc/Kconfig +++ b/arch/powerpc/Kconfig @@ -311,6 +311,7 @@ config PPC select SYSCTL_EXCEPTION_TRACE select THREAD_INFO_IN_TASK select TRACE_IRQFLAGS_SUPPORT + select VDSO_GETRANDOM if VDSO32 # # Please keep this list sorted alphabetically. # diff --git a/arch/powerpc/include/asm/mman.h b/arch/powerpc/include/asm/mman.h index 17a77d47ed6d..42a51a993d94 100644 --- a/arch/powerpc/include/asm/mman.h +++ b/arch/powerpc/include/asm/mman.h @@ -6,7 +6,7 @@ #include -#ifdef CONFIG_PPC64 +#if defined(CONFIG_PPC64) && !defined(BUILD_VDSO) #include #include diff --git a/arch/powerpc/include/asm/vdso/getrandom.h b/arch/powerpc/include/asm/vdso/getrandom.h new file mode 100644 index 000000000000..501d6bb14e8a --- /dev/null +++ b/arch/powerpc/include/asm/vdso/getrandom.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2024 Christophe Leroy , CS GROUP France + */ +#ifndef _ASM_POWERPC_VDSO_GETRANDOM_H +#define _ASM_POWERPC_VDSO_GETRANDOM_H + +#ifndef __ASSEMBLY__ + +static __always_inline int do_syscall_3(const unsigned long _r0, const unsigned long _r3, + const unsigned long _r4, const unsigned long _r5) +{ + register long r0 asm("r0") = _r0; + register unsigned long r3 asm("r3") = _r3; + register unsigned long r4 asm("r4") = _r4; + register unsigned long r5 asm("r5") = _r5; + register int ret asm ("r3"); + + asm volatile( + " sc\n" + " bns+ 1f\n" + " neg %0, %0\n" + "1:\n" + : "=r" (ret), "+r" (r4), "+r" (r5), "+r" (r0) + : "r" (r3) + : "memory", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "cr0", "ctr"); + + return ret; +} + +/** + * getrandom_syscall - Invoke the getrandom() syscall. + * @buffer: Destination buffer to fill with random bytes. + * @len: Size of @buffer in bytes. + * @flags: Zero or more GRND_* flags. + * Returns: The number of bytes written to @buffer, or a negative value indicating an error. + */ +static __always_inline ssize_t getrandom_syscall(void *buffer, size_t len, unsigned int flags) +{ + return do_syscall_3(__NR_getrandom, (unsigned long)buffer, + (unsigned long)len, (unsigned long)flags); +} + +static __always_inline struct vdso_rng_data *__arch_get_vdso_rng_data(void) +{ + return NULL; +} + +ssize_t __c_kernel_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state, + size_t opaque_len, const struct vdso_rng_data *vd); + +#endif /* !__ASSEMBLY__ */ + +#endif /* _ASM_POWERPC_VDSO_GETRANDOM_H */ diff --git a/arch/powerpc/include/asm/vdso/vsyscall.h b/arch/powerpc/include/asm/vdso/vsyscall.h index 48cf23f1e273..92f480d8cc6d 100644 --- a/arch/powerpc/include/asm/vdso/vsyscall.h +++ b/arch/powerpc/include/asm/vdso/vsyscall.h @@ -17,6 +17,12 @@ struct vdso_data *__arch_get_k_vdso_data(void) } #define __arch_get_k_vdso_data __arch_get_k_vdso_data +static __always_inline +struct vdso_rng_data *__arch_get_k_vdso_rng_data(void) +{ + return &vdso_data->rng_data; +} + /* The asm-generic header needs to be included after the definitions above */ #include diff --git a/arch/powerpc/include/asm/vdso_datapage.h b/arch/powerpc/include/asm/vdso_datapage.h index 939daf6b695e..248dee138f7b 100644 --- a/arch/powerpc/include/asm/vdso_datapage.h +++ b/arch/powerpc/include/asm/vdso_datapage.h @@ -83,6 +83,7 @@ struct vdso_arch_data { __u32 compat_syscall_map[SYSCALL_MAP_SIZE]; /* Map of compat syscalls */ struct vdso_data data[CS_BASES]; + struct vdso_rng_data rng_data; }; #else /* CONFIG_PPC64 */ @@ -95,6 +96,7 @@ struct vdso_arch_data { __u32 syscall_map[SYSCALL_MAP_SIZE]; /* Map of syscalls */ __u32 compat_syscall_map[0]; /* No compat syscalls on PPC32 */ struct vdso_data data[CS_BASES]; + struct vdso_rng_data rng_data; }; #endif /* CONFIG_PPC64 */ diff --git a/arch/powerpc/kernel/asm-offsets.c b/arch/powerpc/kernel/asm-offsets.c index 7b3feb6bc210..131a8cc10dbe 100644 --- a/arch/powerpc/kernel/asm-offsets.c +++ b/arch/powerpc/kernel/asm-offsets.c @@ -335,6 +335,7 @@ int main(void) /* datapage offsets for use by vdso */ OFFSET(VDSO_DATA_OFFSET, vdso_arch_data, data); + OFFSET(VDSO_RNG_DATA_OFFSET, vdso_arch_data, rng_data); OFFSET(CFG_TB_TICKS_PER_SEC, vdso_arch_data, tb_ticks_per_sec); #ifdef CONFIG_PPC64 OFFSET(CFG_ICACHE_BLOCKSZ, vdso_arch_data, icache_block_size); diff --git a/arch/powerpc/kernel/vdso/Makefile b/arch/powerpc/kernel/vdso/Makefile index 67fe79d26fae..7a4a935406d8 100644 --- a/arch/powerpc/kernel/vdso/Makefile +++ b/arch/powerpc/kernel/vdso/Makefile @@ -8,6 +8,8 @@ include $(srctree)/lib/vdso/Makefile obj-vdso32 = sigtramp32-32.o gettimeofday-32.o datapage-32.o cacheflush-32.o note-32.o getcpu-32.o obj-vdso64 = sigtramp64-64.o gettimeofday-64.o datapage-64.o cacheflush-64.o note-64.o getcpu-64.o +obj-vdso32 += getrandom-32.o vgetrandom-chacha-32.o + ifneq ($(c-gettimeofday-y),) CFLAGS_vgettimeofday-32.o += -include $(c-gettimeofday-y) # Go prior to 1.16.x assumes r30 is not clobbered by any VDSO code. That used to be true @@ -17,6 +19,10 @@ ifneq ($(c-gettimeofday-y),) CFLAGS_vgettimeofday-64.o += -include $(c-gettimeofday-y) $(call cc-option, -ffixed-r30) endif +ifneq ($(c-getrandom-y),) + CFLAGS_vgetrandom-32.o += -include $(c-getrandom-y) +endif + # Build rules ifdef CROSS32_COMPILE @@ -25,13 +31,13 @@ else VDSOCC := $(CC) endif -targets := $(obj-vdso32) vdso32.so.dbg vgettimeofday-32.o +targets := $(obj-vdso32) vdso32.so.dbg vgettimeofday-32.o vgetrandom-32.o targets += crtsavres-32.o obj-vdso32 := $(addprefix $(obj)/, $(obj-vdso32)) targets += $(obj-vdso64) vdso64.so.dbg vgettimeofday-64.o obj-vdso64 := $(addprefix $(obj)/, $(obj-vdso64)) -ccflags-y := -fno-common -fno-builtin +ccflags-y := -fno-common -fno-builtin -DBUILD_VDSO ccflags-y += $(DISABLE_LATENT_ENTROPY_PLUGIN) ccflags-y += $(call cc-option, -fno-stack-protector) ccflags-y += -DDISABLE_BRANCH_PROFILING @@ -63,7 +69,7 @@ targets += vdso64.lds CPPFLAGS_vdso64.lds += -P -C # link rule for the .so file, .lds has to be first -$(obj)/vdso32.so.dbg: $(obj)/vdso32.lds $(obj-vdso32) $(obj)/vgettimeofday-32.o $(obj)/crtsavres-32.o FORCE +$(obj)/vdso32.so.dbg: $(obj)/vdso32.lds $(obj-vdso32) $(obj)/vgettimeofday-32.o $(obj)/vgetrandom-32.o $(obj)/crtsavres-32.o FORCE $(call if_changed,vdso32ld_and_check) $(obj)/vdso64.so.dbg: $(obj)/vdso64.lds $(obj-vdso64) $(obj)/vgettimeofday-64.o FORCE $(call if_changed,vdso64ld_and_check) @@ -75,6 +81,8 @@ $(obj)/crtsavres-32.o: %-32.o: $(srctree)/arch/powerpc/lib/crtsavres.S FORCE $(call if_changed_dep,vdso32as) $(obj)/vgettimeofday-32.o: %-32.o: %.c FORCE $(call if_changed_dep,vdso32cc) +$(obj)/vgetrandom-32.o: %-32.o: %.c FORCE + $(call if_changed_dep,vdso32cc) $(obj-vdso64): %-64.o: %.S FORCE $(call if_changed_dep,vdso64as) $(obj)/vgettimeofday-64.o: %-64.o: %.c FORCE diff --git a/arch/powerpc/kernel/vdso/getrandom.S b/arch/powerpc/kernel/vdso/getrandom.S new file mode 100644 index 000000000000..f614bb90ab57 --- /dev/null +++ b/arch/powerpc/kernel/vdso/getrandom.S @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Userland implementation of getrandom() for processes + * for use in the vDSO + * + * Copyright (C) 2024 Christophe Leroy , CS GROUP France + */ +#include +#include +#include +#include +#include +#include + +/* + * The macro sets two stack frames, one for the caller and one for the callee + * because there are no requirement for the caller to set a stack frame when + * calling VDSO so it may have omitted to set one, especially on PPC64 + */ + +.macro cvdso_call funct + .cfi_startproc + PPC_STLU r1, -PPC_MIN_STKFRM(r1) + .cfi_adjust_cfa_offset PPC_MIN_STKFRM + mflr r0 + PPC_STLU r1, -PPC_MIN_STKFRM(r1) + .cfi_adjust_cfa_offset PPC_MIN_STKFRM + PPC_STL r0, PPC_MIN_STKFRM + PPC_LR_STKOFF(r1) + .cfi_rel_offset lr, PPC_MIN_STKFRM + PPC_LR_STKOFF + get_realdatapage r8, r11 + addi r8, r8, VDSO_RNG_DATA_OFFSET + bl CFUNC(DOTSYM(\funct)) + PPC_LL r0, PPC_MIN_STKFRM + PPC_LR_STKOFF(r1) + cmpwi r3, 0 + mtlr r0 + addi r1, r1, 2 * PPC_MIN_STKFRM + .cfi_restore lr + .cfi_def_cfa_offset 0 + crclr so + bgelr+ + crset so + neg r3, r3 + blr + .cfi_endproc +.endm + + .text +V_FUNCTION_BEGIN(__kernel_getrandom) + cvdso_call __c_kernel_getrandom +V_FUNCTION_END(__kernel_getrandom) diff --git a/arch/powerpc/kernel/vdso/vdso32.lds.S b/arch/powerpc/kernel/vdso/vdso32.lds.S index 426e1ccc6971..71f086a015df 100644 --- a/arch/powerpc/kernel/vdso/vdso32.lds.S +++ b/arch/powerpc/kernel/vdso/vdso32.lds.S @@ -128,6 +128,7 @@ VERSION #if defined(CONFIG_PPC64) || !defined(CONFIG_SMP) __kernel_getcpu; #endif + __kernel_getrandom; local: *; }; diff --git a/arch/powerpc/kernel/vdso/vgetrandom-chacha.S b/arch/powerpc/kernel/vdso/vgetrandom-chacha.S new file mode 100644 index 000000000000..ac85788205cb --- /dev/null +++ b/arch/powerpc/kernel/vdso/vgetrandom-chacha.S @@ -0,0 +1,312 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2024 Christophe Leroy , CS GROUP France + */ + +#include + +#include + +#define dst_bytes r3 +#define key r4 +#define counter r5 +#define nblocks r6 + +#define idx_r0 r0 +#define val4 r4 + +#define const0 0x61707865 +#define const1 0x3320646e +#define const2 0x79622d32 +#define const3 0x6b206574 + +#define key0 r5 +#define key1 r6 +#define key2 r7 +#define key3 r8 +#define key4 r9 +#define key5 r10 +#define key6 r11 +#define key7 r12 + +#define counter0 r14 +#define counter1 r15 + +#define state0 r16 +#define state1 r17 +#define state2 r18 +#define state3 r19 +#define state4 r20 +#define state5 r21 +#define state6 r22 +#define state7 r23 +#define state8 r24 +#define state9 r25 +#define state10 r26 +#define state11 r27 +#define state12 r28 +#define state13 r29 +#define state14 r30 +#define state15 r31 + +.macro quarterround4 a1 b1 c1 d1 a2 b2 c2 d2 a3 b3 c3 d3 a4 b4 c4 d4 + add \a1, \a1, \b1 + add \a2, \a2, \b2 + add \a3, \a3, \b3 + add \a4, \a4, \b4 + xor \d1, \d1, \a1 + xor \d2, \d2, \a2 + xor \d3, \d3, \a3 + xor \d4, \d4, \a4 + rotlwi \d1, \d1, 16 + rotlwi \d2, \d2, 16 + rotlwi \d3, \d3, 16 + rotlwi \d4, \d4, 16 + add \c1, \c1, \d1 + add \c2, \c2, \d2 + add \c3, \c3, \d3 + add \c4, \c4, \d4 + xor \b1, \b1, \c1 + xor \b2, \b2, \c2 + xor \b3, \b3, \c3 + xor \b4, \b4, \c4 + rotlwi \b1, \b1, 12 + rotlwi \b2, \b2, 12 + rotlwi \b3, \b3, 12 + rotlwi \b4, \b4, 12 + add \a1, \a1, \b1 + add \a2, \a2, \b2 + add \a3, \a3, \b3 + add \a4, \a4, \b4 + xor \d1, \d1, \a1 + xor \d2, \d2, \a2 + xor \d3, \d3, \a3 + xor \d4, \d4, \a4 + rotlwi \d1, \d1, 8 + rotlwi \d2, \d2, 8 + rotlwi \d3, \d3, 8 + rotlwi \d4, \d4, 8 + add \c1, \c1, \d1 + add \c2, \c2, \d2 + add \c3, \c3, \d3 + add \c4, \c4, \d4 + xor \b1, \b1, \c1 + xor \b2, \b2, \c2 + xor \b3, \b3, \c3 + xor \b4, \b4, \c4 + rotlwi \b1, \b1, 7 + rotlwi \b2, \b2, 7 + rotlwi \b3, \b3, 7 + rotlwi \b4, \b4, 7 +.endm + +#define QUARTERROUND4(a1,b1,c1,d1,a2,b2,c2,d2,a3,b3,c3,d3,a4,b4,c4,d4) \ + quarterround4 state##a1 state##b1 state##c1 state##d1 \ + state##a2 state##b2 state##c2 state##d2 \ + state##a3 state##b3 state##c3 state##d3 \ + state##a4 state##b4 state##c4 state##d4 + +/* + * Very basic 32 bits implementation of ChaCha20. Produces a given positive number + * of blocks of output with a nonce of 0, taking an input key and 8-byte + * counter. Importantly does not spill to the stack. Its arguments are: + * + * r3: output bytes + * r4: 32-byte key input + * r5: 8-byte counter input/output (saved on stack) + * r6: number of 64-byte blocks to write to output + * + * r0: counter of blocks (initialised with r6) + * r4: Value '4' after key has been read. + * r5-r12: key + * r14-r15: counter + * r16-r31: state + */ +SYM_FUNC_START(__arch_chacha20_blocks_nostack) +#ifdef __powerpc64__ +#else + stwu r1, -96(r1) + stw counter, 20(r1) +#ifdef __BIG_ENDIAN__ + stmw r14, 24(r1) +#else + stw r14, 24(r1) + stw r15, 28(r1) + stw r16, 32(r1) + stw r17, 36(r1) + stw r18, 40(r1) + stw r19, 44(r1) + stw r20, 48(r1) + stw r21, 52(r1) + stw r22, 56(r1) + stw r23, 60(r1) + stw r24, 64(r1) + stw r25, 68(r1) + stw r26, 72(r1) + stw r27, 76(r1) + stw r28, 80(r1) + stw r29, 84(r1) + stw r30, 88(r1) + stw r31, 92(r1) +#endif + + lwz counter0, 0(counter) + lwz counter1, 4(counter) + mr idx_r0, nblocks + subi dst_bytes, dst_bytes, 4 + + lwz key0, 0(key) + lwz key1, 4(key) + lwz key2, 8(key) + lwz key3, 12(key) + lwz key4, 16(key) + lwz key5, 20(key) + lwz key6, 24(key) + lwz key7, 28(key) + + li val4, 4 +.Lblock: + li r31, 10 + + lis state0, const0@ha + lis state1, const1@ha + lis state2, const2@ha + lis state3, const3@ha + addi state0, state0, const0@l + addi state1, state1, const1@l + addi state2, state2, const2@l + addi state3, state3, const3@l + + mtctr r31 + + mr state4, key0 + mr state5, key1 + mr state6, key2 + mr state7, key3 + mr state8, key4 + mr state9, key5 + mr state10, key6 + mr state11, key7 + + mr state12, counter0 + mr state13, counter1 + + li state14, 0 + li state15, 0 + +.Lpermute: + QUARTERROUND4( 0, 4, 8,12, 1, 5, 9,13, 2, 6,10,14, 3, 7,11,15) + QUARTERROUND4( 0, 5,10,15, 1, 6,11,12, 2, 7, 8,13, 3, 4, 9,14) + + bdnz .Lpermute + + addis state0, state0, const0@ha + addis state1, state1, const1@ha + addis state2, state2, const2@ha + addis state3, state3, const3@ha + addi state0, state0, const0@l + addi state1, state1, const1@l + addi state2, state2, const2@l + addi state3, state3, const3@l + + add state4, state4, key0 + add state5, state5, key1 + add state6, state6, key2 + add state7, state7, key3 + add state8, state8, key4 + add state9, state9, key5 + add state10, state10, key6 + add state11, state11, key7 + + add state12, state12, counter0 + add state13, state13, counter1 + +#ifdef __BIG_ENDIAN__ + stwbrx state0, val4, dst_bytes + addi dst_bytes, dst_bytes, 8 + stwbrx state1, 0, dst_bytes + stwbrx state2, val4, dst_bytes + addi dst_bytes, dst_bytes, 8 + stwbrx state3, 0, dst_bytes + stwbrx state4, val4, dst_bytes + addi dst_bytes, dst_bytes, 8 + stwbrx state5, 0, dst_bytes + stwbrx state6, val4, dst_bytes + addi dst_bytes, dst_bytes, 8 + stwbrx state7, 0, dst_bytes + stwbrx state8, val4, dst_bytes + addi dst_bytes, dst_bytes, 8 + stwbrx state9, 0, dst_bytes + stwbrx state10, val4, dst_bytes + addi dst_bytes, dst_bytes, 8 + stwbrx state11, 0, dst_bytes + stwbrx state12, val4, dst_bytes + addi dst_bytes, dst_bytes, 8 + stwbrx state13, 0, dst_bytes + stwbrx state14, val4, dst_bytes + addi dst_bytes, dst_bytes, 8 + stwbrx state15, 0, dst_bytes +#else + stw state0, 4(dst_bytes) + stw state1, 8(dst_bytes) + stw state2, 12(dst_bytes) + stw state3, 16(dst_bytes) + stw state4, 20(dst_bytes) + stw state5, 24(dst_bytes) + stw state6, 28(dst_bytes) + stw state7, 32(dst_bytes) + stw state8, 36(dst_bytes) + stw state9, 40(dst_bytes) + stw state10, 44(dst_bytes) + stw state11, 48(dst_bytes) + stw state12, 52(dst_bytes) + stw state13, 56(dst_bytes) + stw state14, 60(dst_bytes) + stwu state15, 64(dst_bytes) +#endif + + subic. idx_r0, idx_r0, 1 /* subi. can't use r0 as source */ + + addic counter0, counter0, 1 + addze counter1, counter1 + + bne .Lblock + + lwz counter, 20(r1) + stw counter0, 0(counter) + stw counter1, 4(counter) + + li r6, 0 + li r7, 0 + li r8, 0 + li r9, 0 + li r10, 0 + li r11, 0 + li r12, 0 + +#ifdef __BIG_ENDIAN__ + lmw r14, 24(r1) +#else + lwz r14, 24(r1) + lwz r15, 28(r1) + lwz r16, 32(r1) + lwz r17, 36(r1) + lwz r18, 40(r1) + lwz r19, 44(r1) + lwz r20, 48(r1) + lwz r21, 52(r1) + lwz r22, 56(r1) + lwz r23, 60(r1) + lwz r24, 64(r1) + lwz r25, 68(r1) + lwz r26, 72(r1) + lwz r27, 76(r1) + lwz r28, 80(r1) + lwz r29, 84(r1) + lwz r30, 88(r1) + lwz r31, 92(r1) +#endif + addi r1, r1, 96 +#endif /* __powerpc64__ */ + blr +SYM_FUNC_END(__arch_chacha20_blocks_nostack) diff --git a/arch/powerpc/kernel/vdso/vgetrandom.c b/arch/powerpc/kernel/vdso/vgetrandom.c new file mode 100644 index 000000000000..5f855d45fb7b --- /dev/null +++ b/arch/powerpc/kernel/vdso/vgetrandom.c @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Powerpc userspace implementation of getrandom() + * + * Copyright (C) 2024 Christophe Leroy , CS GROUP France + */ +#include +#include + +ssize_t __c_kernel_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state, + size_t opaque_len, const struct vdso_rng_data *vd) +{ + return __cvdso_getrandom_data(vd, buffer, len, flags, opaque_state, opaque_len); +} diff --git a/tools/arch/powerpc/vdso b/tools/arch/powerpc/vdso new file mode 120000 index 000000000000..4e676d1d1cb4 --- /dev/null +++ b/tools/arch/powerpc/vdso @@ -0,0 +1 @@ +../../../arch/powerpc/kernel/vdso \ No newline at end of file diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index 610056acc1b7..86ebc4115eda 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -9,7 +9,7 @@ ifeq ($(ARCH),$(filter $(ARCH),x86 x86_64)) TEST_GEN_PROGS += vdso_standalone_test_x86 endif TEST_GEN_PROGS += vdso_test_correctness -ifeq ($(ARCH)$(CONFIG_X86_32),$(filter $(ARCH)$(CONFIG_X86_32),x86 x86_64 loongarch arm64)) +ifeq ($(ARCH)$(CONFIG_X86_32),$(filter $(ARCH)$(CONFIG_X86_32),x86 x86_64 loongarch arm64 powerpc)) TEST_GEN_PROGS += vdso_test_getrandom TEST_GEN_PROGS += vdso_test_chacha endif From 8072b39c3a75b63bc08737a74b24c263b7909ba0 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Mon, 2 Sep 2024 21:17:22 +0200 Subject: [PATCH 545/573] powerpc/vdso: Wire up getrandom() vDSO implementation on VDSO64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend getrandom() vDSO implementation to VDSO64. Tested on QEMU on both ppc64_defconfig and ppc64le_defconfig. Results from a Power9 (PowerNV): ~ # ./vdso_test_getrandom bench-single    vdso: 25000000 times in 0.787943615 seconds    libc: 25000000 times in 14.101887252 seconds    syscall: 25000000 times in 14.047475082 seconds Signed-off-by: Christophe Leroy Tested-by: Madhavan Srinivasan Acked-by: Michael Ellerman Signed-off-by: Jason A. Donenfeld --- arch/powerpc/Kconfig | 2 +- arch/powerpc/kernel/vdso/Makefile | 8 ++- arch/powerpc/kernel/vdso/getrandom.S | 8 +++ arch/powerpc/kernel/vdso/vdso64.lds.S | 1 + arch/powerpc/kernel/vdso/vgetrandom-chacha.S | 53 ++++++++++++++++++++ 5 files changed, 69 insertions(+), 3 deletions(-) diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig index e500a59ddecc..b45452ac4a73 100644 --- a/arch/powerpc/Kconfig +++ b/arch/powerpc/Kconfig @@ -311,7 +311,7 @@ config PPC select SYSCTL_EXCEPTION_TRACE select THREAD_INFO_IN_TASK select TRACE_IRQFLAGS_SUPPORT - select VDSO_GETRANDOM if VDSO32 + select VDSO_GETRANDOM # # Please keep this list sorted alphabetically. # diff --git a/arch/powerpc/kernel/vdso/Makefile b/arch/powerpc/kernel/vdso/Makefile index 7a4a935406d8..56fb1633529a 100644 --- a/arch/powerpc/kernel/vdso/Makefile +++ b/arch/powerpc/kernel/vdso/Makefile @@ -9,6 +9,7 @@ obj-vdso32 = sigtramp32-32.o gettimeofday-32.o datapage-32.o cacheflush-32.o not obj-vdso64 = sigtramp64-64.o gettimeofday-64.o datapage-64.o cacheflush-64.o note-64.o getcpu-64.o obj-vdso32 += getrandom-32.o vgetrandom-chacha-32.o +obj-vdso64 += getrandom-64.o vgetrandom-chacha-64.o ifneq ($(c-gettimeofday-y),) CFLAGS_vgettimeofday-32.o += -include $(c-gettimeofday-y) @@ -21,6 +22,7 @@ endif ifneq ($(c-getrandom-y),) CFLAGS_vgetrandom-32.o += -include $(c-getrandom-y) + CFLAGS_vgetrandom-64.o += -include $(c-getrandom-y) $(call cc-option, -ffixed-r30) endif # Build rules @@ -34,7 +36,7 @@ endif targets := $(obj-vdso32) vdso32.so.dbg vgettimeofday-32.o vgetrandom-32.o targets += crtsavres-32.o obj-vdso32 := $(addprefix $(obj)/, $(obj-vdso32)) -targets += $(obj-vdso64) vdso64.so.dbg vgettimeofday-64.o +targets += $(obj-vdso64) vdso64.so.dbg vgettimeofday-64.o vgetrandom-64.o obj-vdso64 := $(addprefix $(obj)/, $(obj-vdso64)) ccflags-y := -fno-common -fno-builtin -DBUILD_VDSO @@ -71,7 +73,7 @@ CPPFLAGS_vdso64.lds += -P -C # link rule for the .so file, .lds has to be first $(obj)/vdso32.so.dbg: $(obj)/vdso32.lds $(obj-vdso32) $(obj)/vgettimeofday-32.o $(obj)/vgetrandom-32.o $(obj)/crtsavres-32.o FORCE $(call if_changed,vdso32ld_and_check) -$(obj)/vdso64.so.dbg: $(obj)/vdso64.lds $(obj-vdso64) $(obj)/vgettimeofday-64.o FORCE +$(obj)/vdso64.so.dbg: $(obj)/vdso64.lds $(obj-vdso64) $(obj)/vgettimeofday-64.o $(obj)/vgetrandom-64.o FORCE $(call if_changed,vdso64ld_and_check) # assembly rules for the .S files @@ -87,6 +89,8 @@ $(obj-vdso64): %-64.o: %.S FORCE $(call if_changed_dep,vdso64as) $(obj)/vgettimeofday-64.o: %-64.o: %.c FORCE $(call if_changed_dep,cc_o_c) +$(obj)/vgetrandom-64.o: %-64.o: %.c FORCE + $(call if_changed_dep,cc_o_c) # Generate VDSO offsets using helper script gen-vdso32sym := $(src)/gen_vdso32_offsets.sh diff --git a/arch/powerpc/kernel/vdso/getrandom.S b/arch/powerpc/kernel/vdso/getrandom.S index f614bb90ab57..f3bbf931931c 100644 --- a/arch/powerpc/kernel/vdso/getrandom.S +++ b/arch/powerpc/kernel/vdso/getrandom.S @@ -27,10 +27,18 @@ .cfi_adjust_cfa_offset PPC_MIN_STKFRM PPC_STL r0, PPC_MIN_STKFRM + PPC_LR_STKOFF(r1) .cfi_rel_offset lr, PPC_MIN_STKFRM + PPC_LR_STKOFF +#ifdef __powerpc64__ + PPC_STL r2, PPC_MIN_STKFRM + STK_GOT(r1) + .cfi_rel_offset r2, PPC_MIN_STKFRM + STK_GOT +#endif get_realdatapage r8, r11 addi r8, r8, VDSO_RNG_DATA_OFFSET bl CFUNC(DOTSYM(\funct)) PPC_LL r0, PPC_MIN_STKFRM + PPC_LR_STKOFF(r1) +#ifdef __powerpc64__ + PPC_LL r2, PPC_MIN_STKFRM + STK_GOT(r1) + .cfi_restore r2 +#endif cmpwi r3, 0 mtlr r0 addi r1, r1, 2 * PPC_MIN_STKFRM diff --git a/arch/powerpc/kernel/vdso/vdso64.lds.S b/arch/powerpc/kernel/vdso/vdso64.lds.S index bda6c8cdd459..6f88d063aabe 100644 --- a/arch/powerpc/kernel/vdso/vdso64.lds.S +++ b/arch/powerpc/kernel/vdso/vdso64.lds.S @@ -123,6 +123,7 @@ VERSION __kernel_sigtramp_rt64; __kernel_getcpu; __kernel_time; + __kernel_getrandom; local: *; }; diff --git a/arch/powerpc/kernel/vdso/vgetrandom-chacha.S b/arch/powerpc/kernel/vdso/vgetrandom-chacha.S index ac85788205cb..7f9061a9e8b4 100644 --- a/arch/powerpc/kernel/vdso/vgetrandom-chacha.S +++ b/arch/powerpc/kernel/vdso/vgetrandom-chacha.S @@ -124,6 +124,26 @@ */ SYM_FUNC_START(__arch_chacha20_blocks_nostack) #ifdef __powerpc64__ + std counter, -216(r1) + + std r14, -144(r1) + std r15, -136(r1) + std r16, -128(r1) + std r17, -120(r1) + std r18, -112(r1) + std r19, -104(r1) + std r20, -96(r1) + std r21, -88(r1) + std r22, -80(r1) + std r23, -72(r1) + std r24, -64(r1) + std r25, -56(r1) + std r26, -48(r1) + std r27, -40(r1) + std r28, -32(r1) + std r29, -24(r1) + std r30, -16(r1) + std r31, -8(r1) #else stwu r1, -96(r1) stw counter, 20(r1) @@ -149,9 +169,13 @@ SYM_FUNC_START(__arch_chacha20_blocks_nostack) stw r30, 88(r1) stw r31, 92(r1) #endif +#endif /* __powerpc64__ */ lwz counter0, 0(counter) lwz counter1, 4(counter) +#ifdef __powerpc64__ + rldimi counter0, counter1, 32, 0 +#endif mr idx_r0, nblocks subi dst_bytes, dst_bytes, 4 @@ -267,12 +291,21 @@ SYM_FUNC_START(__arch_chacha20_blocks_nostack) subic. idx_r0, idx_r0, 1 /* subi. can't use r0 as source */ +#ifdef __powerpc64__ + addi counter0, counter0, 1 + srdi counter1, counter0, 32 +#else addic counter0, counter0, 1 addze counter1, counter1 +#endif bne .Lblock +#ifdef __powerpc64__ + ld counter, -216(r1) +#else lwz counter, 20(r1) +#endif stw counter0, 0(counter) stw counter1, 4(counter) @@ -284,6 +317,26 @@ SYM_FUNC_START(__arch_chacha20_blocks_nostack) li r11, 0 li r12, 0 +#ifdef __powerpc64__ + ld r14, -144(r1) + ld r15, -136(r1) + ld r16, -128(r1) + ld r17, -120(r1) + ld r18, -112(r1) + ld r19, -104(r1) + ld r20, -96(r1) + ld r21, -88(r1) + ld r22, -80(r1) + ld r23, -72(r1) + ld r24, -64(r1) + ld r25, -56(r1) + ld r26, -48(r1) + ld r27, -40(r1) + ld r28, -32(r1) + ld r29, -24(r1) + ld r30, -16(r1) + ld r31, -8(r1) +#else #ifdef __BIG_ENDIAN__ lmw r14, 24(r1) #else From 14be4e6f35221c4731b004553ecf7cbc6dc1d2d8 Mon Sep 17 00:00:00 2001 From: Jens Remus Date: Wed, 11 Sep 2024 10:50:14 +0200 Subject: [PATCH 546/573] selftests: vDSO: fix ELF hash table entry size for s390x The vDSO self tests fail on s390x for a vDSO linked with the GNU linker ld as follows: # ./vdso_test_gettimeofday Floating point exception (core dumped) On s390x the ELF hash table entries are 64 bits instead of 32 bits in size (see Glibc sysdeps/unix/sysv/linux/s390/bits/elfclass.h). Fixes: 40723419f407 ("kselftest: Enable vDSO test on non x86 platforms") Reported-by: Heiko Carstens Tested-by: Heiko Carstens Signed-off-by: Jens Remus Signed-off-by: Heiko Carstens Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/parse_vdso.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/vDSO/parse_vdso.c b/tools/testing/selftests/vDSO/parse_vdso.c index d9ccc5acac18..7dd5668ea8a6 100644 --- a/tools/testing/selftests/vDSO/parse_vdso.c +++ b/tools/testing/selftests/vDSO/parse_vdso.c @@ -36,6 +36,12 @@ #define ELF_BITS_XFORM(bits, x) ELF_BITS_XFORM2(bits, x) #define ELF(x) ELF_BITS_XFORM(ELF_BITS, x) +#ifdef __s390x__ +#define ELF_HASH_ENTRY ELF(Xword) +#else +#define ELF_HASH_ENTRY ELF(Word) +#endif + static struct vdso_info { bool valid; @@ -47,8 +53,8 @@ static struct vdso_info /* Symbol table */ ELF(Sym) *symtab; const char *symstrings; - ELF(Word) *bucket, *chain; - ELF(Word) nbucket, nchain; + ELF_HASH_ENTRY *bucket, *chain; + ELF_HASH_ENTRY nbucket, nchain; /* Version table */ ELF(Versym) *versym; @@ -115,7 +121,7 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base) /* * Fish out the useful bits of the dynamic table. */ - ELF(Word) *hash = 0; + ELF_HASH_ENTRY *hash = 0; vdso_info.symstrings = 0; vdso_info.symtab = 0; vdso_info.versym = 0; @@ -133,7 +139,7 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base) + vdso_info.load_offset); break; case DT_HASH: - hash = (ELF(Word) *) + hash = (ELF_HASH_ENTRY *) ((uintptr_t)dyn[i].d_un.d_ptr + vdso_info.load_offset); break; From a6e23fb8d3c0e3904da70beaf5d7e840a983c97f Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Wed, 11 Sep 2024 10:50:15 +0200 Subject: [PATCH 547/573] selftests: vDSO: fix vdso_config for s390 Running vdso_test_correctness on s390x (aka s390 64 bit) emits a warning: Warning: failed to find clock_gettime64 in vDSO This is caused by the "#elif defined (__s390__)" check in vdso_config.h which the defines VDSO_32BIT. If __s390x__ is defined also __s390__ is defined. Therefore the correct check must make sure that only __s390__ is defined. Therefore add the missing !defined(__s390x__). Also use common __s390x__ define instead of __s390X__. Signed-off-by: Heiko Carstens Fixes: 693f5ca08ca0 ("kselftest: Extend vDSO selftest") Signed-off-by: Jason A. Donenfeld --- tools/testing/selftests/vDSO/vdso_config.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/vDSO/vdso_config.h b/tools/testing/selftests/vDSO/vdso_config.h index 740ce8c98d2e..722260f97561 100644 --- a/tools/testing/selftests/vDSO/vdso_config.h +++ b/tools/testing/selftests/vDSO/vdso_config.h @@ -25,11 +25,11 @@ #define VDSO_VERSION 1 #define VDSO_NAMES 0 #define VDSO_32BIT 1 -#elif defined (__s390__) +#elif defined (__s390__) && !defined(__s390x__) #define VDSO_VERSION 2 #define VDSO_NAMES 0 #define VDSO_32BIT 1 -#elif defined (__s390X__) +#elif defined (__s390x__) #define VDSO_VERSION 2 #define VDSO_NAMES 0 #elif defined(__mips__) From 26d4959681e34a8479b8b7a9ef25ab27550562f6 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 13 Sep 2024 15:05:37 +0200 Subject: [PATCH 548/573] s390/facility: Disable compile time optimization for decompressor code Disable compile time optimizations of test_facility() for the decompressor. The decompressor should not contain any optimized code depending on the architecture level set the kernel image is compiled for to avoid unexpected operation exceptions. Add a __DECOMPRESSOR check to test_facility() to enforce that facilities are always checked during runtime for the decompressor. Reviewed-by: Sven Schnelle Signed-off-by: Heiko Carstens Signed-off-by: Jason A. Donenfeld --- arch/s390/include/asm/facility.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/s390/include/asm/facility.h b/arch/s390/include/asm/facility.h index b7d234838a36..65ebf86506cd 100644 --- a/arch/s390/include/asm/facility.h +++ b/arch/s390/include/asm/facility.h @@ -59,8 +59,10 @@ static inline int test_facility(unsigned long nr) unsigned long facilities_als[] = { FACILITIES_ALS }; if (__builtin_constant_p(nr) && nr < sizeof(facilities_als) * 8) { - if (__test_facility(nr, &facilities_als)) - return 1; + if (__test_facility(nr, &facilities_als)) { + if (!__is_defined(__DECOMPRESSOR)) + return 1; + } } return __test_facility(nr, &stfle_fac_list); } From 013e984397a27129972cb1d89589d2b5be95a40e Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 13 Sep 2024 15:05:38 +0200 Subject: [PATCH 549/573] s390/alternatives: Remove ALT_FACILITY_EARLY Patch all alternatives which depend on facilities from the decompressor. There is no technical reason which enforces to split patching of such alternatives to the decompressor and the kernel. This simplifies alternative handling a bit, since one alternative type is removed. Signed-off-by: Heiko Carstens Signed-off-by: Jason A. Donenfeld --- arch/s390/include/asm/alternative.h | 6 +----- arch/s390/kernel/entry.S | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/arch/s390/include/asm/alternative.h b/arch/s390/include/asm/alternative.h index de980c938a3e..73e781b56bfe 100644 --- a/arch/s390/include/asm/alternative.h +++ b/arch/s390/include/asm/alternative.h @@ -39,11 +39,7 @@ #define ALT_TYPE_SHIFT 20 #define ALT_CTX_SHIFT 28 -#define ALT_FACILITY_EARLY(facility) (ALT_CTX_EARLY << ALT_CTX_SHIFT | \ - ALT_TYPE_FACILITY << ALT_TYPE_SHIFT | \ - (facility) << ALT_DATA_SHIFT) - -#define ALT_FACILITY(facility) (ALT_CTX_LATE << ALT_CTX_SHIFT | \ +#define ALT_FACILITY(facility) (ALT_CTX_EARLY << ALT_CTX_SHIFT | \ ALT_TYPE_FACILITY << ALT_TYPE_SHIFT | \ (facility) << ALT_DATA_SHIFT) diff --git a/arch/s390/kernel/entry.S b/arch/s390/kernel/entry.S index 749410cfdbc0..269436665d02 100644 --- a/arch/s390/kernel/entry.S +++ b/arch/s390/kernel/entry.S @@ -42,7 +42,7 @@ _LPP_OFFSET = __LC_LPP .macro LPSWEY address, lpswe ALTERNATIVE_2 "b \lpswe;nopr", \ - ".insn siy,0xeb0000000071,\address,0", ALT_FACILITY_EARLY(193), \ + ".insn siy,0xeb0000000071,\address,0", ALT_FACILITY(193), \ __stringify(.insn siy,0xeb0000000071,LOWCORE_ALT_ADDRESS+\address,0), \ ALT_LOWCORE .endm From 94c7755b1e8f9a8953c25f0900650be009e7be7a Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 13 Sep 2024 15:05:39 +0200 Subject: [PATCH 550/573] s390/facility: Let test_facility() generate static branch if possible Let test_facility() generate a branch instruction if the tested facility is a constant, and where the result cannot be evaluated during compile time. The branch instruction defaults to "false" and is patched to nop (branch not taken) if the tested facility is available. This avoids runtime checks and is similar to x86's static_cpu_has() and arm64's alternative_has_cap_likely(). Signed-off-by: Heiko Carstens Signed-off-by: Jason A. Donenfeld --- arch/s390/include/asm/facility.h | 37 +++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/arch/s390/include/asm/facility.h b/arch/s390/include/asm/facility.h index 65ebf86506cd..715bcf8fb69a 100644 --- a/arch/s390/include/asm/facility.h +++ b/arch/s390/include/asm/facility.h @@ -14,7 +14,7 @@ #include #include #include - +#include #include #define MAX_FACILITY_BIT (sizeof(stfle_fac_list) * 8) @@ -39,30 +39,51 @@ static inline void __clear_facility(unsigned long nr, void *facilities) ptr[nr >> 3] &= ~(0x80 >> (nr & 7)); } -static inline int __test_facility(unsigned long nr, void *facilities) +static __always_inline bool __test_facility(unsigned long nr, void *facilities) { unsigned char *ptr; if (nr >= MAX_FACILITY_BIT) - return 0; + return false; ptr = (unsigned char *) facilities + (nr >> 3); return (*ptr & (0x80 >> (nr & 7))) != 0; } +/* + * __test_facility_constant() generates a single instruction branch. If the + * tested facility is available (likely) the branch is patched into a nop. + * + * Do not use this function unless you know what you are doing. All users are + * supposed to use test_facility() which will do the right thing. + */ +static __always_inline bool __test_facility_constant(unsigned long nr) +{ + asm goto( + ALTERNATIVE("brcl 15,%l[l_no]", "brcl 0,0", ALT_FACILITY(%[nr])) + : + : [nr] "i" (nr) + : + : l_no); + return true; +l_no: + return false; +} + /* * The test_facility function uses the bit ordering where the MSB is bit 0. * That makes it easier to query facility bits with the bit number as * documented in the Principles of Operation. */ -static inline int test_facility(unsigned long nr) +static __always_inline bool test_facility(unsigned long nr) { unsigned long facilities_als[] = { FACILITIES_ALS }; - if (__builtin_constant_p(nr) && nr < sizeof(facilities_als) * 8) { - if (__test_facility(nr, &facilities_als)) { - if (!__is_defined(__DECOMPRESSOR)) - return 1; + if (!__is_defined(__DECOMPRESSOR) && __builtin_constant_p(nr)) { + if (nr < sizeof(facilities_als) * 8) { + if (__test_facility(nr, &facilities_als)) + return true; } + return __test_facility_constant(nr); } return __test_facility(nr, &stfle_fac_list); } From a919390e9194209bac163ec8e19520bdb3fb738b Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 13 Sep 2024 15:05:40 +0200 Subject: [PATCH 551/573] s390/module: Provide find_section() helper Provide find_section() helper function which can be used to find a section by name, similar to other architectures. Signed-off-by: Heiko Carstens Signed-off-by: Jason A. Donenfeld --- arch/s390/include/asm/module.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/arch/s390/include/asm/module.h b/arch/s390/include/asm/module.h index 9f1eea15872c..916ab59e458a 100644 --- a/arch/s390/include/asm/module.h +++ b/arch/s390/include/asm/module.h @@ -38,4 +38,18 @@ struct mod_arch_specific { #endif /* CONFIG_FUNCTION_TRACER */ }; +static inline const Elf_Shdr *find_section(const Elf_Ehdr *hdr, + const Elf_Shdr *sechdrs, + const char *name) +{ + const char *secstrs = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset; + const Elf_Shdr *s, *se; + + for (s = sechdrs, se = sechdrs + hdr->e_shnum; s < se; s++) { + if (strcmp(name, secstrs + s->sh_name) == 0) + return s; + } + return NULL; +} + #endif /* _ASM_S390_MODULE_H */ From e10863fffe3a377a828f876a39cc1c47567b556f Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 13 Sep 2024 15:05:41 +0200 Subject: [PATCH 552/573] s390/vdso: Allow alternatives in vdso code Implement the infrastructure required to allow alternatives in vdso code. Signed-off-by: Heiko Carstens Signed-off-by: Jason A. Donenfeld --- arch/s390/kernel/vdso.c | 19 +++++++++++++++++++ arch/s390/kernel/vdso64/vdso64.lds.S | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/arch/s390/kernel/vdso.c b/arch/s390/kernel/vdso.c index 2f967ac2b8e3..8e4e6b316337 100644 --- a/arch/s390/kernel/vdso.c +++ b/arch/s390/kernel/vdso.c @@ -12,12 +12,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include extern char vdso64_start[], vdso64_end[]; @@ -250,8 +252,25 @@ static struct page ** __init vdso_setup_pages(void *start, void *end) return pagelist; } +static void vdso_apply_alternatives(void) +{ + const struct elf64_shdr *alt, *shdr; + struct alt_instr *start, *end; + const struct elf64_hdr *hdr; + + hdr = (struct elf64_hdr *)vdso64_start; + shdr = (void *)hdr + hdr->e_shoff; + alt = find_section(hdr, shdr, ".altinstructions"); + if (!alt) + return; + start = (void *)hdr + alt->sh_offset; + end = (void *)hdr + alt->sh_offset + alt->sh_size; + apply_alternatives(start, end); +} + static int __init vdso_init(void) { + vdso_apply_alternatives(); vdso64_mapping.pages = vdso_setup_pages(vdso64_start, vdso64_end); if (IS_ENABLED(CONFIG_COMPAT)) vdso32_mapping.pages = vdso_setup_pages(vdso32_start, vdso32_end); diff --git a/arch/s390/kernel/vdso64/vdso64.lds.S b/arch/s390/kernel/vdso64/vdso64.lds.S index 37e2a505e81d..fa02c6ae3cac 100644 --- a/arch/s390/kernel/vdso64/vdso64.lds.S +++ b/arch/s390/kernel/vdso64/vdso64.lds.S @@ -42,6 +42,10 @@ SECTIONS .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } .rodata1 : { *(.rodata1) } + . = ALIGN(8); + .altinstructions : { *(.altinstructions) } + .altinstr_replacement : { *(.altinstr_replacement) } + .dynamic : { *(.dynamic) } :text :dynamic .eh_frame_hdr : { *(.eh_frame_hdr) } :text :eh_frame_hdr From c1ae1b4ef5bf541c71ced84d087abed029f13b1a Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 13 Sep 2024 15:05:42 +0200 Subject: [PATCH 553/573] s390/vdso: Move vdso symbol handling to separate header file The vdso.h header file, which is included at many places, includes generated header files. This can easily lead to recursive header file inclusions if the vdso code is changed. Therefore move the vdso symbol code, which requires the generated header files, to a separate header file, and include it at the two locations which require it. Signed-off-by: Heiko Carstens Signed-off-by: Jason A. Donenfeld --- arch/s390/include/asm/vdso-symbols.h | 17 +++++++++++++++++ arch/s390/include/asm/vdso.h | 12 ------------ arch/s390/kernel/compat_signal.c | 2 +- arch/s390/kernel/signal.c | 2 +- 4 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 arch/s390/include/asm/vdso-symbols.h diff --git a/arch/s390/include/asm/vdso-symbols.h b/arch/s390/include/asm/vdso-symbols.h new file mode 100644 index 000000000000..0df17574d788 --- /dev/null +++ b/arch/s390/include/asm/vdso-symbols.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __S390_VDSO_SYMBOLS_H__ +#define __S390_VDSO_SYMBOLS_H__ + +#include +#ifdef CONFIG_COMPAT +#include +#endif + +#define VDSO64_SYMBOL(tsk, name) ((tsk)->mm->context.vdso_base + (vdso64_offset_##name)) +#ifdef CONFIG_COMPAT +#define VDSO32_SYMBOL(tsk, name) ((tsk)->mm->context.vdso_base + (vdso32_offset_##name)) +#else +#define VDSO32_SYMBOL(tsk, name) (-1UL) +#endif + +#endif /* __S390_VDSO_SYMBOLS_H__ */ diff --git a/arch/s390/include/asm/vdso.h b/arch/s390/include/asm/vdso.h index 53165aa7813a..91061f0279be 100644 --- a/arch/s390/include/asm/vdso.h +++ b/arch/s390/include/asm/vdso.h @@ -6,18 +6,6 @@ #ifndef __ASSEMBLY__ -#include -#ifdef CONFIG_COMPAT -#include -#endif - -#define VDSO64_SYMBOL(tsk, name) ((tsk)->mm->context.vdso_base + (vdso64_offset_##name)) -#ifdef CONFIG_COMPAT -#define VDSO32_SYMBOL(tsk, name) ((tsk)->mm->context.vdso_base + (vdso32_offset_##name)) -#else -#define VDSO32_SYMBOL(tsk, name) (-1UL) -#endif - extern struct vdso_data *vdso_data; int vdso_getcpu_init(void); diff --git a/arch/s390/kernel/compat_signal.c b/arch/s390/kernel/compat_signal.c index 1942e2a9f8db..5a86b9d1da71 100644 --- a/arch/s390/kernel/compat_signal.c +++ b/arch/s390/kernel/compat_signal.c @@ -24,11 +24,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include "compat_linux.h" #include "compat_ptrace.h" diff --git a/arch/s390/kernel/signal.c b/arch/s390/kernel/signal.c index 6c2cb345402f..e48013cd832c 100644 --- a/arch/s390/kernel/signal.c +++ b/arch/s390/kernel/signal.c @@ -30,9 +30,9 @@ #include #include #include +#include #include #include -#include #include "entry.h" /* From b920aa77be6d57d407b6b6be5d8b4ccc17749cce Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 13 Sep 2024 15:05:43 +0200 Subject: [PATCH 554/573] s390/vdso: Wire up getrandom() vdso implementation Provide the s390 specific vdso getrandom() architecture backend. _vdso_rng_data required data is placed within the _vdso_data vvar page, by using a hardcoded offset larger than vdso_data. As required the chacha20 implementation does not write to the stack. The implementation follows more or less the arm64 implementations and makes use of vector instructions. It has a fallback to the getrandom() system call for machines where the vector facility is not installed. The check if the vector facility is installed, as well as an optimization for machines with the vector-enhancements facility 2, is implemented with alternatives, avoiding runtime checks. Note that __kernel_getrandom() is implemented without the vdso user wrapper which would setup a stack frame for odd cases (aka very old glibc variants) where the caller has not done that. All callers of __kernel_getrandom() are required to setup a stack frame, like the C ABI requires it. The vdso testcases vdso_test_getrandom and vdso_test_chacha pass. Benchmark on a z16: $ ./vdso_test_getrandom bench-single vdso: 25000000 times in 0.493703559 seconds syscall: 25000000 times in 6.584025337 seconds Signed-off-by: Heiko Carstens Reviewed-by: Harald Freudenberger Signed-off-by: Jason A. Donenfeld --- arch/s390/Kconfig | 1 + arch/s390/include/asm/fpu-insn-asm.h | 22 +++ arch/s390/include/asm/vdso/getrandom.h | 40 +++++ arch/s390/include/asm/vdso/vsyscall.h | 15 ++ arch/s390/kernel/vdso.c | 7 +- arch/s390/kernel/vdso64/Makefile | 9 +- arch/s390/kernel/vdso64/vdso.h | 1 + arch/s390/kernel/vdso64/vdso64.lds.S | 3 + arch/s390/kernel/vdso64/vgetrandom-chacha.S | 185 ++++++++++++++++++++ arch/s390/kernel/vdso64/vgetrandom.c | 14 ++ tools/arch/s390/vdso | 1 + tools/testing/selftests/vDSO/Makefile | 2 +- 12 files changed, 291 insertions(+), 9 deletions(-) create mode 100644 arch/s390/include/asm/vdso/getrandom.h create mode 100644 arch/s390/kernel/vdso64/vgetrandom-chacha.S create mode 100644 arch/s390/kernel/vdso64/vgetrandom.c create mode 120000 tools/arch/s390/vdso diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index c60e699e99f5..b0d0b3a8d196 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -243,6 +243,7 @@ config S390 select TRACE_IRQFLAGS_SUPPORT select TTY select USER_STACKTRACE_SUPPORT + select VDSO_GETRANDOM select VIRT_CPU_ACCOUNTING select ZONE_DMA # Note: keep the above list sorted alphabetically diff --git a/arch/s390/include/asm/fpu-insn-asm.h b/arch/s390/include/asm/fpu-insn-asm.h index 02ccfe46050a..d296322be4bc 100644 --- a/arch/s390/include/asm/fpu-insn-asm.h +++ b/arch/s390/include/asm/fpu-insn-asm.h @@ -407,6 +407,28 @@ MRXBOPC 0, 0x0E, v1 .endm +/* VECTOR STORE BYTE REVERSED ELEMENTS */ + .macro VSTBR vr1, disp, index="%r0", base, m + VX_NUM v1, \vr1 + GR_NUM x2, \index + GR_NUM b2, \base + .word 0xE600 | ((v1&15) << 4) | (x2&15) + .word (b2 << 12) | (\disp) + MRXBOPC \m, 0x0E, v1 +.endm +.macro VSTBRH vr1, disp, index="%r0", base + VSTBR \vr1, \disp, \index, \base, 1 +.endm +.macro VSTBRF vr1, disp, index="%r0", base + VSTBR \vr1, \disp, \index, \base, 2 +.endm +.macro VSTBRG vr1, disp, index="%r0", base + VSTBR \vr1, \disp, \index, \base, 3 +.endm +.macro VSTBRQ vr1, disp, index="%r0", base + VSTBR \vr1, \disp, \index, \base, 4 +.endm + /* VECTOR STORE MULTIPLE */ .macro VSTM vfrom, vto, disp, base, hint=3 VX_NUM v1, \vfrom diff --git a/arch/s390/include/asm/vdso/getrandom.h b/arch/s390/include/asm/vdso/getrandom.h new file mode 100644 index 000000000000..36355af7160b --- /dev/null +++ b/arch/s390/include/asm/vdso/getrandom.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __ASM_VDSO_GETRANDOM_H +#define __ASM_VDSO_GETRANDOM_H + +#ifndef __ASSEMBLY__ + +#include +#include +#include +#include +#include + +/** + * getrandom_syscall - Invoke the getrandom() syscall. + * @buffer: Destination buffer to fill with random bytes. + * @len: Size of @buffer in bytes. + * @flags: Zero or more GRND_* flags. + * Returns: The number of random bytes written to @buffer, or a negative value indicating an error. + */ +static __always_inline ssize_t getrandom_syscall(void *buffer, size_t len, unsigned int flags) +{ + return syscall3(__NR_getrandom, (long)buffer, (long)len, (long)flags); +} + +static __always_inline const struct vdso_rng_data *__arch_get_vdso_rng_data(void) +{ + /* + * The RNG data is in the real VVAR data page, but if a task belongs to a time namespace + * then VVAR_DATA_PAGE_OFFSET points to the namespace-specific VVAR page and VVAR_TIMENS_ + * PAGE_OFFSET points to the real VVAR page. + */ + if (IS_ENABLED(CONFIG_TIME_NS) && _vdso_data->clock_mode == VDSO_CLOCKMODE_TIMENS) + return (void *)&_vdso_rng_data + VVAR_TIMENS_PAGE_OFFSET * PAGE_SIZE; + return &_vdso_rng_data; +} + +#endif /* !__ASSEMBLY__ */ + +#endif /* __ASM_VDSO_GETRANDOM_H */ diff --git a/arch/s390/include/asm/vdso/vsyscall.h b/arch/s390/include/asm/vdso/vsyscall.h index 6c67c08cefdd..3c5d5e47814e 100644 --- a/arch/s390/include/asm/vdso/vsyscall.h +++ b/arch/s390/include/asm/vdso/vsyscall.h @@ -2,12 +2,21 @@ #ifndef __ASM_VDSO_VSYSCALL_H #define __ASM_VDSO_VSYSCALL_H +#define __VDSO_RND_DATA_OFFSET 768 + #ifndef __ASSEMBLY__ #include #include #include #include + +enum vvar_pages { + VVAR_DATA_PAGE_OFFSET, + VVAR_TIMENS_PAGE_OFFSET, + VVAR_NR_PAGES +}; + /* * Update the vDSO data page to keep in sync with kernel timekeeping. */ @@ -18,6 +27,12 @@ static __always_inline struct vdso_data *__s390_get_k_vdso_data(void) } #define __arch_get_k_vdso_data __s390_get_k_vdso_data +static __always_inline struct vdso_rng_data *__s390_get_k_vdso_rnd_data(void) +{ + return (void *)vdso_data + __VDSO_RND_DATA_OFFSET; +} +#define __arch_get_k_vdso_rng_data __s390_get_k_vdso_rnd_data + /* The asm-generic header needs to be included after the definitions above */ #include diff --git a/arch/s390/kernel/vdso.c b/arch/s390/kernel/vdso.c index 8e4e6b316337..598b512cde01 100644 --- a/arch/s390/kernel/vdso.c +++ b/arch/s390/kernel/vdso.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -31,12 +32,6 @@ static union vdso_data_store vdso_data_store __page_aligned_data; struct vdso_data *vdso_data = vdso_data_store.data; -enum vvar_pages { - VVAR_DATA_PAGE_OFFSET, - VVAR_TIMENS_PAGE_OFFSET, - VVAR_NR_PAGES, -}; - #ifdef CONFIG_TIME_NS struct vdso_data *arch_get_vdso_data(void *vvar_page) { diff --git a/arch/s390/kernel/vdso64/Makefile b/arch/s390/kernel/vdso64/Makefile index ba19c0ca7c87..37bb4b761229 100644 --- a/arch/s390/kernel/vdso64/Makefile +++ b/arch/s390/kernel/vdso64/Makefile @@ -3,12 +3,17 @@ # Include the generic Makefile to check the built vdso. include $(srctree)/lib/vdso/Makefile -obj-vdso64 = vdso_user_wrapper.o note.o -obj-cvdso64 = vdso64_generic.o getcpu.o +obj-vdso64 = vdso_user_wrapper.o note.o vgetrandom-chacha.o +obj-cvdso64 = vdso64_generic.o getcpu.o vgetrandom.o VDSO_CFLAGS_REMOVE := -pg $(CC_FLAGS_FTRACE) $(CC_FLAGS_EXPOLINE) $(CC_FLAGS_CHECK_STACK) CFLAGS_REMOVE_getcpu.o = $(VDSO_CFLAGS_REMOVE) +CFLAGS_REMOVE_vgetrandom.o = $(VDSO_CFLAGS_REMOVE) CFLAGS_REMOVE_vdso64_generic.o = $(VDSO_CFLAGS_REMOVE) +ifneq ($(c-getrandom-y),) + CFLAGS_vgetrandom.o += -include $(c-getrandom-y) +endif + # Build rules targets := $(obj-vdso64) $(obj-cvdso64) vdso64.so vdso64.so.dbg diff --git a/arch/s390/kernel/vdso64/vdso.h b/arch/s390/kernel/vdso64/vdso.h index 34c7a2312f9d..9e5397e7b590 100644 --- a/arch/s390/kernel/vdso64/vdso.h +++ b/arch/s390/kernel/vdso64/vdso.h @@ -10,5 +10,6 @@ int __s390_vdso_getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *unuse int __s390_vdso_gettimeofday(struct __kernel_old_timeval *tv, struct timezone *tz); int __s390_vdso_clock_gettime(clockid_t clock, struct __kernel_timespec *ts); int __s390_vdso_clock_getres(clockid_t clock, struct __kernel_timespec *ts); +ssize_t __kernel_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state, size_t opaque_len); #endif /* __ARCH_S390_KERNEL_VDSO64_VDSO_H */ diff --git a/arch/s390/kernel/vdso64/vdso64.lds.S b/arch/s390/kernel/vdso64/vdso64.lds.S index fa02c6ae3cac..753040a4b5ab 100644 --- a/arch/s390/kernel/vdso64/vdso64.lds.S +++ b/arch/s390/kernel/vdso64/vdso64.lds.S @@ -4,6 +4,7 @@ * library */ +#include #include #include @@ -13,6 +14,7 @@ OUTPUT_ARCH(s390:64-bit) SECTIONS { PROVIDE(_vdso_data = . - __VVAR_PAGES * PAGE_SIZE); + PROVIDE(_vdso_rng_data = _vdso_data + __VDSO_RND_DATA_OFFSET); #ifdef CONFIG_TIME_NS PROVIDE(_timens_data = _vdso_data + PAGE_SIZE); #endif @@ -144,6 +146,7 @@ VERSION __kernel_restart_syscall; __kernel_rt_sigreturn; __kernel_sigreturn; + __kernel_getrandom; local: *; }; } diff --git a/arch/s390/kernel/vdso64/vgetrandom-chacha.S b/arch/s390/kernel/vdso64/vgetrandom-chacha.S new file mode 100644 index 000000000000..d802b0a96f41 --- /dev/null +++ b/arch/s390/kernel/vdso64/vgetrandom-chacha.S @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include +#include +#include + +#define STATE0 %v0 +#define STATE1 %v1 +#define STATE2 %v2 +#define STATE3 %v3 +#define COPY0 %v4 +#define COPY1 %v5 +#define COPY2 %v6 +#define COPY3 %v7 +#define PERM4 %v16 +#define PERM8 %v17 +#define PERM12 %v18 +#define BEPERM %v19 +#define TMP0 %v20 +#define TMP1 %v21 +#define TMP2 %v22 +#define TMP3 %v23 + + .section .rodata + + .balign 128 +.Lconstants: + .long 0x61707865,0x3320646e,0x79622d32,0x6b206574 # endian-neutral + .long 0x04050607,0x08090a0b,0x0c0d0e0f,0x00010203 # rotl 4 bytes + .long 0x08090a0b,0x0c0d0e0f,0x00010203,0x04050607 # rotl 8 bytes + .long 0x0c0d0e0f,0x00010203,0x04050607,0x08090a0b # rotl 12 bytes + .long 0x03020100,0x07060504,0x0b0a0908,0x0f0e0d0c # byte swap + + .text +/* + * s390 ChaCha20 implementation meant for vDSO. Produces a given positive + * number of blocks of output with nonce 0, taking an input key and 8-bytes + * counter. Does not spill to the stack. + * + * void __arch_chacha20_blocks_nostack(uint8_t *dst_bytes, + * const uint8_t *key, + * uint32_t *counter, + * size_t nblocks) + */ +SYM_FUNC_START(__arch_chacha20_blocks_nostack) + larl %r1,.Lconstants + + /* COPY0 = "expand 32-byte k" */ + VL COPY0,0,,%r1 + + /* PERM4-PERM12,BEPERM = byte selectors for VPERM */ + VLM PERM4,BEPERM,16,%r1 + + /* COPY1,COPY2 = key */ + VLM COPY1,COPY2,0,%r3 + + /* COPY3 = counter || zero nonce */ + lg %r3,0(%r4) + VZERO COPY3 + VLVGG COPY3,%r3,0 + + lghi %r1,0 +.Lblock: + VLR STATE0,COPY0 + VLR STATE1,COPY1 + VLR STATE2,COPY2 + VLR STATE3,COPY3 + + lghi %r0,10 +.Ldoubleround: + /* STATE0 += STATE1, STATE3 = rotl32(STATE3 ^ STATE0, 16) */ + VAF STATE0,STATE0,STATE1 + VX STATE3,STATE3,STATE0 + VERLLF STATE3,STATE3,16 + + /* STATE2 += STATE3, STATE1 = rotl32(STATE1 ^ STATE2, 12) */ + VAF STATE2,STATE2,STATE3 + VX STATE1,STATE1,STATE2 + VERLLF STATE1,STATE1,12 + + /* STATE0 += STATE1, STATE3 = rotl32(STATE3 ^ STATE0, 8) */ + VAF STATE0,STATE0,STATE1 + VX STATE3,STATE3,STATE0 + VERLLF STATE3,STATE3,8 + + /* STATE2 += STATE3, STATE1 = rotl32(STATE1 ^ STATE2, 7) */ + VAF STATE2,STATE2,STATE3 + VX STATE1,STATE1,STATE2 + VERLLF STATE1,STATE1,7 + + /* STATE1[0,1,2,3] = STATE1[1,2,3,0] */ + VPERM STATE1,STATE1,STATE1,PERM4 + /* STATE2[0,1,2,3] = STATE2[2,3,0,1] */ + VPERM STATE2,STATE2,STATE2,PERM8 + /* STATE3[0,1,2,3] = STATE3[3,0,1,2] */ + VPERM STATE3,STATE3,STATE3,PERM12 + + /* STATE0 += STATE1, STATE3 = rotl32(STATE3 ^ STATE0, 16) */ + VAF STATE0,STATE0,STATE1 + VX STATE3,STATE3,STATE0 + VERLLF STATE3,STATE3,16 + + /* STATE2 += STATE3, STATE1 = rotl32(STATE1 ^ STATE2, 12) */ + VAF STATE2,STATE2,STATE3 + VX STATE1,STATE1,STATE2 + VERLLF STATE1,STATE1,12 + + /* STATE0 += STATE1, STATE3 = rotl32(STATE3 ^ STATE0, 8) */ + VAF STATE0,STATE0,STATE1 + VX STATE3,STATE3,STATE0 + VERLLF STATE3,STATE3,8 + + /* STATE2 += STATE3, STATE1 = rotl32(STATE1 ^ STATE2, 7) */ + VAF STATE2,STATE2,STATE3 + VX STATE1,STATE1,STATE2 + VERLLF STATE1,STATE1,7 + + /* STATE1[0,1,2,3] = STATE1[3,0,1,2] */ + VPERM STATE1,STATE1,STATE1,PERM12 + /* STATE2[0,1,2,3] = STATE2[2,3,0,1] */ + VPERM STATE2,STATE2,STATE2,PERM8 + /* STATE3[0,1,2,3] = STATE3[1,2,3,0] */ + VPERM STATE3,STATE3,STATE3,PERM4 + brctg %r0,.Ldoubleround + + /* OUTPUT0 = STATE0 + STATE0 */ + VAF STATE0,STATE0,COPY0 + /* OUTPUT1 = STATE1 + STATE1 */ + VAF STATE1,STATE1,COPY1 + /* OUTPUT2 = STATE2 + STATE2 */ + VAF STATE2,STATE2,COPY2 + /* OUTPUT2 = STATE3 + STATE3 */ + VAF STATE3,STATE3,COPY3 + + /* + * 32 bit wise little endian store to OUTPUT. If the vector + * enhancement facility 2 is not installed use the slow path. + */ + ALTERNATIVE "brc 0xf,.Lstoreslow", "nop", ALT_FACILITY(148) + VSTBRF STATE0,0,,%r2 + VSTBRF STATE1,16,,%r2 + VSTBRF STATE2,32,,%r2 + VSTBRF STATE3,48,,%r2 +.Lstoredone: + + /* ++COPY3.COUNTER */ + /* alsih %r3,1 */ + .insn rilu,0xcc0a00000000,%r3,1 + alcr %r3,%r1 + VLVGG COPY3,%r3,0 + + /* OUTPUT += 64, --NBLOCKS */ + aghi %r2,64 + brctg %r5,.Lblock + + /* COUNTER = COPY3.COUNTER */ + stg %r3,0(%r4) + + /* Zero out potentially sensitive regs */ + VZERO STATE0 + VZERO STATE1 + VZERO STATE2 + VZERO STATE3 + VZERO COPY1 + VZERO COPY2 + + /* Early exit if TMP0-TMP3 have not been used */ + ALTERNATIVE "nopr", "br %r14", ALT_FACILITY(148) + + VZERO TMP0 + VZERO TMP1 + VZERO TMP2 + VZERO TMP3 + + br %r14 + +.Lstoreslow: + /* Convert STATE to little endian format and store to OUTPUT */ + VPERM TMP0,STATE0,STATE0,BEPERM + VPERM TMP1,STATE1,STATE1,BEPERM + VPERM TMP2,STATE2,STATE2,BEPERM + VPERM TMP3,STATE3,STATE3,BEPERM + VSTM TMP0,TMP3,0,%r2 + j .Lstoredone +SYM_FUNC_END(__arch_chacha20_blocks_nostack) diff --git a/arch/s390/kernel/vdso64/vgetrandom.c b/arch/s390/kernel/vdso64/vgetrandom.c new file mode 100644 index 000000000000..b5268b507fb5 --- /dev/null +++ b/arch/s390/kernel/vdso64/vgetrandom.c @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include "vdso.h" + +ssize_t __kernel_getrandom(void *buffer, size_t len, unsigned int flags, void *opaque_state, size_t opaque_len) +{ + if (test_facility(129)) + return __cvdso_getrandom(buffer, len, flags, opaque_state, opaque_len); + if (unlikely(opaque_len == ~0UL && !buffer && !len && !flags)) + return -ENOSYS; + return getrandom_syscall(buffer, len, flags); +} diff --git a/tools/arch/s390/vdso b/tools/arch/s390/vdso new file mode 120000 index 000000000000..6cf4c1cebdcd --- /dev/null +++ b/tools/arch/s390/vdso @@ -0,0 +1 @@ +../../../arch/s390/kernel/vdso64 \ No newline at end of file diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index 86ebc4115eda..af9cedbf5357 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -9,7 +9,7 @@ ifeq ($(ARCH),$(filter $(ARCH),x86 x86_64)) TEST_GEN_PROGS += vdso_standalone_test_x86 endif TEST_GEN_PROGS += vdso_test_correctness -ifeq ($(ARCH)$(CONFIG_X86_32),$(filter $(ARCH)$(CONFIG_X86_32),x86 x86_64 loongarch arm64 powerpc)) +ifeq ($(ARCH)$(CONFIG_X86_32),$(filter $(ARCH)$(CONFIG_X86_32),x86 x86_64 loongarch arm64 powerpc s390)) TEST_GEN_PROGS += vdso_test_getrandom TEST_GEN_PROGS += vdso_test_chacha endif From 78f281e5bdeb6476fab97a2c3fcece1094b42aaf Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 8 Sep 2024 20:53:36 +0200 Subject: [PATCH 555/573] power: supply: Drop use_cnt check from power_supply_property_is_writeable() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit power_supply_property_is_writeable() gets called from the is_visible() callback for the sysfs attributes of power_supply class devices and for the sysfs attributes of power_supply core instantiated hwmon class devices. These sysfs attributes get registered by the device_add() respectively power_supply_add_hwmon_sysfs() calls in power_supply_register(). use_cnt gets initialized to 0 and is incremented only after these calls. So when power_supply_property_is_writeable() gets called it always return -ENODEV because of use_cnt == 0. This causes all the attributes to have permissions of 444 even those which should be writable. This used to be a problem only for hwmon sysfs attributes but since commit be6299c6e55e ("power: supply: sysfs: use power_supply_property_is_writeable()") this now also impacts power_supply class sysfs attributes. Fixes: be6299c6e55e ("power: supply: sysfs: use power_supply_property_is_writeable()") Fixes: e67d4dfc9ff1 ("power: supply: Add HWMON compatibility layer") Cc: stable@vger.kernel.org Cc: Thomas Weißschuh Cc: Andrey Smirnov Signed-off-by: Hans de Goede Link: https://lore.kernel.org/stable/20240908185337.103696-1-hdegoede%40redhat.com Link: https://lore.kernel.org/r/20240908185337.103696-1-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 0417fb34e846..49534458a9f7 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1231,11 +1231,7 @@ EXPORT_SYMBOL_GPL(power_supply_set_property); int power_supply_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { - if (atomic_read(&psy->use_cnt) <= 0 || - !psy->desc->property_is_writeable) - return -ENODEV; - - return psy->desc->property_is_writeable(psy, psp); + return psy->desc->property_is_writeable && psy->desc->property_is_writeable(psy, psp); } EXPORT_SYMBOL_GPL(power_supply_property_is_writeable); From e50a57d16f897e45de1112eb6478577b197fab52 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 8 Sep 2024 20:53:37 +0200 Subject: [PATCH 556/573] power: supply: hwmon: Fix missing temp1_max_alarm attribute Temp channel 0 aka temp1 can have a temp1_max_alarm attribute for power_supply devices which have a POWER_SUPPLY_PROP_TEMP_ALERT_MAX property. HWMON_T_MAX_ALARM was missing from power_supply_hwmon_info for temp channel 0, causing the hwmon temp1_max_alarm attribute to be missing from such power_supply devices. Add this to power_supply_hwmon_info to fix this. Fixes: f1d33ae806ec ("power: supply: remove duplicated argument in power_supply_hwmon_info") Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20240908185337.103696-2-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_hwmon.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/power/supply/power_supply_hwmon.c b/drivers/power/supply/power_supply_hwmon.c index baacefbdf768..6fbbfb1c685e 100644 --- a/drivers/power/supply/power_supply_hwmon.c +++ b/drivers/power/supply/power_supply_hwmon.c @@ -318,7 +318,8 @@ static const struct hwmon_channel_info * const power_supply_hwmon_info[] = { HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | - HWMON_T_MIN_ALARM, + HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM, HWMON_T_LABEL | HWMON_T_INPUT | From 18bcb4aa54eab75dce41e5c176a1c2bff94f0f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cs=C3=B3k=C3=A1s=2C=20Bence?= Date: Wed, 10 Jul 2024 11:14:01 +0200 Subject: [PATCH 557/573] mtd: spi-nor: sst: Factor out common write operation to `sst_nor_write_data()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Writing to the Flash in `sst_nor_write()` is a 3-step process: first an optional one-byte write to get 2-byte-aligned, then the bulk of the data is written out in vendor-specific 2-byte writes. Finally, if there's a byte left over, another one-byte write. This was implemented 3 times in the body of `sst_nor_write()`. To reduce code duplication, factor out these sub-steps to their own function. Signed-off-by: Csókás, Bence Reviewed-by: Pratyush Yadav [pratyush@kernel.org: fixup whitespace, use %zu instead of %i in WARN()] Signed-off-by: Pratyush Yadav Link: https://lore.kernel.org/r/20240710091401.1282824-1-csokas.bence@prolan.hu --- drivers/mtd/spi-nor/sst.c | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c index 180b7390690c..b5ad7118c49a 100644 --- a/drivers/mtd/spi-nor/sst.c +++ b/drivers/mtd/spi-nor/sst.c @@ -167,6 +167,21 @@ static const struct flash_info sst_nor_parts[] = { } }; +static int sst_nor_write_data(struct spi_nor *nor, loff_t to, size_t len, + const u_char *buf) +{ + u8 op = (len == 1) ? SPINOR_OP_BP : SPINOR_OP_AAI_WP; + int ret; + + nor->program_opcode = op; + ret = spi_nor_write_data(nor, to, 1, buf); + if (ret < 0) + return ret; + WARN(ret != len, "While writing %zu byte written %i bytes\n", len, ret); + + return spi_nor_wait_till_ready(nor); +} + static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { @@ -188,16 +203,10 @@ static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len, /* Start write from odd address. */ if (to % 2) { - nor->program_opcode = SPINOR_OP_BP; - /* write one byte. */ - ret = spi_nor_write_data(nor, to, 1, buf); + ret = sst_nor_write_data(nor, to, 1, buf); if (ret < 0) goto out; - WARN(ret != 1, "While writing 1 byte written %i bytes\n", ret); - ret = spi_nor_wait_till_ready(nor); - if (ret) - goto out; to++; actual++; @@ -205,16 +214,11 @@ static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len, /* Write out most of the data here. */ for (; actual < len - 1; actual += 2) { - nor->program_opcode = SPINOR_OP_AAI_WP; - /* write two bytes. */ - ret = spi_nor_write_data(nor, to, 2, buf + actual); + ret = sst_nor_write_data(nor, to, 2, buf + actual); if (ret < 0) goto out; - WARN(ret != 2, "While writing 2 bytes written %i bytes\n", ret); - ret = spi_nor_wait_till_ready(nor); - if (ret) - goto out; + to += 2; nor->sst_write_second = true; } @@ -234,14 +238,9 @@ static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len, if (ret) goto out; - nor->program_opcode = SPINOR_OP_BP; - ret = spi_nor_write_data(nor, to, 1, buf + actual); + ret = sst_nor_write_data(nor, to, 1, buf + actual); if (ret < 0) goto out; - WARN(ret != 1, "While writing 1 byte written %i bytes\n", ret); - ret = spi_nor_wait_till_ready(nor); - if (ret) - goto out; actual += 1; From 86fd0e6410b453fed93cf8085de1e5b0cfdbb6b9 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Fri, 26 Jul 2024 11:58:18 -0700 Subject: [PATCH 558/573] mtd: spi-nor: micron-st: Add n25q064a WP support These flash chips are used on Google / TP-Link / ASUS OnHub devices, and OnHub devices are write-protected by default (same as any other ChromeOS/Chromebook system). I've referred to datasheets, and tested on OnHub devices. Signed-off-by: Brian Norris Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav Link: https://lore.kernel.org/r/20240726185825.142733-1-computersforpeace@gmail.com --- drivers/mtd/spi-nor/micron-st.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c index 3c6499fdb712..e6bab2d00c92 100644 --- a/drivers/mtd/spi-nor/micron-st.c +++ b/drivers/mtd/spi-nor/micron-st.c @@ -436,6 +436,8 @@ static const struct flash_info st_nor_parts[] = { .id = SNOR_ID(0x20, 0xbb, 0x17), .name = "n25q064a", .size = SZ_8M, + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_4BIT_BP | + SPI_NOR_BP3_SR_BIT6, .no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ, }, { .id = SNOR_ID(0x20, 0xbb, 0x18), From a84d45217c8fbfab3f522dc84767f7f08ab1a85e Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 5 Aug 2024 00:15:35 +0200 Subject: [PATCH 559/573] mtd: spi-nor: winbond: add Zetta ZD25Q128C support Zetta normally uses BAh as its vendor ID. But for the ZD25Q128C they took the one from Winbond and messed up the size parameters in SFDP. Most functions seem compatible with the W25Q128, we just have to fix up the size. Link: http://www.zettadevice.com/upload/file/20150821/DS_Zetta_25Q128_RevA.pdf Link: https://www.lcsc.com/datasheet/lcsc_datasheet_2312081757_Zetta-ZD25Q128CSIGT_C19626875.pdf Signed-off-by: Michael Walle Reviewed-by: Pratyush Yadav Signed-off-by: Pratyush Yadav Link: https://lore.kernel.org/r/20240804221535.291923-1-mwalle@kernel.org --- drivers/mtd/spi-nor/winbond.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c index e065e4fd42a3..9f7ce5763e71 100644 --- a/drivers/mtd/spi-nor/winbond.c +++ b/drivers/mtd/spi-nor/winbond.c @@ -17,6 +17,31 @@ SPI_MEM_OP_NO_DUMMY, \ SPI_MEM_OP_DATA_OUT(1, buf, 0)) +static int +w25q128_post_bfpt_fixups(struct spi_nor *nor, + const struct sfdp_parameter_header *bfpt_header, + const struct sfdp_bfpt *bfpt) +{ + /* + * Zetta ZD25Q128C is a clone of the Winbond device. But the encoded + * size is really wrong. It seems that they confused Mbit with MiB. + * Thus the flash is discovered as a 2MiB device. + */ + if (bfpt_header->major == SFDP_JESD216_MAJOR && + bfpt_header->minor == SFDP_JESD216_MINOR && + nor->params->size == SZ_2M && + nor->params->erase_map.regions[0].size == SZ_2M) { + nor->params->size = SZ_16M; + nor->params->erase_map.regions[0].size = SZ_16M; + } + + return 0; +} + +static const struct spi_nor_fixups w25q128_fixups = { + .post_bfpt = w25q128_post_bfpt_fixups, +}; + static int w25q256_post_bfpt_fixups(struct spi_nor *nor, const struct sfdp_parameter_header *bfpt_header, @@ -108,6 +133,7 @@ static const struct flash_info winbond_nor_parts[] = { .size = SZ_16M, .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB, .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, + .fixups = &w25q128_fixups, }, { .id = SNOR_ID(0xef, 0x40, 0x19), .name = "w25q256", From 1dc6cd4f94ad5c9432dc7c7a1e1ed6c52ec856ad Mon Sep 17 00:00:00 2001 From: Takahiro Kuwano Date: Fri, 30 Aug 2024 17:04:28 +0900 Subject: [PATCH 560/573] mtd: spi-nor: spansion: Add support for S28HS256T Infineon S28HS256T is 256Mb Octal SPI device which has same functionalities with 512Mb and 1Gb parts. Link: https://www.infineon.com/dgdl/Infineon-S28HS256T_S28HL256T_256Mb_SEMPER_Flash_Octal_interface_1_8V_3-DataSheet-v02_00-EN.pdf?fileId=8ac78c8c8fc2dd9c018fc66787aa0657 Signed-off-by: Takahiro Kuwano Reviewed-by: Michael Walle Signed-off-by: Pratyush Yadav Link: https://lore.kernel.org/r/20240830080428.6994-1-Takahiro.Kuwano@infineon.com --- drivers/mtd/spi-nor/spansion.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c index 6cc237c24e07..d6c92595f6bc 100644 --- a/drivers/mtd/spi-nor/spansion.c +++ b/drivers/mtd/spi-nor/spansion.c @@ -966,6 +966,10 @@ static const struct flash_info spansion_nor_parts[] = { .name = "s28hl01gt", .mfr_flags = USE_CLPEF, .fixups = &s28hx_t_fixups, + }, { + .id = SNOR_ID(0x34, 0x5b, 0x19), + .mfr_flags = USE_CLPEF, + .fixups = &s28hx_t_fixups, }, { .id = SNOR_ID(0x34, 0x5b, 0x1a), .name = "s28hs512t", From ac5bfa968b60fba409942ab594ad98479b4e1223 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 9 Sep 2024 09:28:54 +0200 Subject: [PATCH 561/573] mtd: spi-nor: fix flash probing Fix flash probing by name. Flash entries without a name are allowed since commit 15eb8303bb42 ("mtd: spi-nor: mark the flash name as obsolete"). But it was just until recently that a flash entry without a name was actually introduced. This triggers a bug in the legacy probe by name path. Skip entries without a name to fix it. Fixes: 2095e7da8049 ("mtd: spi-nor: spansion: Add support for S28HS256T") Reported-by: Jon Hunter Closes: https://lore.kernel.org/r/66c8ebb0-1324-4ad9-9926-8d4eb7e1e63a@nvidia.com/ Tested-by: Jon Hunter Signed-off-by: Michael Walle Reviewed-by: Tudor Ambarus Reviewed-by: Pratyush Yadav Signed-off-by: Pratyush Yadav Link: https://lore.kernel.org/r/20240909072854.812206-1-mwalle@kernel.org --- drivers/mtd/spi-nor/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index e0c4efc424f4..9d6e85bf227b 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -3281,7 +3281,8 @@ static const struct flash_info *spi_nor_match_name(struct spi_nor *nor, for (i = 0; i < ARRAY_SIZE(manufacturers); i++) { for (j = 0; j < manufacturers[i]->nparts; j++) { - if (!strcmp(name, manufacturers[i]->parts[j].name)) { + if (manufacturers[i]->parts[j].name && + !strcmp(name, manufacturers[i]->parts[j].name)) { nor->manufacturer = manufacturers[i]; return &manufacturers[i]->parts[j]; } From 210860e7f7337e47e77577fa5bb168767e2d8a1e Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Sun, 15 Sep 2024 01:07:27 +0200 Subject: [PATCH 562/573] selftests: vDSO: check cpu caps before running chacha test Some archs -- arm64 and s390x -- implemented chacha using instructions that are available most places, but aren't always available. The kernel handles this just fine, but the selftest does not. Check the hwcaps before running, and skip the test if the cpu doesn't support it. As well, on s390x, always emit the fallback instructions of an alternative block, to ensure maximum compatibility. Co-developed-by: Heiko Carstens Signed-off-by: Heiko Carstens Signed-off-by: Jason A. Donenfeld --- tools/include/asm/alternative.h | 10 ++++++++++ .../testing/selftests/vDSO/vdso_test_chacha.c | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/tools/include/asm/alternative.h b/tools/include/asm/alternative.h index 7ce02a223732..8e548ac8f740 100644 --- a/tools/include/asm/alternative.h +++ b/tools/include/asm/alternative.h @@ -2,8 +2,18 @@ #ifndef _TOOLS_ASM_ALTERNATIVE_ASM_H #define _TOOLS_ASM_ALTERNATIVE_ASM_H +#if defined(__s390x__) +#ifdef __ASSEMBLY__ +.macro ALTERNATIVE oldinstr, newinstr, feature + \oldinstr +.endm +#endif +#else + /* Just disable it so we can build arch/x86/lib/memcpy_64.S for perf bench: */ #define ALTERNATIVE # #endif + +#endif diff --git a/tools/testing/selftests/vDSO/vdso_test_chacha.c b/tools/testing/selftests/vDSO/vdso_test_chacha.c index e81d72c9882e..b1ea532c5996 100644 --- a/tools/testing/selftests/vDSO/vdso_test_chacha.c +++ b/tools/testing/selftests/vDSO/vdso_test_chacha.c @@ -5,11 +5,29 @@ #include #include +#include #include #include #include #include "../kselftest.h" +#if defined(__aarch64__) +static bool cpu_has_capabilities(void) +{ + return getauxval(AT_HWCAP) & HWCAP_ASIMD; +} +#elif defined(__s390x__) +static bool cpu_has_capabilities(void) +{ + return getauxval(AT_HWCAP) & HWCAP_S390_VXRS; +} +#else +static bool cpu_has_capabilities(void) +{ + return true; +} +#endif + static uint32_t rol32(uint32_t word, unsigned int shift) { return (word << (shift & 31)) | (word >> ((-shift) & 31)); @@ -67,6 +85,8 @@ int main(int argc, char *argv[]) uint8_t output1[BLOCK_SIZE * BLOCKS], output2[BLOCK_SIZE * BLOCKS]; ksft_print_header(); + if (!cpu_has_capabilities()) + ksft_exit_skip("Required CPU capabilities missing\n"); ksft_set_plan(1); for (unsigned int trial = 0; trial < TRIALS; ++trial) { From a550d6ae4d73dc4b9f1a2b3ad3d8d9e355b396be Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Fri, 9 Aug 2024 16:05:23 +0800 Subject: [PATCH 563/573] pwm: lp3943: Fix an incorrect type in lp3943_pwm_parse_dt() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The return value from the call to of_property_count_u32_elems() is int. However, the return value is being assigned to an u32 variable 'num_outputs', so making 'num_outputs' an int. ./drivers/pwm/pwm-lp3943.c:238:6-17: WARNING: Unsigned expression compared with zero: num_outputs <= 0. Reported-by: Abaci Robot Closes: https://bugzilla.openanolis.cn/show_bug.cgi?id=9710 Signed-off-by: Jiapeng Chong Fixes: 75f0cb339b78 ("pwm: lp3943: Use of_property_count_u32_elems() to get property length") Link: https://lore.kernel.org/r/20240809080523.32717-1-jiapeng.chong@linux.alibaba.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-lp3943.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-lp3943.c b/drivers/pwm/pwm-lp3943.c index f0e94c9e5956..90b0733c00c1 100644 --- a/drivers/pwm/pwm-lp3943.c +++ b/drivers/pwm/pwm-lp3943.c @@ -218,8 +218,7 @@ static int lp3943_pwm_parse_dt(struct device *dev, struct lp3943_platform_data *pdata; struct lp3943_pwm_map *pwm_map; enum lp3943_pwm_output *output; - int i, err, count = 0; - u32 num_outputs; + int i, err, num_outputs, count = 0; if (!node) return -EINVAL; From 59921a7397074e90030659bbb74aad372effdeee Mon Sep 17 00:00:00 2001 From: Liu Ying Date: Mon, 26 Aug 2024 16:33:37 +0800 Subject: [PATCH 564/573] pwm: adp5585: Set OSC_EN bit to 1 when PWM state is enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It turns out that OSC_EN bit in GERNERAL_CFG register has to be set to 1 when PWM state is enabled, otherwise PWM signal won't be generated. Fixes: e9b503879fd2 ("pwm: adp5585: Add Analog Devices ADP5585 support") Signed-off-by: Liu Ying Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20240826083337.1835405-1-victor.liu@nxp.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-adp5585.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pwm/pwm-adp5585.c b/drivers/pwm/pwm-adp5585.c index ed7e8c6bcf32..40472ac5db64 100644 --- a/drivers/pwm/pwm-adp5585.c +++ b/drivers/pwm/pwm-adp5585.c @@ -100,6 +100,10 @@ static int pwm_adp5585_apply(struct pwm_chip *chip, if (ret) return ret; + ret = regmap_set_bits(regmap, ADP5585_GENERAL_CFG, ADP5585_OSC_EN); + if (ret) + return ret; + return regmap_set_bits(regmap, ADP5585_PWM_CFG, ADP5585_PWM_EN); } From 89deb4c8d09eb6d3548e18db76f03a782c3467ce Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Wed, 31 Jul 2024 13:13:03 -0600 Subject: [PATCH 565/573] pwm: omap-dmtimer: Use of_property_read_bool() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use of_property_read_bool() to read boolean properties rather than of_get_property(). This is part of a larger effort to remove callers of of_get_property() and similar functions. of_get_property() leaks the DT property data pointer which is a problem for dynamically allocated nodes which may be freed. Signed-off-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20240731191312.1710417-25-robh@kernel.org Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-omap-dmtimer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c index cd51c4a938f5..e514f3614c43 100644 --- a/drivers/pwm/pwm-omap-dmtimer.c +++ b/drivers/pwm/pwm-omap-dmtimer.c @@ -355,7 +355,7 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev) goto err_platdata; } - if (!of_get_property(timer, "ti,timer-pwm", NULL)) { + if (!of_property_read_bool(timer, "ti,timer-pwm")) { dev_err(&pdev->dev, "Missing ti,timer-pwm capability\n"); ret = -ENODEV; goto err_timer_property; From 433f1f79050d4d0a19fee6c0ddf1b7988bc04dd6 Mon Sep 17 00:00:00 2001 From: Liao Chen Date: Sat, 31 Aug 2024 07:50:58 +0000 Subject: [PATCH 566/573] pwm: atmel-hlcdc: Enable module autoloading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add MODULE_DEVICE_TABLE(), so modules could be properly autoloaded based on the alias from of_device_id table. Signed-off-by: Liao Chen Link: https://lore.kernel.org/r/20240831075059.790861-2-liaochen4@huawei.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-atmel-hlcdc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c index 2afb302be02c..02660bd811c4 100644 --- a/drivers/pwm/pwm-atmel-hlcdc.c +++ b/drivers/pwm/pwm-atmel-hlcdc.c @@ -290,6 +290,7 @@ static const struct of_device_id atmel_hlcdc_pwm_dt_ids[] = { { .compatible = "atmel,hlcdc-pwm" }, { /* sentinel */ }, }; +MODULE_DEVICE_TABLE(of, atmel_hlcdc_pwm_dt_ids); static struct platform_driver atmel_hlcdc_pwm_driver = { .driver = { From 60cd67a40b74f99afeca91e73bee415341a43993 Mon Sep 17 00:00:00 2001 From: Liao Chen Date: Sat, 31 Aug 2024 07:50:59 +0000 Subject: [PATCH 567/573] pwm: atmel-hlcdc: Drop trailing comma MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the trailing comma in the terminator entry for the ID table to make code robust against misrebases. Signed-off-by: Liao Chen Link: https://lore.kernel.org/r/20240831075059.790861-3-liaochen4@huawei.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-atmel-hlcdc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c index 02660bd811c4..eb39955a6d77 100644 --- a/drivers/pwm/pwm-atmel-hlcdc.c +++ b/drivers/pwm/pwm-atmel-hlcdc.c @@ -234,7 +234,7 @@ static const struct of_device_id atmel_hlcdc_dt_ids[] = { .data = &atmel_hlcdc_pwm_sama5d3_errata, }, { .compatible = "microchip,sam9x60-hlcdc", }, - { /* sentinel */ }, + { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, atmel_hlcdc_dt_ids); @@ -288,7 +288,7 @@ static void atmel_hlcdc_pwm_remove(struct platform_device *pdev) static const struct of_device_id atmel_hlcdc_pwm_dt_ids[] = { { .compatible = "atmel,hlcdc-pwm" }, - { /* sentinel */ }, + { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, atmel_hlcdc_pwm_dt_ids); From 34d973c9c1850971c0ee27150ee33bd85cd7e2b7 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 16 Aug 2024 12:30:58 -0500 Subject: [PATCH 568/573] pwm: axi-pwmgen: use shared macro for version reg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The linux/fpga/adi-axi-common.h header already defines a macro for the version register offset. Use this macro in the axi-pwmgen driver instead of defining it again. Signed-off-by: David Lechner Link: https://lore.kernel.org/r/20240816-pwm-axi-pwmgen-use-shared-macro-v1-1-994153ebc3a7@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-axi-pwmgen.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-axi-pwmgen.c b/drivers/pwm/pwm-axi-pwmgen.c index 3ad60edf20a5..b5477659ba18 100644 --- a/drivers/pwm/pwm-axi-pwmgen.c +++ b/drivers/pwm/pwm-axi-pwmgen.c @@ -29,7 +29,6 @@ #include #include -#define AXI_PWMGEN_REG_CORE_VERSION 0x00 #define AXI_PWMGEN_REG_ID 0x04 #define AXI_PWMGEN_REG_SCRATCHPAD 0x08 #define AXI_PWMGEN_REG_CORE_MAGIC 0x0C @@ -145,7 +144,7 @@ static int axi_pwmgen_setup(struct regmap *regmap, struct device *dev) "failed to read expected value from register: got %08x, expected %08x\n", val, AXI_PWMGEN_REG_CORE_MAGIC_VAL); - ret = regmap_read(regmap, AXI_PWMGEN_REG_CORE_VERSION, &val); + ret = regmap_read(regmap, ADI_AXI_REG_VERSION, &val); if (ret) return ret; From edeedfaa0c3ab7a7468ba876b664ffb2f0f5e0d5 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 18 Aug 2024 19:28:28 +0200 Subject: [PATCH 569/573] dt-bindings: pwm: allwinner,sun4i-a10-pwm: add top-level constraints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Properties with variable number of items per each device are expected to have widest constraints in top-level "properties:" block and further customized (narrowed) in "if:then:". Add missing top-level constraints for clock-names. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20240818172828.121728-1-krzysztof.kozlowski@linaro.org Signed-off-by: Uwe Kleine-König --- .../devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml index 66e400f2a3a4..1b192e197b11 100644 --- a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml +++ b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml @@ -46,10 +46,11 @@ properties: - description: Module Clock - description: Bus Clock - # Even though it only applies to subschemas under the conditionals, - # not listing them here will trigger a warning because of the - # additionalsProperties set to false. - clock-names: true + clock-names: + minItems: 1 + items: + - const: mod + - const: bus resets: maxItems: 1 From 8db7fdffaaf6cc9f21be5f601c7ef12b173074a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 9 Sep 2024 09:31:24 +0200 Subject: [PATCH 570/573] pwm: Switch back to struct platform_driver::remove() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After commit 0edb555a65d1 ("platform: Make platform_driver::remove() return void") .remove() is (again) the right callback to implement for platform drivers. Convert all pwm drivers to use .remove(), with the eventual goal to drop struct platform_driver::remove_new(). As .remove() and .remove_new() have the same prototypes, conversion is done by just changing the structure member name in the driver initializer. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20240909073125.382040-2-u.kleine-koenig@baylibre.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-atmel-hlcdc.c | 2 +- drivers/pwm/pwm-atmel-tcb.c | 2 +- drivers/pwm/pwm-clk.c | 2 +- drivers/pwm/pwm-hibvt.c | 2 +- drivers/pwm/pwm-img.c | 2 +- drivers/pwm/pwm-lpc18xx-sct.c | 2 +- drivers/pwm/pwm-omap-dmtimer.c | 2 +- drivers/pwm/pwm-rcar.c | 2 +- drivers/pwm/pwm-rockchip.c | 2 +- drivers/pwm/pwm-sifive.c | 2 +- drivers/pwm/pwm-sun4i.c | 2 +- drivers/pwm/pwm-tegra.c | 2 +- drivers/pwm/pwm-tiecap.c | 2 +- drivers/pwm/pwm-tiehrpwm.c | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c index eb39955a6d77..387a0d1fa4f2 100644 --- a/drivers/pwm/pwm-atmel-hlcdc.c +++ b/drivers/pwm/pwm-atmel-hlcdc.c @@ -299,7 +299,7 @@ static struct platform_driver atmel_hlcdc_pwm_driver = { .pm = pm_ptr(&atmel_hlcdc_pwm_pm_ops), }, .probe = atmel_hlcdc_pwm_probe, - .remove_new = atmel_hlcdc_pwm_remove, + .remove = atmel_hlcdc_pwm_remove, }; module_platform_driver(atmel_hlcdc_pwm_driver); diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c index f9a9c12cbcdd..5ee4254d1e48 100644 --- a/drivers/pwm/pwm-atmel-tcb.c +++ b/drivers/pwm/pwm-atmel-tcb.c @@ -527,7 +527,7 @@ static struct platform_driver atmel_tcb_pwm_driver = { .pm = pm_ptr(&atmel_tcb_pwm_pm_ops), }, .probe = atmel_tcb_pwm_probe, - .remove_new = atmel_tcb_pwm_remove, + .remove = atmel_tcb_pwm_remove, }; module_platform_driver(atmel_tcb_pwm_driver); diff --git a/drivers/pwm/pwm-clk.c b/drivers/pwm/pwm-clk.c index c19a482d7e28..f8f5af57acba 100644 --- a/drivers/pwm/pwm-clk.c +++ b/drivers/pwm/pwm-clk.c @@ -130,7 +130,7 @@ static struct platform_driver pwm_clk_driver = { .of_match_table = pwm_clk_dt_ids, }, .probe = pwm_clk_probe, - .remove_new = pwm_clk_remove, + .remove = pwm_clk_remove, }; module_platform_driver(pwm_clk_driver); diff --git a/drivers/pwm/pwm-hibvt.c b/drivers/pwm/pwm-hibvt.c index 2eb0b13d4e10..e02ee6383dbc 100644 --- a/drivers/pwm/pwm-hibvt.c +++ b/drivers/pwm/pwm-hibvt.c @@ -276,7 +276,7 @@ static struct platform_driver hibvt_pwm_driver = { .of_match_table = hibvt_pwm_of_match, }, .probe = hibvt_pwm_probe, - .remove_new = hibvt_pwm_remove, + .remove = hibvt_pwm_remove, }; module_platform_driver(hibvt_pwm_driver); diff --git a/drivers/pwm/pwm-img.c b/drivers/pwm/pwm-img.c index d6596583ed4e..71542956feca 100644 --- a/drivers/pwm/pwm-img.c +++ b/drivers/pwm/pwm-img.c @@ -416,7 +416,7 @@ static struct platform_driver img_pwm_driver = { .of_match_table = img_pwm_of_match, }, .probe = img_pwm_probe, - .remove_new = img_pwm_remove, + .remove = img_pwm_remove, }; module_platform_driver(img_pwm_driver); diff --git a/drivers/pwm/pwm-lpc18xx-sct.c b/drivers/pwm/pwm-lpc18xx-sct.c index 04b76d257fd8..f351baa63453 100644 --- a/drivers/pwm/pwm-lpc18xx-sct.c +++ b/drivers/pwm/pwm-lpc18xx-sct.c @@ -446,7 +446,7 @@ static struct platform_driver lpc18xx_pwm_driver = { .of_match_table = lpc18xx_pwm_of_match, }, .probe = lpc18xx_pwm_probe, - .remove_new = lpc18xx_pwm_remove, + .remove = lpc18xx_pwm_remove, }; module_platform_driver(lpc18xx_pwm_driver); diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c index e514f3614c43..1858a77401f8 100644 --- a/drivers/pwm/pwm-omap-dmtimer.c +++ b/drivers/pwm/pwm-omap-dmtimer.c @@ -455,7 +455,7 @@ static struct platform_driver pwm_omap_dmtimer_driver = { .of_match_table = pwm_omap_dmtimer_of_match, }, .probe = pwm_omap_dmtimer_probe, - .remove_new = pwm_omap_dmtimer_remove, + .remove = pwm_omap_dmtimer_remove, }; module_platform_driver(pwm_omap_dmtimer_driver); diff --git a/drivers/pwm/pwm-rcar.c b/drivers/pwm/pwm-rcar.c index 4cfecd88ede0..2261789cc27d 100644 --- a/drivers/pwm/pwm-rcar.c +++ b/drivers/pwm/pwm-rcar.c @@ -253,7 +253,7 @@ MODULE_DEVICE_TABLE(of, rcar_pwm_of_table); static struct platform_driver rcar_pwm_driver = { .probe = rcar_pwm_probe, - .remove_new = rcar_pwm_remove, + .remove = rcar_pwm_remove, .driver = { .name = "pwm-rcar", .of_match_table = rcar_pwm_of_table, diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c index 0fa7575dbb54..c5f50e5eaf41 100644 --- a/drivers/pwm/pwm-rockchip.c +++ b/drivers/pwm/pwm-rockchip.c @@ -386,7 +386,7 @@ static struct platform_driver rockchip_pwm_driver = { .of_match_table = rockchip_pwm_dt_ids, }, .probe = rockchip_pwm_probe, - .remove_new = rockchip_pwm_remove, + .remove = rockchip_pwm_remove, }; module_platform_driver(rockchip_pwm_driver); diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c index ed7957cc51fd..d5b647e6be78 100644 --- a/drivers/pwm/pwm-sifive.c +++ b/drivers/pwm/pwm-sifive.c @@ -336,7 +336,7 @@ MODULE_DEVICE_TABLE(of, pwm_sifive_of_match); static struct platform_driver pwm_sifive_driver = { .probe = pwm_sifive_probe, - .remove_new = pwm_sifive_remove, + .remove = pwm_sifive_remove, .driver = { .name = "pwm-sifive", .of_match_table = pwm_sifive_of_match, diff --git a/drivers/pwm/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c index 5c29590d1821..e60dc7d6b591 100644 --- a/drivers/pwm/pwm-sun4i.c +++ b/drivers/pwm/pwm-sun4i.c @@ -493,7 +493,7 @@ static struct platform_driver sun4i_pwm_driver = { .of_match_table = sun4i_pwm_dt_ids, }, .probe = sun4i_pwm_probe, - .remove_new = sun4i_pwm_remove, + .remove = sun4i_pwm_remove, }; module_platform_driver(sun4i_pwm_driver); diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index a3d69976148f..172063b51d44 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -432,7 +432,7 @@ static struct platform_driver tegra_pwm_driver = { .pm = &tegra_pwm_pm_ops, }, .probe = tegra_pwm_probe, - .remove_new = tegra_pwm_remove, + .remove = tegra_pwm_remove, }; module_platform_driver(tegra_pwm_driver); diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c index d6c2b1b1387e..d91b2bdc88fc 100644 --- a/drivers/pwm/pwm-tiecap.c +++ b/drivers/pwm/pwm-tiecap.c @@ -324,7 +324,7 @@ static struct platform_driver ecap_pwm_driver = { .pm = pm_ptr(&ecap_pwm_pm_ops), }, .probe = ecap_pwm_probe, - .remove_new = ecap_pwm_remove, + .remove = ecap_pwm_remove, }; module_platform_driver(ecap_pwm_driver); diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c index e5104725d9b7..0125e73b98df 100644 --- a/drivers/pwm/pwm-tiehrpwm.c +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -603,7 +603,7 @@ static struct platform_driver ehrpwm_pwm_driver = { .pm = pm_ptr(&ehrpwm_pwm_pm_ops), }, .probe = ehrpwm_pwm_probe, - .remove_new = ehrpwm_pwm_remove, + .remove = ehrpwm_pwm_remove, }; module_platform_driver(ehrpwm_pwm_driver); From 6e50721426e48f63be53b4732bd08029633933d1 Mon Sep 17 00:00:00 2001 From: George Stark Date: Thu, 11 Jul 2024 02:41:14 +0300 Subject: [PATCH 571/573] dt-bindings: pwm: amlogic: Add optional power-domains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On newer SoCs, the PWM hardware can require a power domain to operate so add corresponding optional property. Signed-off-by: George Stark Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20240710234116.2370655-2-gnstark@salutedevices.com Signed-off-by: Uwe Kleine-König --- Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml b/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml index 1d71d4f8f328..267908c2bf7b 100644 --- a/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml +++ b/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml @@ -56,6 +56,9 @@ properties: minItems: 1 maxItems: 2 + power-domains: + maxItems: 1 + "#pwm-cells": const: 3 From a4cf667d7791cd1fbb501f89a957d18797e6f111 Mon Sep 17 00:00:00 2001 From: George Stark Date: Thu, 11 Jul 2024 02:41:15 +0300 Subject: [PATCH 572/573] dt-bindings: pwm: amlogic: Add new bindings for meson A1 PWM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chip has 3 dual-channel PWM modules PWM_AB, PWM_CD, PWM_EF. Signed-off-by: George Stark Signed-off-by: Dmitry Rokosov Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20240710234116.2370655-3-gnstark@salutedevices.com Signed-off-by: Uwe Kleine-König --- .../devicetree/bindings/pwm/pwm-amlogic.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml b/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml index 267908c2bf7b..e021cf59421a 100644 --- a/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml +++ b/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml @@ -37,6 +37,10 @@ properties: - enum: - amlogic,meson8-pwm-v2 - amlogic,meson-s4-pwm + - items: + - enum: + - amlogic,meson-a1-pwm + - const: amlogic,meson-s4-pwm - items: - enum: - amlogic,meson8b-pwm-v2 @@ -139,6 +143,16 @@ allOf: required: - clocks + - if: + properties: + compatible: + contains: + enum: + - amlogic,meson-a1-pwm + then: + required: + - power-domains + additionalProperties: false examples: From d242feaf81d63b25d8c1fb1a68738dc33966a376 Mon Sep 17 00:00:00 2001 From: Andrew Kreimer Date: Thu, 12 Sep 2024 15:49:34 +0300 Subject: [PATCH 573/573] pwm: stm32: Fix a typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a typo in comments. Reported-by: Matthew Wilcox Signed-off-by: Andrew Kreimer Link: https://lore.kernel.org/r/20240912124944.43284-1-algonell@gmail.com Signed-off-by: Uwe Kleine-König --- drivers/pwm/pwm-stm32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c index f85eb41cb084..eb24054f9729 100644 --- a/drivers/pwm/pwm-stm32.c +++ b/drivers/pwm/pwm-stm32.c @@ -222,7 +222,7 @@ static int stm32_pwm_capture(struct pwm_chip *chip, struct pwm_device *pwm, scale = max_arr / min(max_arr, raw_prd); } else { - scale = priv->max_arr; /* bellow resolution, use max scale */ + scale = priv->max_arr; /* below resolution, use max scale */ } if (psc && scale > 1) {